diff options
Diffstat (limited to 'lib/isc')
328 files changed, 91822 insertions, 0 deletions
diff --git a/lib/isc/Kyuafile b/lib/isc/Kyuafile new file mode 100644 index 0000000..c796010 --- /dev/null +++ b/lib/isc/Kyuafile @@ -0,0 +1,15 @@ +-- 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. + +syntax(2) +test_suite('bind9') + +include('tests/Kyuafile') diff --git a/lib/isc/Makefile.in b/lib/isc/Makefile.in new file mode 100644 index 0000000..08f1281 --- /dev/null +++ b/lib/isc/Makefile.in @@ -0,0 +1,144 @@ +# 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. + +srcdir = @srcdir@ +VPATH = @srcdir@ +top_srcdir = @top_srcdir@ + +VERSION=@BIND9_VERSION@ + +@BIND9_MAKE_INCLUDES@ + +CINCLUDES = -I${srcdir}/unix/include \ + -I${srcdir}/pthreads/include \ + -I./include \ + -I${srcdir}/include \ + -I${srcdir} \ + ${DNS_INCLUDES} \ + ${OPENSSL_CFLAGS} \ + ${JSON_C_CFLAGS} \ + ${LIBXML2_CFLAGS} \ + ${LIBUV_CFLAGS} \ + ${ZLIB_CFLAGS} +CDEFINES = +CWARNINGS = + +# Alphabetically +UNIXOBJS = unix/pk11_api.@O@ \ + unix/dir.@O@ unix/errno.@O@ \ + unix/errno2result.@O@ unix/file.@O@ unix/fsaccess.@O@ \ + unix/interfaceiter.@O@ unix/meminfo.@O@ \ + unix/net.@O@ unix/os.@O@ unix/resource.@O@ unix/socket.@O@ \ + unix/stdio.@O@ unix/stdtime.@O@ \ + unix/syslog.@O@ unix/time.@O@ + +THREADOBJS = pthreads/condition.@O@ pthreads/mutex.@O@ pthreads/thread.@O@ + +WIN32OBJS = win32/condition.@O@ win32/dir.@O@ win32/errno.@O@ \ + win32/file.@O@ win32/fsaccess.@O@ \ + win32/meminfo.@O@ win32/once.@O@ \ + win32/stdtime.@O@ win32/thread.@O@ win32/time.@O@ + +# Alphabetically +OBJS = pk11.@O@ pk11_result.@O@ \ + aes.@O@ app.@O@ assertions.@O@ astack.@O@ \ + backtrace.@O@ base32.@O@ base64.@O@ \ + bind9.@O@ buffer.@O@ bufferlist.@O@ \ + commandline.@O@ counter.@O@ crc64.@O@ error.@O@ entropy.@O@ \ + event.@O@ hash.@O@ ht.@O@ heap.@O@ hex.@O@ \ + hmac.@O@ httpd.@O@ iterated_hash.@O@ \ + lex.@O@ lfsr.@O@ lib.@O@ log.@O@ \ + managers.@O@ md.@O@ mem.@O@ mutexblock.@O@ \ + netmgr/netmgr.@O@ netmgr/tcp.@O@ netmgr/udp.@O@ \ + netmgr/tcpdns.@O@ \ + netmgr/uverr2result.@O@ netmgr/uv-compat.@O@ \ + netaddr.@O@ netscope.@O@ nonce.@O@ openssl_shim.@O@ pool.@O@ \ + parseint.@O@ portset.@O@ quota.@O@ \ + radix.@O@ random.@O@ ratelimiter.@O@ \ + region.@O@ regex.@O@ result.@O@ rwlock.@O@ \ + safe.@O@ serial.@O@ siphash.@O@ sockaddr.@O@ stats.@O@ \ + string.@O@ symtab.@O@ task.@O@ taskpool.@O@ tls.@O@ \ + trampoline.@O@ \ + url.@O@ utf8.@O@ tm.@O@ timer.@O@ version.@O@ \ + ${UNIXOBJS} ${THREADOBJS} +SYMTBLOBJS = backtrace-emptytbl.@O@ + +# Alphabetically +SRCS = pk11.c pk11_result.c \ + aes.c app.c assertions.c astack.c \ + backtrace.c base32.c base64.c bind9.c \ + buffer.c bufferlist.c commandline.c counter.c crc64.c \ + entropy.c error.c event.c hash.c ht.c heap.c \ + hex.c hmac.c httpd.c iterated_hash.c \ + lex.c lfsr.c lib.c log.c \ + managers.c md.c mem.c mutexblock.c \ + netaddr.c netscope.c nonce.c openssl_shim.c pool.c \ + parseint.c portset.c quota.c radix.c random.c \ + ratelimiter.c region.c regex.c result.c rwlock.c \ + safe.c serial.c siphash.c sockaddr.c stats.c string.c \ + symtab.c task.c taskpool.c timer.c tls.c \ + trampoline.c \ + url.c utf8.c tm.c version.c + +LIBS = ${OPENSSL_LIBS} ${JSON_C_LIBS} ${LIBUV_LIBS} ${LIBXML2_LIBS} ${ZLIB_LIBS} @LIBS@ + +# Note: the order of SUBDIRS is important. +# Attempt to disable parallel processing. +.NOTPARALLEL: +.NO_PARALLEL: +SUBDIRS = include netmgr unix pthreads +TARGETS = timestamp +TESTDIRS = @UNITTESTS@ + +@BIND9_MAKE_RULES@ + +version.@O@: version.c + ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} \ + -DVERSION=\"${VERSION}\" \ + -c ${srcdir}/version.c + +libisc.@SA@: ${OBJS} ${SYMTBLOBJS} + ${AR} ${ARFLAGS} $@ ${OBJS} ${SYMTBLOBJS} + ${RANLIB} $@ + +libisc-nosymtbl.@SA@: ${OBJS} + ${AR} ${ARFLAGS} $@ ${OBJS} + ${RANLIB} $@ + +libisc.la: ${OBJS} ${SYMTBLOBJS} + ${LIBTOOL_MODE_LINK} \ + ${CC} ${ALL_CFLAGS} ${LDFLAGS} -o libisc.la -rpath ${libdir} \ + -release "${VERSION}" \ + ${OBJS} ${SYMTBLOBJS} ${LIBS} + +libisc-nosymtbl.la: ${OBJS} + ${LIBTOOL_MODE_LINK} \ + ${CC} ${ALL_CFLAGS} ${LDFLAGS} -o libisc-nosymtbl.la -rpath ${libdir} \ + -release "${VERSION}" \ + ${OBJS} ${LIBS} + +timestamp: libisc.@A@ libisc-nosymtbl.@A@ + touch timestamp + +testdirs: libisc.@A@ libisc-nosymtbl.@A@ + +installdirs: + $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${libdir} + +install:: timestamp installdirs + ${LIBTOOL_MODE_INSTALL} ${INSTALL_LIBRARY} libisc.@A@ ${DESTDIR}${libdir} + +uninstall:: + ${LIBTOOL_MODE_UNINSTALL} rm -f ${DESTDIR}${libdir}/libisc.@A@ + +clean distclean:: + rm -f libisc.@A@ libisc-nosymtbl.@A@ libisc.la \ + libisc-nosymtbl.la timestamp diff --git a/lib/isc/aes.c b/lib/isc/aes.c new file mode 100644 index 0000000..a0e23c5 --- /dev/null +++ b/lib/isc/aes.c @@ -0,0 +1,72 @@ +/* + * 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. + */ + +/*! \file isc/aes.c */ + +#include <openssl/evp.h> +#include <openssl/opensslv.h> + +#include <isc/aes.h> +#include <isc/assertions.h> +#include <isc/platform.h> +#include <isc/string.h> +#include <isc/types.h> +#include <isc/util.h> + +void +isc_aes128_crypt(const unsigned char *key, const unsigned char *in, + unsigned char *out) { + EVP_CIPHER_CTX *c; + int len; + + c = EVP_CIPHER_CTX_new(); + RUNTIME_CHECK(c != NULL); + RUNTIME_CHECK(EVP_EncryptInit(c, EVP_aes_128_ecb(), key, NULL) == 1); + EVP_CIPHER_CTX_set_padding(c, 0); + RUNTIME_CHECK( + EVP_EncryptUpdate(c, out, &len, in, ISC_AES_BLOCK_LENGTH) == 1); + RUNTIME_CHECK(len == ISC_AES_BLOCK_LENGTH); + EVP_CIPHER_CTX_free(c); +} + +void +isc_aes192_crypt(const unsigned char *key, const unsigned char *in, + unsigned char *out) { + EVP_CIPHER_CTX *c; + int len; + + c = EVP_CIPHER_CTX_new(); + RUNTIME_CHECK(c != NULL); + RUNTIME_CHECK(EVP_EncryptInit(c, EVP_aes_192_ecb(), key, NULL) == 1); + EVP_CIPHER_CTX_set_padding(c, 0); + RUNTIME_CHECK( + EVP_EncryptUpdate(c, out, &len, in, ISC_AES_BLOCK_LENGTH) == 1); + RUNTIME_CHECK(len == ISC_AES_BLOCK_LENGTH); + EVP_CIPHER_CTX_free(c); +} + +void +isc_aes256_crypt(const unsigned char *key, const unsigned char *in, + unsigned char *out) { + EVP_CIPHER_CTX *c; + int len; + + c = EVP_CIPHER_CTX_new(); + RUNTIME_CHECK(c != NULL); + RUNTIME_CHECK(EVP_EncryptInit(c, EVP_aes_256_ecb(), key, NULL) == 1); + EVP_CIPHER_CTX_set_padding(c, 0); + RUNTIME_CHECK( + EVP_EncryptUpdate(c, out, &len, in, ISC_AES_BLOCK_LENGTH) == 1); + RUNTIME_CHECK(len == ISC_AES_BLOCK_LENGTH); + EVP_CIPHER_CTX_free(c); +} diff --git a/lib/isc/app.c b/lib/isc/app.c new file mode 100644 index 0000000..a3cc7f6 --- /dev/null +++ b/lib/isc/app.c @@ -0,0 +1,544 @@ +/* + * 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. + */ + +/*! \file */ + +#include <errno.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdlib.h> +#include <sys/types.h> +#include <unistd.h> + +#ifndef WIN32 +#include <inttypes.h> +#include <signal.h> +#include <sys/time.h> +#endif /* WIN32 */ + +#include <isc/app.h> +#include <isc/atomic.h> +#include <isc/condition.h> +#include <isc/event.h> +#include <isc/mem.h> +#include <isc/mutex.h> +#include <isc/platform.h> +#include <isc/strerr.h> +#include <isc/string.h> +#include <isc/task.h> +#include <isc/thread.h> +#include <isc/time.h> +#include <isc/util.h> + +#ifdef WIN32 +#include <process.h> +#else /* WIN32 */ +#include <pthread.h> +#endif /* WIN32 */ + +/*% + * For BIND9 internal applications built with threads, we use a single app + * context and let multiple worker, I/O, timer threads do actual jobs. + */ + +static isc_thread_t blockedthread; +static atomic_bool is_running = 0; + +#ifdef WIN32 +/* + * We need to remember which thread is the main thread... + */ +static isc_thread_t main_thread; +#endif /* ifdef WIN32 */ + +/* + * The application context of this module. + */ +#define APPCTX_MAGIC ISC_MAGIC('A', 'p', 'c', 'x') +#define VALID_APPCTX(c) ISC_MAGIC_VALID(c, APPCTX_MAGIC) + +#ifdef WIN32 +#define NUM_EVENTS 2 + +enum { RELOAD_EVENT, SHUTDOWN_EVENT }; +#endif /* WIN32 */ + +struct isc_appctx { + unsigned int magic; + isc_mem_t *mctx; + isc_mutex_t lock; + isc_eventlist_t on_run; + atomic_bool shutdown_requested; + atomic_bool running; + atomic_bool want_shutdown; + atomic_bool want_reload; + atomic_bool blocked; +#ifdef WIN32 + HANDLE hEvents[NUM_EVENTS]; +#else /* WIN32 */ + isc_mutex_t readylock; + isc_condition_t ready; +#endif /* WIN32 */ +}; + +static isc_appctx_t isc_g_appctx; + +#ifndef WIN32 +static void +handle_signal(int sig, void (*handler)(int)) { + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = handler; + + if (sigfillset(&sa.sa_mask) != 0 || sigaction(sig, &sa, NULL) < 0) { + char strbuf[ISC_STRERRORSIZE]; + strerror_r(errno, strbuf, sizeof(strbuf)); + isc_error_fatal(__FILE__, __LINE__, + "handle_signal() %d setup: %s", sig, strbuf); + } +} +#endif /* ifndef WIN32 */ + +isc_result_t +isc_app_ctxstart(isc_appctx_t *ctx) { + REQUIRE(VALID_APPCTX(ctx)); + + /* + * Start an ISC library application. + */ + + isc_mutex_init(&ctx->lock); + +#ifndef WIN32 + isc_mutex_init(&ctx->readylock); + isc_condition_init(&ctx->ready); +#endif /* WIN32 */ + + ISC_LIST_INIT(ctx->on_run); + + atomic_init(&ctx->shutdown_requested, false); + atomic_init(&ctx->running, false); + atomic_init(&ctx->want_shutdown, false); + atomic_init(&ctx->want_reload, false); + atomic_init(&ctx->blocked, false); + +#ifdef WIN32 + main_thread = GetCurrentThread(); + + /* Create the reload event in a non-signaled state */ + ctx->hEvents[RELOAD_EVENT] = CreateEvent(NULL, FALSE, FALSE, NULL); + + /* Create the shutdown event in a non-signaled state */ + ctx->hEvents[SHUTDOWN_EVENT] = CreateEvent(NULL, FALSE, FALSE, NULL); +#else /* WIN32 */ + int presult; + sigset_t sset; + char strbuf[ISC_STRERRORSIZE]; + + /* + * Always ignore SIGPIPE. + */ + handle_signal(SIGPIPE, SIG_IGN); + + handle_signal(SIGHUP, SIG_DFL); + handle_signal(SIGTERM, SIG_DFL); + handle_signal(SIGINT, SIG_DFL); + + /* + * Block SIGHUP, SIGINT, SIGTERM. + * + * If isc_app_start() is called from the main thread before any other + * threads have been created, then the pthread_sigmask() call below + * will result in all threads having SIGHUP, SIGINT and SIGTERM + * blocked by default, ensuring that only the thread that calls + * sigwait() for them will get those signals. + */ + if (sigemptyset(&sset) != 0 || sigaddset(&sset, SIGHUP) != 0 || + sigaddset(&sset, SIGINT) != 0 || sigaddset(&sset, SIGTERM) != 0) + { + strerror_r(errno, strbuf, sizeof(strbuf)); + isc_error_fatal(__FILE__, __LINE__, + "isc_app_start() sigsetops: %s", strbuf); + } + presult = pthread_sigmask(SIG_BLOCK, &sset, NULL); + if (presult != 0) { + strerror_r(presult, strbuf, sizeof(strbuf)); + isc_error_fatal(__FILE__, __LINE__, + "isc_app_start() pthread_sigmask: %s", strbuf); + } + +#endif /* WIN32 */ + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_app_start(void) { + isc_g_appctx.magic = APPCTX_MAGIC; + isc_g_appctx.mctx = NULL; + /* The remaining members will be initialized in ctxstart() */ + + return (isc_app_ctxstart(&isc_g_appctx)); +} + +isc_result_t +isc_app_onrun(isc_mem_t *mctx, isc_task_t *task, isc_taskaction_t action, + void *arg) { + return (isc_app_ctxonrun(&isc_g_appctx, mctx, task, action, arg)); +} + +isc_result_t +isc_app_ctxonrun(isc_appctx_t *ctx, isc_mem_t *mctx, isc_task_t *task, + isc_taskaction_t action, void *arg) { + isc_event_t *event; + isc_task_t *cloned_task = NULL; + + if (atomic_load_acquire(&ctx->running)) { + return (ISC_R_ALREADYRUNNING); + } + + /* + * Note that we store the task to which we're going to send the event + * in the event's "sender" field. + */ + isc_task_attach(task, &cloned_task); + event = isc_event_allocate(mctx, cloned_task, ISC_APPEVENT_SHUTDOWN, + action, arg, sizeof(*event)); + + LOCK(&ctx->lock); + ISC_LINK_INIT(event, ev_link); + ISC_LIST_APPEND(ctx->on_run, event, ev_link); + UNLOCK(&ctx->lock); + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_app_ctxrun(isc_appctx_t *ctx) { + isc_event_t *event, *next_event; + isc_task_t *task; + + REQUIRE(VALID_APPCTX(ctx)); + +#ifdef WIN32 + REQUIRE(main_thread == GetCurrentThread()); +#endif /* ifdef WIN32 */ + + if (atomic_compare_exchange_strong_acq_rel(&ctx->running, + &(bool){ false }, true)) + { + /* + * Post any on-run events (in FIFO order). + */ + LOCK(&ctx->lock); + for (event = ISC_LIST_HEAD(ctx->on_run); event != NULL; + event = next_event) + { + next_event = ISC_LIST_NEXT(event, ev_link); + ISC_LIST_UNLINK(ctx->on_run, event, ev_link); + task = event->ev_sender; + event->ev_sender = NULL; + isc_task_sendanddetach(&task, &event); + } + UNLOCK(&ctx->lock); + } + +#ifndef WIN32 + /* + * BIND9 internal tools using multiple contexts do not + * rely on signal. */ + if (isc_bind9 && ctx != &isc_g_appctx) { + return (ISC_R_SUCCESS); + } +#endif /* WIN32 */ + + /* + * There is no danger if isc_app_shutdown() is called before we + * wait for signals. Signals are blocked, so any such signal will + * simply be made pending and we will get it when we call + * sigwait(). + */ + while (!atomic_load_acquire(&ctx->want_shutdown)) { +#ifdef WIN32 + DWORD dwWaitResult = WaitForMultipleObjects( + NUM_EVENTS, ctx->hEvents, FALSE, INFINITE); + + /* See why we returned */ + + if (WaitSucceeded(dwWaitResult, NUM_EVENTS)) { + /* + * The return was due to one of the events + * being signaled + */ + switch (WaitSucceededIndex(dwWaitResult)) { + case RELOAD_EVENT: + atomic_store_release(&ctx->want_reload, true); + + break; + + case SHUTDOWN_EVENT: + atomic_store_release(&ctx->want_shutdown, true); + break; + } + } +#else /* WIN32 */ + if (isc_bind9) { + sigset_t sset; + int sig; + /* + * BIND9 internal; single context: + * Wait for SIGHUP, SIGINT, or SIGTERM. + */ + if (sigemptyset(&sset) != 0 || + sigaddset(&sset, SIGHUP) != 0 || + sigaddset(&sset, SIGINT) != 0 || + sigaddset(&sset, SIGTERM) != 0) + { + char strbuf[ISC_STRERRORSIZE]; + strerror_r(errno, strbuf, sizeof(strbuf)); + isc_error_fatal(__FILE__, __LINE__, + "isc_app_run() sigsetops: %s", + strbuf); + } + + if (sigwait(&sset, &sig) == 0) { + switch (sig) { + case SIGINT: + case SIGTERM: + atomic_store_release( + &ctx->want_shutdown, true); + break; + case SIGHUP: + atomic_store_release(&ctx->want_reload, + true); + break; + default: + UNREACHABLE(); + } + } + } else { + /* + * External, or BIND9 using multiple contexts: + * wait until woken up. + */ + if (atomic_load_acquire(&ctx->want_shutdown)) { + break; + } + if (!atomic_load_acquire(&ctx->want_reload)) { + LOCK(&ctx->readylock); + WAIT(&ctx->ready, &ctx->readylock); + UNLOCK(&ctx->readylock); + } + } +#endif /* WIN32 */ + if (atomic_compare_exchange_strong_acq_rel( + &ctx->want_reload, &(bool){ true }, false)) + { + return (ISC_R_RELOAD); + } + + if (atomic_load_acquire(&ctx->want_shutdown) && + atomic_load_acquire(&ctx->blocked)) + { + exit(1); + } + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_app_run(void) { + isc_result_t result; + + REQUIRE(atomic_compare_exchange_strong_acq_rel(&is_running, + &(bool){ false }, true)); + result = isc_app_ctxrun(&isc_g_appctx); + atomic_store_release(&is_running, false); + + return (result); +} + +bool +isc_app_isrunning() { + return (atomic_load_acquire(&is_running)); +} + +void +isc_app_ctxshutdown(isc_appctx_t *ctx) { + REQUIRE(VALID_APPCTX(ctx)); + + REQUIRE(atomic_load_acquire(&ctx->running)); + + /* If ctx->shutdown_requested == true, we are already shutting + * down and we want to just bail out. + */ + if (atomic_compare_exchange_strong_acq_rel(&ctx->shutdown_requested, + &(bool){ false }, true)) + { +#ifdef WIN32 + SetEvent(ctx->hEvents[SHUTDOWN_EVENT]); +#else /* WIN32 */ + if (isc_bind9 && ctx != &isc_g_appctx) { + /* BIND9 internal, but using multiple contexts */ + atomic_store_release(&ctx->want_shutdown, true); + } else if (isc_bind9) { + /* BIND9 internal, single context */ + if (kill(getpid(), SIGTERM) < 0) { + char strbuf[ISC_STRERRORSIZE]; + strerror_r(errno, strbuf, sizeof(strbuf)); + isc_error_fatal(__FILE__, __LINE__, + "isc_app_shutdown() " + "kill: %s", + strbuf); + } + } else { + /* External, multiple contexts */ + atomic_store_release(&ctx->want_shutdown, true); + SIGNAL(&ctx->ready); + } +#endif /* WIN32 */ + } +} + +void +isc_app_shutdown(void) { + isc_app_ctxshutdown(&isc_g_appctx); +} + +void +isc_app_ctxsuspend(isc_appctx_t *ctx) { + REQUIRE(VALID_APPCTX(ctx)); + + REQUIRE(atomic_load(&ctx->running)); + + /* + * Don't send the reload signal if we're shutting down. + */ + if (!atomic_load_acquire(&ctx->shutdown_requested)) { +#ifdef WIN32 + SetEvent(ctx->hEvents[RELOAD_EVENT]); +#else /* WIN32 */ + if (isc_bind9 && ctx != &isc_g_appctx) { + /* BIND9 internal, but using multiple contexts */ + atomic_store_release(&ctx->want_reload, true); + } else if (isc_bind9) { + /* BIND9 internal, single context */ + if (kill(getpid(), SIGHUP) < 0) { + char strbuf[ISC_STRERRORSIZE]; + strerror_r(errno, strbuf, sizeof(strbuf)); + isc_error_fatal(__FILE__, __LINE__, + "isc_app_reload() " + "kill: %s", + strbuf); + } + } else { + /* External, multiple contexts */ + atomic_store_release(&ctx->want_reload, true); + SIGNAL(&ctx->ready); + } +#endif /* WIN32 */ + } +} + +void +isc_app_reload(void) { + isc_app_ctxsuspend(&isc_g_appctx); +} + +void +isc_app_ctxfinish(isc_appctx_t *ctx) { + REQUIRE(VALID_APPCTX(ctx)); + + isc_mutex_destroy(&ctx->lock); +#ifndef WIN32 + isc_mutex_destroy(&ctx->readylock); + isc_condition_destroy(&ctx->ready); +#endif /* WIN32 */ +} + +void +isc_app_finish(void) { + isc_app_ctxfinish(&isc_g_appctx); +} + +void +isc_app_block(void) { + REQUIRE(atomic_load_acquire(&isc_g_appctx.running)); + REQUIRE(atomic_compare_exchange_strong_acq_rel(&isc_g_appctx.blocked, + &(bool){ false }, true)); + +#ifdef WIN32 + blockedthread = GetCurrentThread(); +#else /* WIN32 */ + sigset_t sset; + blockedthread = pthread_self(); + RUNTIME_CHECK(sigemptyset(&sset) == 0 && + sigaddset(&sset, SIGINT) == 0 && + sigaddset(&sset, SIGTERM) == 0); + RUNTIME_CHECK(pthread_sigmask(SIG_UNBLOCK, &sset, NULL) == 0); +#endif /* WIN32 */ +} + +void +isc_app_unblock(void) { + REQUIRE(atomic_load_acquire(&isc_g_appctx.running)); + REQUIRE(atomic_compare_exchange_strong_acq_rel(&isc_g_appctx.blocked, + &(bool){ true }, false)); + +#ifdef WIN32 + REQUIRE(blockedthread == GetCurrentThread()); +#else /* WIN32 */ + REQUIRE(blockedthread == pthread_self()); + + sigset_t sset; + RUNTIME_CHECK(sigemptyset(&sset) == 0 && + sigaddset(&sset, SIGINT) == 0 && + sigaddset(&sset, SIGTERM) == 0); + RUNTIME_CHECK(pthread_sigmask(SIG_BLOCK, &sset, NULL) == 0); +#endif /* WIN32 */ +} + +isc_result_t +isc_appctx_create(isc_mem_t *mctx, isc_appctx_t **ctxp) { + isc_appctx_t *ctx; + + REQUIRE(mctx != NULL); + REQUIRE(ctxp != NULL && *ctxp == NULL); + + ctx = isc_mem_get(mctx, sizeof(*ctx)); + + ctx->magic = APPCTX_MAGIC; + + ctx->mctx = NULL; + isc_mem_attach(mctx, &ctx->mctx); + + *ctxp = ctx; + + return (ISC_R_SUCCESS); +} + +void +isc_appctx_destroy(isc_appctx_t **ctxp) { + isc_appctx_t *ctx; + + REQUIRE(ctxp != NULL); + ctx = *ctxp; + *ctxp = NULL; + REQUIRE(VALID_APPCTX(ctx)); + + ctx->magic = 0; + + isc_mem_putanddetach(&ctx->mctx, ctx, sizeof(*ctx)); +} diff --git a/lib/isc/assertions.c b/lib/isc/assertions.c new file mode 100644 index 0000000..1f16fb3 --- /dev/null +++ b/lib/isc/assertions.c @@ -0,0 +1,129 @@ +/* + * 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. + */ + +/*! \file */ + +#include <stdio.h> +#include <stdlib.h> + +#include <isc/assertions.h> +#include <isc/backtrace.h> +#include <isc/print.h> +#include <isc/result.h> + +/* + * The maximum number of stack frames to dump on assertion failure. + */ +#ifndef BACKTRACE_MAXFRAME +#define BACKTRACE_MAXFRAME 128 +#endif /* ifndef BACKTRACE_MAXFRAME */ + +/*% + * Forward. + */ +static void +default_callback(const char *, int, isc_assertiontype_t, const char *); + +static isc_assertioncallback_t isc_assertion_failed_cb = default_callback; + +/*% + * Public. + */ + +/*% assertion failed handler */ +/* coverity[+kill] */ +void +isc_assertion_failed(const char *file, int line, isc_assertiontype_t type, + const char *cond) { + isc_assertion_failed_cb(file, line, type, cond); + abort(); +} + +/*% Set callback. */ +void +isc_assertion_setcallback(isc_assertioncallback_t cb) { + if (cb == NULL) { + isc_assertion_failed_cb = default_callback; + } else { + isc_assertion_failed_cb = cb; + } +} + +/*% Type to Text */ +const char * +isc_assertion_typetotext(isc_assertiontype_t type) { + const char *result; + + /* + * These strings have purposefully not been internationalized + * because they are considered to essentially be keywords of + * the ISC development environment. + */ + switch (type) { + case isc_assertiontype_require: + result = "REQUIRE"; + break; + case isc_assertiontype_ensure: + result = "ENSURE"; + break; + case isc_assertiontype_insist: + result = "INSIST"; + break; + case isc_assertiontype_invariant: + result = "INVARIANT"; + break; + default: + result = "UNKNOWN"; + } + return (result); +} + +/* + * Private. + */ + +static void +default_callback(const char *file, int line, isc_assertiontype_t type, + const char *cond) { + void *tracebuf[BACKTRACE_MAXFRAME]; + int i, nframes; + const char *logsuffix = "."; + const char *fname; + isc_result_t result; + + result = isc_backtrace_gettrace(tracebuf, BACKTRACE_MAXFRAME, &nframes); + if (result == ISC_R_SUCCESS && nframes > 0) { + logsuffix = ", back trace"; + } + + fprintf(stderr, "%s:%d: %s(%s) failed%s\n", file, line, + isc_assertion_typetotext(type), cond, logsuffix); + + if (result == ISC_R_SUCCESS) { + for (i = 0; i < nframes; i++) { + unsigned long offset; + + fname = NULL; + result = isc_backtrace_getsymbol(tracebuf[i], &fname, + &offset); + if (result == ISC_R_SUCCESS) { + fprintf(stderr, "#%d %p in %s()+0x%lx\n", i, + tracebuf[i], fname, offset); + } else { + fprintf(stderr, "#%d %p in ??\n", i, + tracebuf[i]); + } + } + } + fflush(stderr); +} diff --git a/lib/isc/astack.c b/lib/isc/astack.c new file mode 100644 index 0000000..484ef26 --- /dev/null +++ b/lib/isc/astack.c @@ -0,0 +1,85 @@ +/* + * 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. + */ + +#include <inttypes.h> +#include <string.h> + +#include <isc/astack.h> +#include <isc/atomic.h> +#include <isc/mem.h> +#include <isc/mutex.h> +#include <isc/types.h> +#include <isc/util.h> + +struct isc_astack { + isc_mem_t *mctx; + size_t size; + size_t pos; + isc_mutex_t lock; + uintptr_t nodes[]; +}; + +isc_astack_t * +isc_astack_new(isc_mem_t *mctx, size_t size) { + isc_astack_t *stack = isc_mem_get( + mctx, sizeof(isc_astack_t) + size * sizeof(uintptr_t)); + + *stack = (isc_astack_t){ + .size = size, + }; + isc_mem_attach(mctx, &stack->mctx); + memset(stack->nodes, 0, size * sizeof(uintptr_t)); + isc_mutex_init(&stack->lock); + return (stack); +} + +bool +isc_astack_trypush(isc_astack_t *stack, void *obj) { + if (!isc_mutex_trylock(&stack->lock)) { + if (stack->pos >= stack->size) { + UNLOCK(&stack->lock); + return (false); + } + stack->nodes[stack->pos++] = (uintptr_t)obj; + UNLOCK(&stack->lock); + return (true); + } else { + return (false); + } +} + +void * +isc_astack_pop(isc_astack_t *stack) { + LOCK(&stack->lock); + uintptr_t rv; + if (stack->pos == 0) { + rv = 0; + } else { + rv = stack->nodes[--stack->pos]; + } + UNLOCK(&stack->lock); + return ((void *)rv); +} + +void +isc_astack_destroy(isc_astack_t *stack) { + LOCK(&stack->lock); + REQUIRE(stack->pos == 0); + UNLOCK(&stack->lock); + + isc_mutex_destroy(&stack->lock); + + isc_mem_putanddetach(&stack->mctx, stack, + sizeof(struct isc_astack) + + stack->size * sizeof(uintptr_t)); +} diff --git a/lib/isc/backtrace-emptytbl.c b/lib/isc/backtrace-emptytbl.c new file mode 100644 index 0000000..0c6f61f --- /dev/null +++ b/lib/isc/backtrace-emptytbl.c @@ -0,0 +1,29 @@ +/* + * 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. + */ + +/*! \file */ + +/* + * This file defines an empty (default) symbol table used in backtrace.c + * If the application wants to have a complete symbol table, it should redefine + * isc__backtrace_symtable with the complete table in some way, and link the + * version of the library not including this definition + * (e.g. libisc-nosymbol.a). + */ + +#include <isc/backtrace.h> + +LIBISC_EXTERNAL_DATA const int isc__backtrace_nsymbols = 0; +LIBISC_EXTERNAL_DATA const isc_backtrace_symmap_t isc__backtrace_symtable[] = { + { NULL, "" } +}; diff --git a/lib/isc/backtrace.c b/lib/isc/backtrace.c new file mode 100644 index 0000000..69488de --- /dev/null +++ b/lib/isc/backtrace.c @@ -0,0 +1,304 @@ +/* + * 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. + */ + +/*! \file */ + +#include <stdlib.h> +#include <string.h> +#ifdef HAVE_LIBCTRACE +#include <execinfo.h> +#endif /* ifdef HAVE_LIBCTRACE */ + +#include <isc/backtrace.h> +#include <isc/result.h> +#include <isc/util.h> + +#ifdef USE_BACKTRACE +/* + * Getting a back trace of a running process is tricky and highly platform + * dependent. Our current approach is as follows: + * 1. If the system library supports the "backtrace()" function, use it. + * 2. Otherwise, if the compiler is gcc and the architecture is x86_64 or IA64, + * then use gcc's (hidden) Unwind_Backtrace() function. Note that this + * function doesn't work for C programs on many other architectures. + * 3. Otherwise, if the architecture x86 or x86_64, try to unwind the stack + * frame following frame pointers. This assumes the executable binary + * compiled with frame pointers; this is not always true for x86_64 (rather, + * compiler optimizations often disable frame pointers). The validation + * checks in getnextframeptr() hopefully rejects bogus values stored in + * the RBP register in such a case. If the backtrace function itself crashes + * due to this problem, the whole package should be rebuilt with + * --disable-backtrace. + */ +#ifdef HAVE_LIBCTRACE +#define BACKTRACE_LIBC +#elif defined(HAVE_UNWIND_BACKTRACE) +#define BACKTRACE_GCC +#elif defined(WIN32) +#define BACKTRACE_WIN32 +#elif defined(__x86_64__) || defined(__i386__) +#define BACKTRACE_X86STACK +#else /* ifdef HAVE_LIBCTRACE */ +#define BACKTRACE_DISABLED +#endif /* HAVE_LIBCTRACE */ +#else /* USE_BACKTRACE */ +#define BACKTRACE_DISABLED +#endif /* USE_BACKTRACE */ + +#ifdef BACKTRACE_LIBC +isc_result_t +isc_backtrace_gettrace(void **addrs, int maxaddrs, int *nframes) { + int n; + + /* + * Validate the arguments: intentionally avoid using REQUIRE(). + * See notes in backtrace.h. + */ + if (addrs == NULL || nframes == NULL) { + return (ISC_R_FAILURE); + } + + /* + * backtrace(3) includes this function itself in the address array, + * which should be eliminated from the returned sequence. + */ + n = backtrace(addrs, maxaddrs); + if (n < 2) { + return (ISC_R_NOTFOUND); + } + n--; + memmove(addrs, &addrs[1], sizeof(void *) * n); + *nframes = n; + return (ISC_R_SUCCESS); +} +#elif defined(BACKTRACE_GCC) +extern int +_Unwind_Backtrace(void *fn, void *a); +extern void * +_Unwind_GetIP(void *ctx); + +typedef struct { + void **result; + int max_depth; + int skip_count; + int count; +} trace_arg_t; + +static int +btcallback(void *uc, void *opq) { + trace_arg_t *arg = (trace_arg_t *)opq; + + if (arg->skip_count > 0) { + arg->skip_count--; + } else { + arg->result[arg->count++] = (void *)_Unwind_GetIP(uc); + } + if (arg->count == arg->max_depth) { + return (5); /* _URC_END_OF_STACK */ + } + return (0); /* _URC_NO_REASON */ +} + +isc_result_t +isc_backtrace_gettrace(void **addrs, int maxaddrs, int *nframes) { + trace_arg_t arg; + + /* Argument validation: see above. */ + if (addrs == NULL || nframes == NULL) { + return (ISC_R_FAILURE); + } + + arg.skip_count = 1; + arg.result = addrs; + arg.max_depth = maxaddrs; + arg.count = 0; + _Unwind_Backtrace(btcallback, &arg); + + *nframes = arg.count; + + return (ISC_R_SUCCESS); +} +#elif defined(BACKTRACE_WIN32) +isc_result_t +isc_backtrace_gettrace(void **addrs, int maxaddrs, int *nframes) { + unsigned long ftc = (unsigned long)maxaddrs; + + *nframes = (int)CaptureStackBackTrace(1, ftc, addrs, NULL); + return (ISC_R_SUCCESS); +} +#elif defined(BACKTRACE_X86STACK) +#ifdef __x86_64__ +static unsigned long +getrbp(void) { + unsigned long rbp; + __asm("movq %%rbp, %0\n" : "=r"(rbp)); + return rbp; +} +#endif /* ifdef __x86_64__ */ + +static void ** +getnextframeptr(void **sp) { + void **newsp = (void **)*sp; + + /* + * Perform sanity check for the new frame pointer, derived from + * google glog. This can actually be bogus depending on compiler. + */ + + /* prohibit the stack frames from growing downwards */ + if (newsp <= sp) { + return (NULL); + } + + /* A heuristics to reject "too large" frame: this actually happened. */ + if ((char *)newsp - (char *)sp > 100000) { + return (NULL); + } + + /* + * Not sure if other checks used in glog are needed at this moment. + * For our purposes we don't have to consider non-contiguous frames, + * for example. + */ + + return (newsp); +} + +isc_result_t +isc_backtrace_gettrace(void **addrs, int maxaddrs, int *nframes) { + int i = 0; + void **sp; + + /* Argument validation: see above. */ + if (addrs == NULL || nframes == NULL) { + return (ISC_R_FAILURE); + } + +#ifdef __x86_64__ + sp = (void **)getrbp(); + if (sp == NULL) { + return (ISC_R_NOTFOUND); + } + /* + * sp is the frame ptr of this function itself due to the call to + * getrbp(), so need to unwind one frame for consistency. + */ + sp = getnextframeptr(sp); +#else /* ifdef __x86_64__ */ + /* + * i386: the frame pointer is stored 2 words below the address for the + * first argument. Note that the body of this function cannot be + * inlined since it depends on the address of the function argument. + */ + sp = (void **)&addrs - 2; +#endif /* ifdef __x86_64__ */ + + while (sp != NULL && i < maxaddrs) { + addrs[i++] = *(sp + 1); + sp = getnextframeptr(sp); + } + + *nframes = i; + + return (ISC_R_SUCCESS); +} +#elif defined(BACKTRACE_DISABLED) +isc_result_t +isc_backtrace_gettrace(void **addrs, int maxaddrs, int *nframes) { + /* Argument validation: see above. */ + if (addrs == NULL || nframes == NULL) { + return (ISC_R_FAILURE); + } + + UNUSED(maxaddrs); + + return (ISC_R_NOTIMPLEMENTED); +} +#endif /* ifdef BACKTRACE_LIBC */ + +isc_result_t +isc_backtrace_getsymbolfromindex(int idx, const void **addrp, + const char **symbolp) { + REQUIRE(addrp != NULL && *addrp == NULL); + REQUIRE(symbolp != NULL && *symbolp == NULL); + + if (idx < 0 || idx >= isc__backtrace_nsymbols) { + return (ISC_R_RANGE); + } + + *addrp = isc__backtrace_symtable[idx].addr; + *symbolp = isc__backtrace_symtable[idx].symbol; + return (ISC_R_SUCCESS); +} + +static int +symtbl_compare(const void *addr, const void *entryarg) { + const isc_backtrace_symmap_t *entry = entryarg; + const isc_backtrace_symmap_t *end = + &isc__backtrace_symtable[isc__backtrace_nsymbols - 1]; + + if (isc__backtrace_nsymbols == 1 || entry == end) { + if (addr >= entry->addr) { + /* + * If addr is equal to or larger than that of the last + * entry of the table, we cannot be sure if this is + * within a valid range so we consider it valid. + */ + return (0); + } + return (-1); + } + + /* entry + 1 is a valid entry from now on. */ + if (addr < entry->addr) { + return (-1); + } else if (addr >= (entry + 1)->addr) { + return (1); + } + return (0); +} + +isc_result_t +isc_backtrace_getsymbol(const void *addr, const char **symbolp, + unsigned long *offsetp) { + isc_result_t result = ISC_R_SUCCESS; + isc_backtrace_symmap_t *found; + + /* + * Validate the arguments: intentionally avoid using REQUIRE(). + * See notes in backtrace.h. + */ + if (symbolp == NULL || *symbolp != NULL || offsetp == NULL) { + return (ISC_R_FAILURE); + } + + if (isc__backtrace_nsymbols < 1) { + return (ISC_R_NOTFOUND); + } + + /* + * Search the table for the entry that meets: + * entry.addr <= addr < next_entry.addr. + */ + found = bsearch(addr, isc__backtrace_symtable, isc__backtrace_nsymbols, + sizeof(isc__backtrace_symtable[0]), symtbl_compare); + if (found == NULL) { + result = ISC_R_NOTFOUND; + } else { + *symbolp = found->symbol; + *offsetp = (unsigned long)((const char *)addr - + (char *)found->addr); + } + + return (result); +} diff --git a/lib/isc/base32.c b/lib/isc/base32.c new file mode 100644 index 0000000..90c37c7 --- /dev/null +++ b/lib/isc/base32.c @@ -0,0 +1,443 @@ +/* + * 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. + */ + +/*! \file */ + +#include <stdbool.h> + +#include <isc/base32.h> +#include <isc/buffer.h> +#include <isc/lex.h> +#include <isc/region.h> +#include <isc/string.h> +#include <isc/util.h> + +#define RETERR(x) \ + do { \ + isc_result_t _r = (x); \ + if (_r != ISC_R_SUCCESS) \ + return ((_r)); \ + } while (0) + +/*@{*/ +/*! + * These static functions are also present in lib/dns/rdata.c. I'm not + * sure where they should go. -- bwelling + */ +static isc_result_t +str_totext(const char *source, isc_buffer_t *target); + +static isc_result_t +mem_tobuffer(isc_buffer_t *target, void *base, unsigned int length); + +/*@}*/ + +static const char base32[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=" + "abcdefghijklmnopqrstuvwxyz234567"; +static const char base32hex[] = "0123456789ABCDEFGHIJKLMNOPQRSTUV=" + "0123456789abcdefghijklmnopqrstuv"; + +static isc_result_t +base32_totext(isc_region_t *source, int wordlength, const char *wordbreak, + isc_buffer_t *target, const char base[], char pad) { + char buf[9]; + unsigned int loops = 0; + + if (wordlength >= 0 && wordlength < 8) { + wordlength = 8; + } + + memset(buf, 0, sizeof(buf)); + while (source->length > 0) { + buf[0] = base[((source->base[0] >> 3) & 0x1f)]; /* 5 + */ + if (source->length == 1) { + buf[1] = base[(source->base[0] << 2) & 0x1c]; + buf[2] = buf[3] = buf[4] = pad; + buf[5] = buf[6] = buf[7] = pad; + RETERR(str_totext(buf, target)); + break; + } + buf[1] = base[((source->base[0] << 2) & 0x1c) | /* 3 = 8 */ + ((source->base[1] >> 6) & 0x03)]; /* 2 + */ + buf[2] = base[((source->base[1] >> 1) & 0x1f)]; /* 5 + */ + if (source->length == 2) { + buf[3] = base[(source->base[1] << 4) & 0x10]; + buf[4] = buf[5] = buf[6] = buf[7] = pad; + RETERR(str_totext(buf, target)); + break; + } + buf[3] = base[((source->base[1] << 4) & 0x10) | /* 1 = 8 */ + ((source->base[2] >> 4) & 0x0f)]; /* 4 + */ + if (source->length == 3) { + buf[4] = base[(source->base[2] << 1) & 0x1e]; + buf[5] = buf[6] = buf[7] = pad; + RETERR(str_totext(buf, target)); + break; + } + buf[4] = base[((source->base[2] << 1) & 0x1e) | /* 4 = 8 */ + ((source->base[3] >> 7) & 0x01)]; /* 1 + */ + buf[5] = base[((source->base[3] >> 2) & 0x1f)]; /* 5 + */ + if (source->length == 4) { + buf[6] = base[(source->base[3] << 3) & 0x18]; + buf[7] = pad; + RETERR(str_totext(buf, target)); + break; + } + buf[6] = base[((source->base[3] << 3) & 0x18) | /* 2 = 8 */ + ((source->base[4] >> 5) & 0x07)]; /* 3 + */ + buf[7] = base[source->base[4] & 0x1f]; /* 5 = 8 */ + RETERR(str_totext(buf, target)); + isc_region_consume(source, 5); + + loops++; + if (source->length != 0 && wordlength >= 0 && + (int)((loops + 1) * 8) >= wordlength) + { + loops = 0; + RETERR(str_totext(wordbreak, target)); + } + } + if (source->length > 0) { + isc_region_consume(source, source->length); + } + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_base32_totext(isc_region_t *source, int wordlength, const char *wordbreak, + isc_buffer_t *target) { + return (base32_totext(source, wordlength, wordbreak, target, base32, + '=')); +} + +isc_result_t +isc_base32hex_totext(isc_region_t *source, int wordlength, + const char *wordbreak, isc_buffer_t *target) { + return (base32_totext(source, wordlength, wordbreak, target, base32hex, + '=')); +} + +isc_result_t +isc_base32hexnp_totext(isc_region_t *source, int wordlength, + const char *wordbreak, isc_buffer_t *target) { + return (base32_totext(source, wordlength, wordbreak, target, base32hex, + 0)); +} + +/*% + * State of a base32 decoding process in progress. + */ +typedef struct { + int length; /*%< Desired length of binary data or -1 */ + isc_buffer_t *target; /*%< Buffer for resulting binary data */ + int digits; /*%< Number of buffered base32 digits */ + bool seen_end; /*%< True if "=" end marker seen */ + int val[8]; + const char *base; /*%< Which encoding we are using */ + int seen_32; /*%< Number of significant bytes if non + * zero */ + bool pad; /*%< Expect padding */ +} base32_decode_ctx_t; + +static void +base32_decode_init(base32_decode_ctx_t *ctx, int length, const char base[], + bool pad, isc_buffer_t *target) { + ctx->digits = 0; + ctx->seen_end = false; + ctx->seen_32 = 0; + ctx->length = length; + ctx->target = target; + ctx->base = base; + ctx->pad = pad; +} + +static isc_result_t +base32_decode_char(base32_decode_ctx_t *ctx, int c) { + const char *s; + unsigned int last; + + if (ctx->seen_end) { + return (ISC_R_BADBASE32); + } + if ((s = strchr(ctx->base, c)) == NULL) { + return (ISC_R_BADBASE32); + } + last = (unsigned int)(s - ctx->base); + + /* + * Handle lower case. + */ + if (last > 32) { + last -= 33; + } + + /* + * Check that padding is contiguous. + */ + if (last != 32 && ctx->seen_32 != 0) { + return (ISC_R_BADBASE32); + } + + /* + * If padding is not permitted flag padding as a error. + */ + if (last == 32 && !ctx->pad) { + return (ISC_R_BADBASE32); + } + + /* + * Check that padding starts at the right place and that + * bits that should be zero are. + * Record how many significant bytes in answer (seen_32). + */ + if (last == 32 && ctx->seen_32 == 0) { + switch (ctx->digits) { + case 0: + case 1: + return (ISC_R_BADBASE32); + case 2: + if ((ctx->val[1] & 0x03) != 0) { + return (ISC_R_BADBASE32); + } + ctx->seen_32 = 1; + break; + case 3: + return (ISC_R_BADBASE32); + case 4: + if ((ctx->val[3] & 0x0f) != 0) { + return (ISC_R_BADBASE32); + } + ctx->seen_32 = 2; + break; + case 5: + if ((ctx->val[4] & 0x01) != 0) { + return (ISC_R_BADBASE32); + } + ctx->seen_32 = 3; + break; + case 6: + return (ISC_R_BADBASE32); + case 7: + if ((ctx->val[6] & 0x07) != 0) { + return (ISC_R_BADBASE32); + } + ctx->seen_32 = 4; + break; + } + } + + /* + * Zero fill pad values. + */ + ctx->val[ctx->digits++] = (last == 32) ? 0 : last; + + if (ctx->digits == 8) { + int n = 5; + unsigned char buf[5]; + + if (ctx->seen_32 != 0) { + ctx->seen_end = true; + n = ctx->seen_32; + } + buf[0] = (ctx->val[0] << 3) | (ctx->val[1] >> 2); + buf[1] = (ctx->val[1] << 6) | (ctx->val[2] << 1) | + (ctx->val[3] >> 4); + buf[2] = (ctx->val[3] << 4) | (ctx->val[4] >> 1); + buf[3] = (ctx->val[4] << 7) | (ctx->val[5] << 2) | + (ctx->val[6] >> 3); + buf[4] = (ctx->val[6] << 5) | (ctx->val[7]); + RETERR(mem_tobuffer(ctx->target, buf, n)); + if (ctx->length >= 0) { + if (n > ctx->length) { + return (ISC_R_BADBASE32); + } else { + ctx->length -= n; + } + } + ctx->digits = 0; + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +base32_decode_finish(base32_decode_ctx_t *ctx) { + if (ctx->length > 0) { + return (ISC_R_UNEXPECTEDEND); + } + /* + * Add missing padding if required. + */ + if (!ctx->pad && ctx->digits != 0) { + ctx->pad = true; + do { + RETERR(base32_decode_char(ctx, '=')); + } while (ctx->digits != 0); + } + if (ctx->digits != 0) { + return (ISC_R_BADBASE32); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +base32_tobuffer(isc_lex_t *lexer, const char base[], bool pad, + isc_buffer_t *target, int length) { + unsigned int before, after; + base32_decode_ctx_t ctx; + isc_textregion_t *tr; + isc_token_t token; + bool eol; + + REQUIRE(length >= -2); + + base32_decode_init(&ctx, length, base, pad, target); + + before = isc_buffer_usedlength(target); + while (!ctx.seen_end && (ctx.length != 0)) { + unsigned int i; + + if (length > 0) { + eol = false; + } else { + eol = true; + } + RETERR(isc_lex_getmastertoken(lexer, &token, + isc_tokentype_string, eol)); + if (token.type != isc_tokentype_string) { + break; + } + tr = &token.value.as_textregion; + for (i = 0; i < tr->length; i++) { + RETERR(base32_decode_char(&ctx, tr->base[i])); + } + } + after = isc_buffer_usedlength(target); + if (ctx.length < 0 && !ctx.seen_end) { + isc_lex_ungettoken(lexer, &token); + } + RETERR(base32_decode_finish(&ctx)); + if (length == -2 && before == after) { + return (ISC_R_UNEXPECTEDEND); + } + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_base32_tobuffer(isc_lex_t *lexer, isc_buffer_t *target, int length) { + return (base32_tobuffer(lexer, base32, true, target, length)); +} + +isc_result_t +isc_base32hex_tobuffer(isc_lex_t *lexer, isc_buffer_t *target, int length) { + return (base32_tobuffer(lexer, base32hex, true, target, length)); +} + +isc_result_t +isc_base32hexnp_tobuffer(isc_lex_t *lexer, isc_buffer_t *target, int length) { + return (base32_tobuffer(lexer, base32hex, false, target, length)); +} + +static isc_result_t +base32_decodestring(const char *cstr, const char base[], bool pad, + isc_buffer_t *target) { + base32_decode_ctx_t ctx; + + base32_decode_init(&ctx, -1, base, pad, target); + for (;;) { + int c = *cstr++; + if (c == '\0') { + break; + } + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { + continue; + } + RETERR(base32_decode_char(&ctx, c)); + } + RETERR(base32_decode_finish(&ctx)); + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_base32_decodestring(const char *cstr, isc_buffer_t *target) { + return (base32_decodestring(cstr, base32, true, target)); +} + +isc_result_t +isc_base32hex_decodestring(const char *cstr, isc_buffer_t *target) { + return (base32_decodestring(cstr, base32hex, true, target)); +} + +isc_result_t +isc_base32hexnp_decodestring(const char *cstr, isc_buffer_t *target) { + return (base32_decodestring(cstr, base32hex, false, target)); +} + +static isc_result_t +base32_decoderegion(isc_region_t *source, const char base[], bool pad, + isc_buffer_t *target) { + base32_decode_ctx_t ctx; + + base32_decode_init(&ctx, -1, base, pad, target); + while (source->length != 0) { + int c = *source->base; + RETERR(base32_decode_char(&ctx, c)); + isc_region_consume(source, 1); + } + RETERR(base32_decode_finish(&ctx)); + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_base32_decoderegion(isc_region_t *source, isc_buffer_t *target) { + return (base32_decoderegion(source, base32, true, target)); +} + +isc_result_t +isc_base32hex_decoderegion(isc_region_t *source, isc_buffer_t *target) { + return (base32_decoderegion(source, base32hex, true, target)); +} + +isc_result_t +isc_base32hexnp_decoderegion(isc_region_t *source, isc_buffer_t *target) { + return (base32_decoderegion(source, base32hex, false, target)); +} + +static isc_result_t +str_totext(const char *source, isc_buffer_t *target) { + unsigned int l; + isc_region_t region; + + isc_buffer_availableregion(target, ®ion); + l = strlen(source); + + if (l > region.length) { + return (ISC_R_NOSPACE); + } + + memmove(region.base, source, l); + isc_buffer_add(target, l); + return (ISC_R_SUCCESS); +} + +static isc_result_t +mem_tobuffer(isc_buffer_t *target, void *base, unsigned int length) { + isc_region_t tr; + + isc_buffer_availableregion(target, &tr); + if (length > tr.length) { + return (ISC_R_NOSPACE); + } + memmove(tr.base, base, length); + isc_buffer_add(target, length); + return (ISC_R_SUCCESS); +} diff --git a/lib/isc/base64.c b/lib/isc/base64.c new file mode 100644 index 0000000..958ef4f --- /dev/null +++ b/lib/isc/base64.c @@ -0,0 +1,270 @@ +/* + * 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. + */ + +/*! \file */ + +#include <stdbool.h> + +#include <isc/base64.h> +#include <isc/buffer.h> +#include <isc/lex.h> +#include <isc/string.h> +#include <isc/util.h> + +#define RETERR(x) \ + do { \ + isc_result_t _r = (x); \ + if (_r != ISC_R_SUCCESS) \ + return ((_r)); \ + } while (0) + +/*@{*/ +/*! + * These static functions are also present in lib/dns/rdata.c. I'm not + * sure where they should go. -- bwelling + */ +static isc_result_t +str_totext(const char *source, isc_buffer_t *target); + +static isc_result_t +mem_tobuffer(isc_buffer_t *target, void *base, unsigned int length); + +static const char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw" + "xyz0123456789+/="; +/*@}*/ + +isc_result_t +isc_base64_totext(isc_region_t *source, int wordlength, const char *wordbreak, + isc_buffer_t *target) { + char buf[5]; + unsigned int loops = 0; + + if (wordlength < 4) { + wordlength = 4; + } + + memset(buf, 0, sizeof(buf)); + while (source->length > 2) { + buf[0] = base64[(source->base[0] >> 2) & 0x3f]; + buf[1] = base64[((source->base[0] << 4) & 0x30) | + ((source->base[1] >> 4) & 0x0f)]; + buf[2] = base64[((source->base[1] << 2) & 0x3c) | + ((source->base[2] >> 6) & 0x03)]; + buf[3] = base64[source->base[2] & 0x3f]; + RETERR(str_totext(buf, target)); + isc_region_consume(source, 3); + + loops++; + if (source->length != 0 && (int)((loops + 1) * 4) >= wordlength) + { + loops = 0; + RETERR(str_totext(wordbreak, target)); + } + } + if (source->length == 2) { + buf[0] = base64[(source->base[0] >> 2) & 0x3f]; + buf[1] = base64[((source->base[0] << 4) & 0x30) | + ((source->base[1] >> 4) & 0x0f)]; + buf[2] = base64[((source->base[1] << 2) & 0x3c)]; + buf[3] = '='; + RETERR(str_totext(buf, target)); + isc_region_consume(source, 2); + } else if (source->length == 1) { + buf[0] = base64[(source->base[0] >> 2) & 0x3f]; + buf[1] = base64[((source->base[0] << 4) & 0x30)]; + buf[2] = buf[3] = '='; + RETERR(str_totext(buf, target)); + isc_region_consume(source, 1); + } + return (ISC_R_SUCCESS); +} + +/*% + * State of a base64 decoding process in progress. + */ +typedef struct { + int length; /*%< Desired length of binary data or -1 */ + isc_buffer_t *target; /*%< Buffer for resulting binary data */ + int digits; /*%< Number of buffered base64 digits */ + bool seen_end; /*%< True if "=" end marker seen */ + int val[4]; +} base64_decode_ctx_t; + +static void +base64_decode_init(base64_decode_ctx_t *ctx, int length, isc_buffer_t *target) { + ctx->digits = 0; + ctx->seen_end = false; + ctx->length = length; + ctx->target = target; +} + +static isc_result_t +base64_decode_char(base64_decode_ctx_t *ctx, int c) { + const char *s; + + if (ctx->seen_end) { + return (ISC_R_BADBASE64); + } + if ((s = strchr(base64, c)) == NULL) { + return (ISC_R_BADBASE64); + } + ctx->val[ctx->digits++] = (int)(s - base64); + if (ctx->digits == 4) { + int n; + unsigned char buf[3]; + if (ctx->val[0] == 64 || ctx->val[1] == 64) { + return (ISC_R_BADBASE64); + } + if (ctx->val[2] == 64 && ctx->val[3] != 64) { + return (ISC_R_BADBASE64); + } + /* + * Check that bits that should be zero are. + */ + if (ctx->val[2] == 64 && (ctx->val[1] & 0xf) != 0) { + return (ISC_R_BADBASE64); + } + /* + * We don't need to test for ctx->val[2] != 64 as + * the bottom two bits of 64 are zero. + */ + if (ctx->val[3] == 64 && (ctx->val[2] & 0x3) != 0) { + return (ISC_R_BADBASE64); + } + n = (ctx->val[2] == 64) ? 1 : (ctx->val[3] == 64) ? 2 : 3; + if (n != 3) { + ctx->seen_end = true; + if (ctx->val[2] == 64) { + ctx->val[2] = 0; + } + if (ctx->val[3] == 64) { + ctx->val[3] = 0; + } + } + buf[0] = (ctx->val[0] << 2) | (ctx->val[1] >> 4); + buf[1] = (ctx->val[1] << 4) | (ctx->val[2] >> 2); + buf[2] = (ctx->val[2] << 6) | (ctx->val[3]); + RETERR(mem_tobuffer(ctx->target, buf, n)); + if (ctx->length >= 0) { + if (n > ctx->length) { + return (ISC_R_BADBASE64); + } else { + ctx->length -= n; + } + } + ctx->digits = 0; + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +base64_decode_finish(base64_decode_ctx_t *ctx) { + if (ctx->length > 0) { + return (ISC_R_UNEXPECTEDEND); + } + if (ctx->digits != 0) { + return (ISC_R_BADBASE64); + } + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_base64_tobuffer(isc_lex_t *lexer, isc_buffer_t *target, int length) { + unsigned int before, after; + base64_decode_ctx_t ctx; + isc_textregion_t *tr; + isc_token_t token; + bool eol; + + REQUIRE(length >= -2); + + base64_decode_init(&ctx, length, target); + + before = isc_buffer_usedlength(target); + while (!ctx.seen_end && (ctx.length != 0)) { + unsigned int i; + + if (length > 0) { + eol = false; + } else { + eol = true; + } + RETERR(isc_lex_getmastertoken(lexer, &token, + isc_tokentype_string, eol)); + if (token.type != isc_tokentype_string) { + break; + } + tr = &token.value.as_textregion; + for (i = 0; i < tr->length; i++) { + RETERR(base64_decode_char(&ctx, tr->base[i])); + } + } + after = isc_buffer_usedlength(target); + if (ctx.length < 0 && !ctx.seen_end) { + isc_lex_ungettoken(lexer, &token); + } + RETERR(base64_decode_finish(&ctx)); + if (length == -2 && before == after) { + return (ISC_R_UNEXPECTEDEND); + } + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_base64_decodestring(const char *cstr, isc_buffer_t *target) { + base64_decode_ctx_t ctx; + + base64_decode_init(&ctx, -1, target); + for (;;) { + int c = *cstr++; + if (c == '\0') { + break; + } + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { + continue; + } + RETERR(base64_decode_char(&ctx, c)); + } + RETERR(base64_decode_finish(&ctx)); + return (ISC_R_SUCCESS); +} + +static isc_result_t +str_totext(const char *source, isc_buffer_t *target) { + unsigned int l; + isc_region_t region; + + isc_buffer_availableregion(target, ®ion); + l = strlen(source); + + if (l > region.length) { + return (ISC_R_NOSPACE); + } + + memmove(region.base, source, l); + isc_buffer_add(target, l); + return (ISC_R_SUCCESS); +} + +static isc_result_t +mem_tobuffer(isc_buffer_t *target, void *base, unsigned int length) { + isc_region_t tr; + + isc_buffer_availableregion(target, &tr); + if (length > tr.length) { + return (ISC_R_NOSPACE); + } + memmove(tr.base, base, length); + isc_buffer_add(target, length); + return (ISC_R_SUCCESS); +} diff --git a/lib/isc/bind9.c b/lib/isc/bind9.c new file mode 100644 index 0000000..2b6f474 --- /dev/null +++ b/lib/isc/bind9.c @@ -0,0 +1,27 @@ +/* + * 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. + */ + +/*! \file */ + +#include <stdbool.h> + +#include <isc/bind9.h> + +/* + * This determines whether we are using the libisc/libdns libraries + * in BIND9 or in some other application. It is initialized to true + * and remains unchanged for BIND9 and related tools; export library + * clients will run isc_lib_register(), which sets it to false, + * overriding certain BIND9 behaviors. + */ +LIBISC_EXTERNAL_DATA bool isc_bind9 = true; diff --git a/lib/isc/buffer.c b/lib/isc/buffer.c new file mode 100644 index 0000000..b284840 --- /dev/null +++ b/lib/isc/buffer.c @@ -0,0 +1,542 @@ +/* + * 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. + */ + +/*! \file */ + +#include <inttypes.h> +#include <stdarg.h> +#include <stdbool.h> + +#include <isc/buffer.h> +#include <isc/mem.h> +#include <isc/print.h> +#include <isc/region.h> +#include <isc/string.h> +#include <isc/util.h> + +void +isc__buffer_init(isc_buffer_t *b, void *base, unsigned int length) { + /* + * Make 'b' refer to the 'length'-byte region starting at 'base'. + * XXXDCL see the comment in buffer.h about base being const. + */ + ISC__BUFFER_INIT(b, base, length); +} + +void +isc__buffer_initnull(isc_buffer_t *b) { + /* + * Initialize a new buffer which has no backing store. This can + * later be grown as needed and swapped in place. + */ + ISC__BUFFER_INIT(b, NULL, 0); +} + +void +isc_buffer_reinit(isc_buffer_t *b, void *base, unsigned int length) { + /* + * Re-initialize the buffer enough to reconfigure the base of the + * buffer. We will swap in the new buffer, after copying any + * data we contain into the new buffer and adjusting all of our + * internal pointers. + * + * The buffer must not be smaller than the length of the original + * buffer. + */ + REQUIRE(b->length <= length); + REQUIRE(base != NULL); + REQUIRE(!b->autore); + + if (b->length > 0U) { + (void)memmove(base, b->base, b->length); + } + + b->base = base; + b->length = length; +} + +void +isc__buffer_invalidate(isc_buffer_t *b) { + /* + * Make 'b' an invalid buffer. + */ + ISC__BUFFER_INVALIDATE(b); +} + +void +isc_buffer_setautorealloc(isc_buffer_t *b, bool enable) { + REQUIRE(ISC_BUFFER_VALID(b)); + REQUIRE(b->mctx != NULL); + b->autore = enable; +} + +void +isc__buffer_region(isc_buffer_t *b, isc_region_t *r) { + /* + * Make 'r' refer to the region of 'b'. + */ + ISC__BUFFER_REGION(b, r); +} + +void +isc__buffer_usedregion(const isc_buffer_t *b, isc_region_t *r) { + /* + * Make 'r' refer to the used region of 'b'. + */ + ISC__BUFFER_USEDREGION(b, r); +} + +void +isc__buffer_availableregion(isc_buffer_t *b, isc_region_t *r) { + /* + * Make 'r' refer to the available region of 'b'. + */ + ISC__BUFFER_AVAILABLEREGION(b, r); +} + +void +isc__buffer_add(isc_buffer_t *b, unsigned int n) { + /* + * Increase the 'used' region of 'b' by 'n' bytes. + */ + ISC__BUFFER_ADD(b, n); +} + +void +isc__buffer_subtract(isc_buffer_t *b, unsigned int n) { + /* + * Decrease the 'used' region of 'b' by 'n' bytes. + */ + ISC__BUFFER_SUBTRACT(b, n); +} + +void +isc__buffer_clear(isc_buffer_t *b) { + /* + * Make the used region empty. + */ + ISC__BUFFER_CLEAR(b); +} + +void +isc__buffer_consumedregion(isc_buffer_t *b, isc_region_t *r) { + /* + * Make 'r' refer to the consumed region of 'b'. + */ + ISC__BUFFER_CONSUMEDREGION(b, r); +} + +void +isc__buffer_remainingregion(isc_buffer_t *b, isc_region_t *r) { + /* + * Make 'r' refer to the remaining region of 'b'. + */ + ISC__BUFFER_REMAININGREGION(b, r); +} + +void +isc__buffer_activeregion(isc_buffer_t *b, isc_region_t *r) { + /* + * Make 'r' refer to the active region of 'b'. + */ + ISC__BUFFER_ACTIVEREGION(b, r); +} + +void +isc__buffer_setactive(isc_buffer_t *b, unsigned int n) { + /* + * Sets the end of the active region 'n' bytes after current. + */ + ISC__BUFFER_SETACTIVE(b, n); +} + +void +isc__buffer_first(isc_buffer_t *b) { + /* + * Make the consumed region empty. + */ + ISC__BUFFER_FIRST(b); +} + +void +isc__buffer_forward(isc_buffer_t *b, unsigned int n) { + /* + * Increase the 'consumed' region of 'b' by 'n' bytes. + */ + ISC__BUFFER_FORWARD(b, n); +} + +void +isc__buffer_back(isc_buffer_t *b, unsigned int n) { + /* + * Decrease the 'consumed' region of 'b' by 'n' bytes. + */ + ISC__BUFFER_BACK(b, n); +} + +void +isc_buffer_compact(isc_buffer_t *b) { + unsigned int length; + void *src; + + /* + * Compact the used region by moving the remaining region so it occurs + * at the start of the buffer. The used region is shrunk by the size + * of the consumed region, and the consumed region is then made empty. + */ + + REQUIRE(ISC_BUFFER_VALID(b)); + + src = isc_buffer_current(b); + length = isc_buffer_remaininglength(b); + if (length > 0U) { + (void)memmove(b->base, src, (size_t)length); + } + + if (b->active > b->current) { + b->active -= b->current; + } else { + b->active = 0; + } + b->current = 0; + b->used = length; +} + +uint8_t +isc_buffer_getuint8(isc_buffer_t *b) { + unsigned char *cp; + uint8_t result; + + /* + * Read an unsigned 8-bit integer from 'b' and return it. + */ + + REQUIRE(ISC_BUFFER_VALID(b)); + REQUIRE(b->used - b->current >= 1); + + cp = isc_buffer_current(b); + b->current += 1; + result = ((uint8_t)(cp[0])); + + return (result); +} + +void +isc__buffer_putuint8(isc_buffer_t *b, uint8_t val) { + ISC__BUFFER_PUTUINT8(b, val); +} + +uint16_t +isc_buffer_getuint16(isc_buffer_t *b) { + unsigned char *cp; + uint16_t result; + + /* + * Read an unsigned 16-bit integer in network byte order from 'b', + * convert it to host byte order, and return it. + */ + + REQUIRE(ISC_BUFFER_VALID(b)); + REQUIRE(b->used - b->current >= 2); + + cp = isc_buffer_current(b); + b->current += 2; + result = ((unsigned int)(cp[0])) << 8; + result |= ((unsigned int)(cp[1])); + + return (result); +} + +void +isc__buffer_putuint16(isc_buffer_t *b, uint16_t val) { + ISC__BUFFER_PUTUINT16(b, val); +} + +void +isc__buffer_putuint24(isc_buffer_t *b, uint32_t val) { + ISC__BUFFER_PUTUINT24(b, val); +} + +uint32_t +isc_buffer_getuint32(isc_buffer_t *b) { + unsigned char *cp; + uint32_t result; + + /* + * Read an unsigned 32-bit integer in network byte order from 'b', + * convert it to host byte order, and return it. + */ + + REQUIRE(ISC_BUFFER_VALID(b)); + REQUIRE(b->used - b->current >= 4); + + cp = isc_buffer_current(b); + b->current += 4; + result = ((unsigned int)(cp[0])) << 24; + result |= ((unsigned int)(cp[1])) << 16; + result |= ((unsigned int)(cp[2])) << 8; + result |= ((unsigned int)(cp[3])); + + return (result); +} + +void +isc__buffer_putuint32(isc_buffer_t *b, uint32_t val) { + ISC__BUFFER_PUTUINT32(b, val); +} + +uint64_t +isc_buffer_getuint48(isc_buffer_t *b) { + unsigned char *cp; + uint64_t result; + + /* + * Read an unsigned 48-bit integer in network byte order from 'b', + * convert it to host byte order, and return it. + */ + + REQUIRE(ISC_BUFFER_VALID(b)); + REQUIRE(b->used - b->current >= 6); + + cp = isc_buffer_current(b); + b->current += 6; + result = ((int64_t)(cp[0])) << 40; + result |= ((int64_t)(cp[1])) << 32; + result |= ((int64_t)(cp[2])) << 24; + result |= ((int64_t)(cp[3])) << 16; + result |= ((int64_t)(cp[4])) << 8; + result |= ((int64_t)(cp[5])); + + return (result); +} + +void +isc__buffer_putuint48(isc_buffer_t *b, uint64_t val) { + isc_result_t result; + uint16_t valhi; + uint32_t vallo; + + REQUIRE(ISC_BUFFER_VALID(b)); + if (ISC_UNLIKELY(b->autore)) { + result = isc_buffer_reserve(&b, 6); + REQUIRE(result == ISC_R_SUCCESS); + } + REQUIRE(isc_buffer_availablelength(b) >= 6); + + valhi = (uint16_t)(val >> 32); + vallo = (uint32_t)(val & 0xFFFFFFFF); + ISC__BUFFER_PUTUINT16(b, valhi); + ISC__BUFFER_PUTUINT32(b, vallo); +} + +void +isc__buffer_putmem(isc_buffer_t *b, const unsigned char *base, + unsigned int length) { + ISC__BUFFER_PUTMEM(b, base, length); +} + +void +isc__buffer_putstr(isc_buffer_t *b, const char *source) { + ISC__BUFFER_PUTSTR(b, source); +} + +void +isc_buffer_putdecint(isc_buffer_t *b, int64_t v) { + unsigned int l = 0; + unsigned char *cp; + char buf[21]; + isc_result_t result; + + REQUIRE(ISC_BUFFER_VALID(b)); + + /* xxxwpk do it more low-level way ? */ + l = snprintf(buf, 21, "%" PRId64, v); + RUNTIME_CHECK(l <= 21); + if (ISC_UNLIKELY(b->autore)) { + result = isc_buffer_reserve(&b, l); + REQUIRE(result == ISC_R_SUCCESS); + } + REQUIRE(isc_buffer_availablelength(b) >= l); + + cp = isc_buffer_used(b); + memmove(cp, buf, l); + b->used += l; +} + +isc_result_t +isc_buffer_dup(isc_mem_t *mctx, isc_buffer_t **dstp, const isc_buffer_t *src) { + isc_buffer_t *dst = NULL; + isc_region_t region; + isc_result_t result; + + REQUIRE(dstp != NULL && *dstp == NULL); + REQUIRE(ISC_BUFFER_VALID(src)); + + isc_buffer_usedregion(src, ®ion); + + isc_buffer_allocate(mctx, &dst, region.length); + + result = isc_buffer_copyregion(dst, ®ion); + RUNTIME_CHECK(result == ISC_R_SUCCESS); /* NOSPACE is impossible */ + *dstp = dst; + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_buffer_copyregion(isc_buffer_t *b, const isc_region_t *r) { + isc_result_t result; + + REQUIRE(ISC_BUFFER_VALID(b)); + REQUIRE(r != NULL); + + if (ISC_UNLIKELY(b->autore)) { + result = isc_buffer_reserve(&b, r->length); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + + if (r->length > isc_buffer_availablelength(b)) { + return (ISC_R_NOSPACE); + } + + if (r->length > 0U) { + memmove(isc_buffer_used(b), r->base, r->length); + b->used += r->length; + } + + return (ISC_R_SUCCESS); +} + +void +isc_buffer_allocate(isc_mem_t *mctx, isc_buffer_t **dynbuffer, + unsigned int length) { + REQUIRE(dynbuffer != NULL && *dynbuffer == NULL); + + isc_buffer_t *dbuf = isc_mem_get(mctx, sizeof(isc_buffer_t)); + unsigned char *bdata = isc_mem_get(mctx, length); + + isc_buffer_init(dbuf, bdata, length); + + ENSURE(ISC_BUFFER_VALID(dbuf)); + + dbuf->mctx = mctx; + + *dynbuffer = dbuf; +} + +isc_result_t +isc_buffer_reserve(isc_buffer_t **dynbuffer, unsigned int size) { + unsigned char *bdata; + uint64_t len; + + REQUIRE(dynbuffer != NULL); + REQUIRE(ISC_BUFFER_VALID(*dynbuffer)); + + len = (*dynbuffer)->length; + if ((len - (*dynbuffer)->used) >= size) { + return (ISC_R_SUCCESS); + } + + if ((*dynbuffer)->mctx == NULL) { + return (ISC_R_NOSPACE); + } + + /* Round to nearest buffer size increment */ + len = size + (*dynbuffer)->used; + len = (len + ISC_BUFFER_INCR - 1 - ((len - 1) % ISC_BUFFER_INCR)); + + /* Cap at UINT_MAX */ + if (len > UINT_MAX) { + len = UINT_MAX; + } + + if ((len - (*dynbuffer)->used) < size) { + return (ISC_R_NOMEMORY); + } + + /* + * XXXMUKS: This is far more expensive than plain realloc() as + * it doesn't remap pages, but does ordinary copy. So is + * isc_mem_reallocate(), which has additional issues. + */ + bdata = isc_mem_get((*dynbuffer)->mctx, (unsigned int)len); + + memmove(bdata, (*dynbuffer)->base, (*dynbuffer)->length); + isc_mem_put((*dynbuffer)->mctx, (*dynbuffer)->base, + (*dynbuffer)->length); + + (*dynbuffer)->base = bdata; + (*dynbuffer)->length = (unsigned int)len; + + return (ISC_R_SUCCESS); +} + +void +isc_buffer_free(isc_buffer_t **dynbuffer) { + isc_buffer_t *dbuf; + isc_mem_t *mctx; + + REQUIRE(dynbuffer != NULL); + REQUIRE(ISC_BUFFER_VALID(*dynbuffer)); + REQUIRE((*dynbuffer)->mctx != NULL); + + dbuf = *dynbuffer; + *dynbuffer = NULL; /* destroy external reference */ + mctx = dbuf->mctx; + dbuf->mctx = NULL; + + isc_mem_put(mctx, dbuf->base, dbuf->length); + isc_buffer_invalidate(dbuf); + isc_mem_put(mctx, dbuf, sizeof(isc_buffer_t)); +} + +isc_result_t +isc_buffer_printf(isc_buffer_t *b, const char *format, ...) { + va_list ap; + int n; + isc_result_t result; + + REQUIRE(ISC_BUFFER_VALID(b)); + + va_start(ap, format); + n = vsnprintf(NULL, 0, format, ap); + va_end(ap); + + if (n < 0) { + return (ISC_R_FAILURE); + } + + if (ISC_UNLIKELY(b->autore)) { + result = isc_buffer_reserve(&b, n + 1); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + + if (isc_buffer_availablelength(b) < (unsigned int)n + 1) { + return (ISC_R_NOSPACE); + } + + va_start(ap, format); + n = vsnprintf(isc_buffer_used(b), n + 1, format, ap); + va_end(ap); + + if (n < 0) { + return (ISC_R_FAILURE); + } + + b->used += n; + + return (ISC_R_SUCCESS); +} diff --git a/lib/isc/bufferlist.c b/lib/isc/bufferlist.c new file mode 100644 index 0000000..e5f2583 --- /dev/null +++ b/lib/isc/bufferlist.c @@ -0,0 +1,56 @@ +/* + * 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. + */ + +/*! \file */ + +#include <stddef.h> + +#include <isc/buffer.h> +#include <isc/bufferlist.h> +#include <isc/util.h> + +unsigned int +isc_bufferlist_usedcount(isc_bufferlist_t *bl) { + isc_buffer_t *buffer; + unsigned int length; + + REQUIRE(bl != NULL); + + length = 0; + buffer = ISC_LIST_HEAD(*bl); + while (buffer != NULL) { + REQUIRE(ISC_BUFFER_VALID(buffer)); + length += isc_buffer_usedlength(buffer); + buffer = ISC_LIST_NEXT(buffer, link); + } + + return (length); +} + +unsigned int +isc_bufferlist_availablecount(isc_bufferlist_t *bl) { + isc_buffer_t *buffer; + unsigned int length; + + REQUIRE(bl != NULL); + + length = 0; + buffer = ISC_LIST_HEAD(*bl); + while (buffer != NULL) { + REQUIRE(ISC_BUFFER_VALID(buffer)); + length += isc_buffer_availablelength(buffer); + buffer = ISC_LIST_NEXT(buffer, link); + } + + return (length); +} diff --git a/lib/isc/commandline.c b/lib/isc/commandline.c new file mode 100644 index 0000000..dfe7023 --- /dev/null +++ b/lib/isc/commandline.c @@ -0,0 +1,265 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 AND BSD-3-Clause + + * 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/. + */ + +/* + * Copyright (C) 1987, 1993, 1994 The Regents of the University of California. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file + * This file was adapted from the NetBSD project's source tree, RCS ID: + * NetBSD: getopt.c,v 1.15 1999/09/20 04:39:37 lukem Exp + * + * The primary change has been to rename items to the ISC namespace + * and format in the ISC coding style. + */ + +#include <stdbool.h> +#include <stdio.h> + +#include <isc/commandline.h> +#include <isc/mem.h> +#include <isc/print.h> +#include <isc/string.h> +#include <isc/util.h> + +/*% Index into parent argv vector. */ +LIBISC_EXTERNAL_DATA int isc_commandline_index = 1; +/*% Character checked for validity. */ +LIBISC_EXTERNAL_DATA int isc_commandline_option; +/*% Argument associated with option. */ +LIBISC_EXTERNAL_DATA char *isc_commandline_argument; +/*% For printing error messages. */ +LIBISC_EXTERNAL_DATA char *isc_commandline_progname; +/*% Print error messages. */ +LIBISC_EXTERNAL_DATA bool isc_commandline_errprint = true; +/*% Reset processing. */ +LIBISC_EXTERNAL_DATA bool isc_commandline_reset = true; + +static char endopt = '\0'; + +#define BADOPT '?' +#define BADARG ':' +#define ENDOPT &endopt + +/*! + * getopt -- + * Parse argc/argv argument vector. + */ +int +isc_commandline_parse(int argc, char *const *argv, const char *options) { + static char *place = ENDOPT; + const char *option; /* Index into *options of option. */ + + REQUIRE(argc >= 0 && argv != NULL && options != NULL); + + /* + * Update scanning pointer, either because a reset was requested or + * the previous argv was finished. + */ + if (isc_commandline_reset || *place == '\0') { + if (isc_commandline_reset) { + isc_commandline_index = 1; + isc_commandline_reset = false; + } + + if (isc_commandline_progname == NULL) { + isc_commandline_progname = argv[0]; + } + + if (isc_commandline_index >= argc || + *(place = argv[isc_commandline_index]) != '-') + { + /* + * Index out of range or points to non-option. + */ + place = ENDOPT; + return (-1); + } + + if (place[1] != '\0' && *++place == '-' && place[1] == '\0') { + /* + * Found '--' to signal end of options. Advance + * index to next argv, the first non-option. + */ + isc_commandline_index++; + place = ENDOPT; + return (-1); + } + } + + isc_commandline_option = *place++; + option = strchr(options, isc_commandline_option); + + /* + * Ensure valid option has been passed as specified by options string. + * '-:' is never a valid command line option because it could not + * distinguish ':' from the argument specifier in the options string. + */ + if (isc_commandline_option == ':' || option == NULL) { + if (*place == '\0') { + isc_commandline_index++; + } + + if (isc_commandline_errprint && *options != ':') { + fprintf(stderr, "%s: illegal option -- %c\n", + isc_commandline_progname, + isc_commandline_option); + } + + return (BADOPT); + } + + if (*++option != ':') { + /* + * Option does not take an argument. + */ + isc_commandline_argument = NULL; + + /* + * Skip to next argv if at the end of the current argv. + */ + if (*place == '\0') { + ++isc_commandline_index; + } + } else { + /* + * Option needs an argument. + */ + if (*place != '\0') { + /* + * Option is in this argv, -D1 style. + */ + isc_commandline_argument = place; + } else if (argc > ++isc_commandline_index) { + /* + * Option is next argv, -D 1 style. + */ + isc_commandline_argument = argv[isc_commandline_index]; + } else { + /* + * Argument needed, but no more argv. + */ + place = ENDOPT; + + /* + * Silent failure with "missing argument" return + * when ':' starts options string, per historical spec. + */ + if (*options == ':') { + return (BADARG); + } + + if (isc_commandline_errprint) { + fprintf(stderr, + "%s: option requires an argument -- " + "%c\n", + isc_commandline_progname, + isc_commandline_option); + } + + return (BADOPT); + } + + place = ENDOPT; + + /* + * Point to argv that follows argument. + */ + isc_commandline_index++; + } + + return (isc_commandline_option); +} + +isc_result_t +isc_commandline_strtoargv(isc_mem_t *mctx, char *s, unsigned int *argcp, + char ***argvp, unsigned int n) { + isc_result_t result; + +restart: + /* Discard leading whitespace. */ + while (*s == ' ' || *s == '\t') { + s++; + } + + if (*s == '\0') { + /* We have reached the end of the string. */ + *argcp = n; + *argvp = isc_mem_get(mctx, n * sizeof(char *)); + } else { + char *p = s; + while (*p != ' ' && *p != '\t' && *p != '\0' && *p != '{') { + if (*p == '\n') { + *p = ' '; + goto restart; + } + p++; + } + + /* do "grouping", items between { and } are one arg */ + if (*p == '{') { + char *t = p; + /* + * shift all characters to left by 1 to get rid of '{' + */ + while (*t != '\0') { + t++; + *(t - 1) = *t; + } + while (*p != '\0' && *p != '}') { + p++; + } + /* get rid of '}' character */ + if (*p == '}') { + *p = '\0'; + p++; + } + /* normal case, no "grouping" */ + } else if (*p != '\0') { + *p++ = '\0'; + } + + result = isc_commandline_strtoargv(mctx, p, argcp, argvp, + n + 1); + if (result != ISC_R_SUCCESS) { + return (result); + } + (*argvp)[n] = s; + } + + return (ISC_R_SUCCESS); +} diff --git a/lib/isc/counter.c b/lib/isc/counter.c new file mode 100644 index 0000000..0c0ccd6 --- /dev/null +++ b/lib/isc/counter.c @@ -0,0 +1,112 @@ +/* + * 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. + */ + +/*! \file */ + +#include <stdbool.h> +#include <stddef.h> + +#include <isc/atomic.h> +#include <isc/counter.h> +#include <isc/magic.h> +#include <isc/mem.h> +#include <isc/refcount.h> +#include <isc/util.h> + +#define COUNTER_MAGIC ISC_MAGIC('C', 'n', 't', 'r') +#define VALID_COUNTER(r) ISC_MAGIC_VALID(r, COUNTER_MAGIC) + +struct isc_counter { + unsigned int magic; + isc_mem_t *mctx; + isc_refcount_t references; + atomic_uint_fast32_t limit; + atomic_uint_fast32_t used; +}; + +isc_result_t +isc_counter_create(isc_mem_t *mctx, int limit, isc_counter_t **counterp) { + isc_counter_t *counter; + + REQUIRE(counterp != NULL && *counterp == NULL); + + counter = isc_mem_get(mctx, sizeof(*counter)); + + counter->mctx = NULL; + isc_mem_attach(mctx, &counter->mctx); + + isc_refcount_init(&counter->references, 1); + atomic_init(&counter->limit, limit); + atomic_init(&counter->used, 0); + + counter->magic = COUNTER_MAGIC; + *counterp = counter; + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_counter_increment(isc_counter_t *counter) { + uint32_t used = atomic_fetch_add_relaxed(&counter->used, 1) + 1; + uint32_t limit = atomic_load_acquire(&counter->limit); + + if (limit != 0 && used >= limit) { + return (ISC_R_QUOTA); + } + + return (ISC_R_SUCCESS); +} + +unsigned int +isc_counter_used(isc_counter_t *counter) { + REQUIRE(VALID_COUNTER(counter)); + + return (atomic_load_acquire(&counter->used)); +} + +void +isc_counter_setlimit(isc_counter_t *counter, int limit) { + REQUIRE(VALID_COUNTER(counter)); + + atomic_store(&counter->limit, limit); +} + +void +isc_counter_attach(isc_counter_t *source, isc_counter_t **targetp) { + REQUIRE(VALID_COUNTER(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + isc_refcount_increment(&source->references); + + *targetp = source; +} + +static void +destroy(isc_counter_t *counter) { + isc_refcount_destroy(&counter->references); + counter->magic = 0; + isc_mem_putanddetach(&counter->mctx, counter, sizeof(*counter)); +} + +void +isc_counter_detach(isc_counter_t **counterp) { + isc_counter_t *counter; + + REQUIRE(counterp != NULL && *counterp != NULL); + counter = *counterp; + *counterp = NULL; + REQUIRE(VALID_COUNTER(counter)); + + if (isc_refcount_decrement(&counter->references) == 1) { + destroy(counter); + } +} diff --git a/lib/isc/crc64.c b/lib/isc/crc64.c new file mode 100644 index 0000000..4b6c213 --- /dev/null +++ b/lib/isc/crc64.c @@ -0,0 +1,140 @@ +/* + * 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. + */ + +#include <inttypes.h> + +#include <isc/assertions.h> +#include <isc/crc64.h> +#include <isc/string.h> +#include <isc/types.h> +#include <isc/util.h> + +/*%< + * ECMA-182 CRC64 polynomial. + */ +static const uint64_t crc64_table[256] = { + 0x0000000000000000ULL, 0x42F0E1EBA9EA3693ULL, 0x85E1C3D753D46D26ULL, + 0xC711223CFA3E5BB5ULL, 0x493366450E42ECDFULL, 0x0BC387AEA7A8DA4CULL, + 0xCCD2A5925D9681F9ULL, 0x8E224479F47CB76AULL, 0x9266CC8A1C85D9BEULL, + 0xD0962D61B56FEF2DULL, 0x17870F5D4F51B498ULL, 0x5577EEB6E6BB820BULL, + 0xDB55AACF12C73561ULL, 0x99A54B24BB2D03F2ULL, 0x5EB4691841135847ULL, + 0x1C4488F3E8F96ED4ULL, 0x663D78FF90E185EFULL, 0x24CD9914390BB37CULL, + 0xE3DCBB28C335E8C9ULL, 0xA12C5AC36ADFDE5AULL, 0x2F0E1EBA9EA36930ULL, + 0x6DFEFF5137495FA3ULL, 0xAAEFDD6DCD770416ULL, 0xE81F3C86649D3285ULL, + 0xF45BB4758C645C51ULL, 0xB6AB559E258E6AC2ULL, 0x71BA77A2DFB03177ULL, + 0x334A9649765A07E4ULL, 0xBD68D2308226B08EULL, 0xFF9833DB2BCC861DULL, + 0x388911E7D1F2DDA8ULL, 0x7A79F00C7818EB3BULL, 0xCC7AF1FF21C30BDEULL, + 0x8E8A101488293D4DULL, 0x499B3228721766F8ULL, 0x0B6BD3C3DBFD506BULL, + 0x854997BA2F81E701ULL, 0xC7B97651866BD192ULL, 0x00A8546D7C558A27ULL, + 0x4258B586D5BFBCB4ULL, 0x5E1C3D753D46D260ULL, 0x1CECDC9E94ACE4F3ULL, + 0xDBFDFEA26E92BF46ULL, 0x990D1F49C77889D5ULL, 0x172F5B3033043EBFULL, + 0x55DFBADB9AEE082CULL, 0x92CE98E760D05399ULL, 0xD03E790CC93A650AULL, + 0xAA478900B1228E31ULL, 0xE8B768EB18C8B8A2ULL, 0x2FA64AD7E2F6E317ULL, + 0x6D56AB3C4B1CD584ULL, 0xE374EF45BF6062EEULL, 0xA1840EAE168A547DULL, + 0x66952C92ECB40FC8ULL, 0x2465CD79455E395BULL, 0x3821458AADA7578FULL, + 0x7AD1A461044D611CULL, 0xBDC0865DFE733AA9ULL, 0xFF3067B657990C3AULL, + 0x711223CFA3E5BB50ULL, 0x33E2C2240A0F8DC3ULL, 0xF4F3E018F031D676ULL, + 0xB60301F359DBE0E5ULL, 0xDA050215EA6C212FULL, 0x98F5E3FE438617BCULL, + 0x5FE4C1C2B9B84C09ULL, 0x1D14202910527A9AULL, 0x93366450E42ECDF0ULL, + 0xD1C685BB4DC4FB63ULL, 0x16D7A787B7FAA0D6ULL, 0x5427466C1E109645ULL, + 0x4863CE9FF6E9F891ULL, 0x0A932F745F03CE02ULL, 0xCD820D48A53D95B7ULL, + 0x8F72ECA30CD7A324ULL, 0x0150A8DAF8AB144EULL, 0x43A04931514122DDULL, + 0x84B16B0DAB7F7968ULL, 0xC6418AE602954FFBULL, 0xBC387AEA7A8DA4C0ULL, + 0xFEC89B01D3679253ULL, 0x39D9B93D2959C9E6ULL, 0x7B2958D680B3FF75ULL, + 0xF50B1CAF74CF481FULL, 0xB7FBFD44DD257E8CULL, 0x70EADF78271B2539ULL, + 0x321A3E938EF113AAULL, 0x2E5EB66066087D7EULL, 0x6CAE578BCFE24BEDULL, + 0xABBF75B735DC1058ULL, 0xE94F945C9C3626CBULL, 0x676DD025684A91A1ULL, + 0x259D31CEC1A0A732ULL, 0xE28C13F23B9EFC87ULL, 0xA07CF2199274CA14ULL, + 0x167FF3EACBAF2AF1ULL, 0x548F120162451C62ULL, 0x939E303D987B47D7ULL, + 0xD16ED1D631917144ULL, 0x5F4C95AFC5EDC62EULL, 0x1DBC74446C07F0BDULL, + 0xDAAD56789639AB08ULL, 0x985DB7933FD39D9BULL, 0x84193F60D72AF34FULL, + 0xC6E9DE8B7EC0C5DCULL, 0x01F8FCB784FE9E69ULL, 0x43081D5C2D14A8FAULL, + 0xCD2A5925D9681F90ULL, 0x8FDAB8CE70822903ULL, 0x48CB9AF28ABC72B6ULL, + 0x0A3B7B1923564425ULL, 0x70428B155B4EAF1EULL, 0x32B26AFEF2A4998DULL, + 0xF5A348C2089AC238ULL, 0xB753A929A170F4ABULL, 0x3971ED50550C43C1ULL, + 0x7B810CBBFCE67552ULL, 0xBC902E8706D82EE7ULL, 0xFE60CF6CAF321874ULL, + 0xE224479F47CB76A0ULL, 0xA0D4A674EE214033ULL, 0x67C58448141F1B86ULL, + 0x253565A3BDF52D15ULL, 0xAB1721DA49899A7FULL, 0xE9E7C031E063ACECULL, + 0x2EF6E20D1A5DF759ULL, 0x6C0603E6B3B7C1CAULL, 0xF6FAE5C07D3274CDULL, + 0xB40A042BD4D8425EULL, 0x731B26172EE619EBULL, 0x31EBC7FC870C2F78ULL, + 0xBFC9838573709812ULL, 0xFD39626EDA9AAE81ULL, 0x3A28405220A4F534ULL, + 0x78D8A1B9894EC3A7ULL, 0x649C294A61B7AD73ULL, 0x266CC8A1C85D9BE0ULL, + 0xE17DEA9D3263C055ULL, 0xA38D0B769B89F6C6ULL, 0x2DAF4F0F6FF541ACULL, + 0x6F5FAEE4C61F773FULL, 0xA84E8CD83C212C8AULL, 0xEABE6D3395CB1A19ULL, + 0x90C79D3FEDD3F122ULL, 0xD2377CD44439C7B1ULL, 0x15265EE8BE079C04ULL, + 0x57D6BF0317EDAA97ULL, 0xD9F4FB7AE3911DFDULL, 0x9B041A914A7B2B6EULL, + 0x5C1538ADB04570DBULL, 0x1EE5D94619AF4648ULL, 0x02A151B5F156289CULL, + 0x4051B05E58BC1E0FULL, 0x87409262A28245BAULL, 0xC5B073890B687329ULL, + 0x4B9237F0FF14C443ULL, 0x0962D61B56FEF2D0ULL, 0xCE73F427ACC0A965ULL, + 0x8C8315CC052A9FF6ULL, 0x3A80143F5CF17F13ULL, 0x7870F5D4F51B4980ULL, + 0xBF61D7E80F251235ULL, 0xFD913603A6CF24A6ULL, 0x73B3727A52B393CCULL, + 0x31439391FB59A55FULL, 0xF652B1AD0167FEEAULL, 0xB4A25046A88DC879ULL, + 0xA8E6D8B54074A6ADULL, 0xEA16395EE99E903EULL, 0x2D071B6213A0CB8BULL, + 0x6FF7FA89BA4AFD18ULL, 0xE1D5BEF04E364A72ULL, 0xA3255F1BE7DC7CE1ULL, + 0x64347D271DE22754ULL, 0x26C49CCCB40811C7ULL, 0x5CBD6CC0CC10FAFCULL, + 0x1E4D8D2B65FACC6FULL, 0xD95CAF179FC497DAULL, 0x9BAC4EFC362EA149ULL, + 0x158E0A85C2521623ULL, 0x577EEB6E6BB820B0ULL, 0x906FC95291867B05ULL, + 0xD29F28B9386C4D96ULL, 0xCEDBA04AD0952342ULL, 0x8C2B41A1797F15D1ULL, + 0x4B3A639D83414E64ULL, 0x09CA82762AAB78F7ULL, 0x87E8C60FDED7CF9DULL, + 0xC51827E4773DF90EULL, 0x020905D88D03A2BBULL, 0x40F9E43324E99428ULL, + 0x2CFFE7D5975E55E2ULL, 0x6E0F063E3EB46371ULL, 0xA91E2402C48A38C4ULL, + 0xEBEEC5E96D600E57ULL, 0x65CC8190991CB93DULL, 0x273C607B30F68FAEULL, + 0xE02D4247CAC8D41BULL, 0xA2DDA3AC6322E288ULL, 0xBE992B5F8BDB8C5CULL, + 0xFC69CAB42231BACFULL, 0x3B78E888D80FE17AULL, 0x7988096371E5D7E9ULL, + 0xF7AA4D1A85996083ULL, 0xB55AACF12C735610ULL, 0x724B8ECDD64D0DA5ULL, + 0x30BB6F267FA73B36ULL, 0x4AC29F2A07BFD00DULL, 0x08327EC1AE55E69EULL, + 0xCF235CFD546BBD2BULL, 0x8DD3BD16FD818BB8ULL, 0x03F1F96F09FD3CD2ULL, + 0x41011884A0170A41ULL, 0x86103AB85A2951F4ULL, 0xC4E0DB53F3C36767ULL, + 0xD8A453A01B3A09B3ULL, 0x9A54B24BB2D03F20ULL, 0x5D45907748EE6495ULL, + 0x1FB5719CE1045206ULL, 0x919735E51578E56CULL, 0xD367D40EBC92D3FFULL, + 0x1476F63246AC884AULL, 0x568617D9EF46BED9ULL, 0xE085162AB69D5E3CULL, + 0xA275F7C11F7768AFULL, 0x6564D5FDE549331AULL, 0x279434164CA30589ULL, + 0xA9B6706FB8DFB2E3ULL, 0xEB46918411358470ULL, 0x2C57B3B8EB0BDFC5ULL, + 0x6EA7525342E1E956ULL, 0x72E3DAA0AA188782ULL, 0x30133B4B03F2B111ULL, + 0xF7021977F9CCEAA4ULL, 0xB5F2F89C5026DC37ULL, 0x3BD0BCE5A45A6B5DULL, + 0x79205D0E0DB05DCEULL, 0xBE317F32F78E067BULL, 0xFCC19ED95E6430E8ULL, + 0x86B86ED5267CDBD3ULL, 0xC4488F3E8F96ED40ULL, 0x0359AD0275A8B6F5ULL, + 0x41A94CE9DC428066ULL, 0xCF8B0890283E370CULL, 0x8D7BE97B81D4019FULL, + 0x4A6ACB477BEA5A2AULL, 0x089A2AACD2006CB9ULL, 0x14DEA25F3AF9026DULL, + 0x562E43B4931334FEULL, 0x913F6188692D6F4BULL, 0xD3CF8063C0C759D8ULL, + 0x5DEDC41A34BBEEB2ULL, 0x1F1D25F19D51D821ULL, 0xD80C07CD676F8394ULL, + 0x9AFCE626CE85B507ULL +}; + +void +isc_crc64_init(uint64_t *crc) { + REQUIRE(crc != NULL); + + *crc = 0xffffffffffffffffULL; +} + +void +isc_crc64_update(uint64_t *crc, const void *data, size_t len) { + const unsigned char *p = data; + int i; + + REQUIRE(crc != NULL); + REQUIRE(data != NULL); + + while (len-- > 0U) { + i = ((int)(*crc >> 56) ^ *p++) & 0xff; + *crc = crc64_table[i] ^ (*crc << 8); + } +} + +void +isc_crc64_final(uint64_t *crc) { + REQUIRE(crc != NULL); + + *crc ^= 0xffffffffffffffffULL; +} diff --git a/lib/isc/entropy.c b/lib/isc/entropy.c new file mode 100644 index 0000000..ce79ba2 --- /dev/null +++ b/lib/isc/entropy.c @@ -0,0 +1,28 @@ +/* + * 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. + */ + +#include <openssl/err.h> +#include <openssl/rand.h> + +#include <isc/types.h> +#include <isc/util.h> + +#include "entropy_private.h" + +void +isc_entropy_get(void *buf, size_t buflen) { + if (RAND_bytes(buf, buflen) < 1) { + FATAL_ERROR(__FILE__, __LINE__, "RAND_bytes(): %s", + ERR_error_string(ERR_get_error(), NULL)); + } +} diff --git a/lib/isc/entropy_private.h b/lib/isc/entropy_private.h new file mode 100644 index 0000000..df9a382 --- /dev/null +++ b/lib/isc/entropy_private.h @@ -0,0 +1,37 @@ +/* + * 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. + */ + +#pragma once + +#include <stdlib.h> + +#include <isc/lang.h> + +/*! \file isc/entropy_private.h + * \brief Implements wrapper around CSPRNG cryptographic library calls + * for getting cryptographically secure pseudo-random numbers. + * + * - If OpenSSL is used, it uses RAND_bytes() + * - If PKCS#11 is used, it uses pkcs_C_GenerateRandom() + * + */ + +ISC_LANG_BEGINDECLS + +void +isc_entropy_get(void *buf, size_t buflen); +/*!< + * \brief Get cryptographically-secure pseudo-random data. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/isc/error.c b/lib/isc/error.c new file mode 100644 index 0000000..336be84 --- /dev/null +++ b/lib/isc/error.c @@ -0,0 +1,94 @@ +/* + * 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. + */ + +/*! \file */ + +#include <stdio.h> +#include <stdlib.h> + +#include <isc/error.h> +#include <isc/print.h> + +/*% Default unexpected callback. */ +static void +default_unexpected_callback(const char *, int, const char *, va_list) + ISC_FORMAT_PRINTF(3, 0); + +/*% Default fatal callback. */ +static void +default_fatal_callback(const char *, int, const char *, va_list) + ISC_FORMAT_PRINTF(3, 0); + +/*% unexpected_callback */ +static isc_errorcallback_t unexpected_callback = default_unexpected_callback; +static isc_errorcallback_t fatal_callback = default_fatal_callback; + +void +isc_error_setunexpected(isc_errorcallback_t cb) { + if (cb == NULL) { + unexpected_callback = default_unexpected_callback; + } else { + unexpected_callback = cb; + } +} + +void +isc_error_setfatal(isc_errorcallback_t cb) { + if (cb == NULL) { + fatal_callback = default_fatal_callback; + } else { + fatal_callback = cb; + } +} + +void +isc_error_unexpected(const char *file, int line, const char *format, ...) { + va_list args; + + va_start(args, format); + (unexpected_callback)(file, line, format, args); + va_end(args); +} + +void +isc_error_fatal(const char *file, int line, const char *format, ...) { + va_list args; + + va_start(args, format); + (fatal_callback)(file, line, format, args); + va_end(args); + abort(); +} + +void +isc_error_runtimecheck(const char *file, int line, const char *expression) { + isc_error_fatal(file, line, "RUNTIME_CHECK(%s) failed", expression); +} + +static void +default_unexpected_callback(const char *file, int line, const char *format, + va_list args) { + fprintf(stderr, "%s:%d: ", file, line); + vfprintf(stderr, format, args); + fprintf(stderr, "\n"); + fflush(stderr); +} + +static void +default_fatal_callback(const char *file, int line, const char *format, + va_list args) { + fprintf(stderr, "%s:%d: fatal error: ", file, line); + vfprintf(stderr, format, args); + fprintf(stderr, "\n"); + fflush(stderr); +} diff --git a/lib/isc/event.c b/lib/isc/event.c new file mode 100644 index 0000000..4849d01 --- /dev/null +++ b/lib/isc/event.c @@ -0,0 +1,95 @@ +/* + * 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. + */ + +/*! + * \file + */ + +#include <isc/event.h> +#include <isc/mem.h> +#include <isc/util.h> + +/*** + *** Events. + ***/ + +static void +destroy(isc_event_t *event) { + isc_mem_t *mctx = event->ev_destroy_arg; + + isc_mem_put(mctx, event, event->ev_size); +} + +isc_event_t * +isc_event_allocate(isc_mem_t *mctx, void *sender, isc_eventtype_t type, + isc_taskaction_t action, void *arg, size_t size) { + isc_event_t *event; + + REQUIRE(size >= sizeof(struct isc_event)); + REQUIRE(action != NULL); + + event = isc_mem_get(mctx, size); + + ISC_EVENT_INIT(event, size, 0, NULL, type, action, arg, sender, destroy, + mctx); + + return (event); +} + +isc_event_t * +isc_event_constallocate(isc_mem_t *mctx, void *sender, isc_eventtype_t type, + isc_taskaction_t action, const void *arg, size_t size) { + isc_event_t *event; + void *deconst_arg; + + REQUIRE(size >= sizeof(struct isc_event)); + REQUIRE(action != NULL); + + event = isc_mem_get(mctx, size); + + /* + * Removing the const attribute from "arg" is the best of two + * evils here. If the event->ev_arg member is made const, then + * it affects a great many users of the task/event subsystem + * which are not passing in an "arg" which starts its life as + * const. Changing isc_event_allocate() and isc_task_onshutdown() + * to not have "arg" prototyped as const (which is quite legitimate, + * because neither of those functions modify arg) can cause + * compiler whining anytime someone does want to use a const + * arg that they themselves never modify, such as with + * gcc -Wwrite-strings and using a string "arg". + */ + DE_CONST(arg, deconst_arg); + + ISC_EVENT_INIT(event, size, 0, NULL, type, action, deconst_arg, sender, + destroy, mctx); + + return (event); +} + +void +isc_event_free(isc_event_t **eventp) { + isc_event_t *event; + + REQUIRE(eventp != NULL); + event = *eventp; + *eventp = NULL; + REQUIRE(event != NULL); + + REQUIRE(!ISC_LINK_LINKED(event, ev_link)); + REQUIRE(!ISC_LINK_LINKED(event, ev_ratelink)); + + if (event->ev_destroy != NULL) { + (event->ev_destroy)(event); + } +} diff --git a/lib/isc/fsaccess.c b/lib/isc/fsaccess.c new file mode 100644 index 0000000..54bf38a --- /dev/null +++ b/lib/isc/fsaccess.c @@ -0,0 +1,103 @@ +/* + * 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. + */ + +/*! \file + * \brief + * This file contains the OS-independent functionality of the API. + */ +#include <stdbool.h> + +#include <isc/fsaccess.h> +#include <isc/print.h> +#include <isc/result.h> +#include <isc/util.h> + +/*! + * Shorthand. Maybe ISC__FSACCESS_PERMISSIONBITS should not even be in + * <isc/fsaccess.h>. Could check consistency with sizeof(isc_fsaccess_t) + * and the number of bits in each function. + */ +#define STEP (ISC__FSACCESS_PERMISSIONBITS) +#define GROUP (STEP) +#define OTHER (STEP * 2) + +void +isc_fsaccess_add(int trustee, int permission, isc_fsaccess_t *access) { + REQUIRE(trustee <= 0x7); + REQUIRE(permission <= 0xFF); + + if ((trustee & ISC_FSACCESS_OWNER) != 0) { + *access |= permission; + } + + if ((trustee & ISC_FSACCESS_GROUP) != 0) { + *access |= (permission << GROUP); + } + + if ((trustee & ISC_FSACCESS_OTHER) != 0) { + *access |= (permission << OTHER); + } +} + +void +isc_fsaccess_remove(int trustee, int permission, isc_fsaccess_t *access) { + REQUIRE(trustee <= 0x7); + REQUIRE(permission <= 0xFF); + + if ((trustee & ISC_FSACCESS_OWNER) != 0) { + *access &= ~permission; + } + + if ((trustee & ISC_FSACCESS_GROUP) != 0) { + *access &= ~(permission << GROUP); + } + + if ((trustee & ISC_FSACCESS_OTHER) != 0) { + *access &= ~(permission << OTHER); + } +} + +static isc_result_t +check_bad_bits(isc_fsaccess_t access, bool is_dir) { + isc_fsaccess_t bits; + + /* + * Check for disallowed user bits. + */ + if (is_dir) { + bits = ISC_FSACCESS_READ | ISC_FSACCESS_WRITE | + ISC_FSACCESS_EXECUTE; + } else { + bits = ISC_FSACCESS_CREATECHILD | ISC_FSACCESS_ACCESSCHILD | + ISC_FSACCESS_DELETECHILD | ISC_FSACCESS_LISTDIRECTORY; + } + + /* + * Set group bad bits. + */ + bits |= bits << STEP; + /* + * Set other bad bits. + */ + bits |= bits << STEP; + + if ((access & bits) != 0) { + if (is_dir) { + return (ISC_R_NOTFILE); + } else { + return (ISC_R_NOTDIRECTORY); + } + } + + return (ISC_R_SUCCESS); +} diff --git a/lib/isc/hash.c b/lib/isc/hash.c new file mode 100644 index 0000000..b3fb97a --- /dev/null +++ b/lib/isc/hash.c @@ -0,0 +1,153 @@ +/* + * 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. + */ + +#include <inttypes.h> +#include <stdbool.h> +#include <stddef.h> +#if defined(WIN32) || defined(WIN64) +#include <malloc.h> +#endif /* if defined(WIN32) || defined(WIN64) */ + +#include "entropy_private.h" +#include "isc/hash.h" /* IWYU pragma: keep */ +#include "isc/likely.h" +#include "isc/once.h" +#include "isc/random.h" +#include "isc/result.h" +#include "isc/siphash.h" +#include "isc/string.h" +#include "isc/types.h" +#include "isc/util.h" + +static uint8_t isc_hash_key[16]; +static uint8_t isc_hash32_key[8]; +static bool hash_initialized = false; +static isc_once_t isc_hash_once = ISC_ONCE_INIT; + +static void +isc_hash_initialize(void) { + /* + * Set a constant key to help in problem reproduction should + * fuzzing find a crash or a hang. + */ + uint64_t key[2] = { 0, 1 }; +#if !FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + isc_entropy_get(key, sizeof(key)); +#endif /* if FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION */ + memmove(isc_hash_key, key, sizeof(isc_hash_key)); +#if !FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + isc_entropy_get(key, sizeof(key)); +#endif /* if FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION */ + memmove(isc_hash32_key, key, sizeof(isc_hash32_key)); + hash_initialized = true; +} + +static uint8_t maptolower[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, + 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, + 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, + 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, + 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, + 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, + 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, + 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, + 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, + 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, + 0xfc, 0xfd, 0xfe, 0xff +}; + +const void * +isc_hash_get_initializer(void) { + if (ISC_UNLIKELY(!hash_initialized)) { + RUNTIME_CHECK( + isc_once_do(&isc_hash_once, isc_hash_initialize) == + ISC_R_SUCCESS); + } + + return (isc_hash_key); +} + +void +isc_hash_set_initializer(const void *initializer) { + REQUIRE(initializer != NULL); + + /* + * Ensure that isc_hash_initialize() is not called after + * isc_hash_set_initializer() is called. + */ + if (ISC_UNLIKELY(!hash_initialized)) { + RUNTIME_CHECK( + isc_once_do(&isc_hash_once, isc_hash_initialize) == + ISC_R_SUCCESS); + } + + memmove(isc_hash_key, initializer, sizeof(isc_hash_key)); +} + +uint64_t +isc_hash64(const void *data, const size_t length, const bool case_sensitive) { + uint64_t hval; + + REQUIRE(length == 0 || data != NULL); + + RUNTIME_CHECK(isc_once_do(&isc_hash_once, isc_hash_initialize) == + ISC_R_SUCCESS); + + if (case_sensitive) { + isc_siphash24(isc_hash_key, data, length, (uint8_t *)&hval); + } else { + uint8_t input[1024]; + REQUIRE(length <= 1024); + for (unsigned int i = 0; i < length; i++) { + input[i] = maptolower[((const uint8_t *)data)[i]]; + } + isc_siphash24(isc_hash_key, input, length, (uint8_t *)&hval); + } + + return (hval); +} + +uint32_t +isc_hash32(const void *data, const size_t length, const bool case_sensitive) { + uint32_t hval; + + REQUIRE(length == 0 || data != NULL); + + RUNTIME_CHECK(isc_once_do(&isc_hash_once, isc_hash_initialize) == + ISC_R_SUCCESS); + + if (case_sensitive) { + isc_halfsiphash24(isc_hash_key, data, length, (uint8_t *)&hval); + } else { + uint8_t input[1024]; + REQUIRE(length <= 1024); + for (unsigned int i = 0; i < length; i++) { + input[i] = maptolower[((const uint8_t *)data)[i]]; + } + isc_halfsiphash24(isc_hash_key, input, length, + (uint8_t *)&hval); + } + + return (hval); +} diff --git a/lib/isc/heap.c b/lib/isc/heap.c new file mode 100644 index 0000000..7b0cc28 --- /dev/null +++ b/lib/isc/heap.c @@ -0,0 +1,280 @@ +/* + * 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. + */ + +/*! \file + * Heap implementation of priority queues adapted from the following: + * + * \li "Introduction to Algorithms," Cormen, Leiserson, and Rivest, + * MIT Press / McGraw Hill, 1990, ISBN 0-262-03141-8, chapter 7. + * + * \li "Algorithms," Second Edition, Sedgewick, Addison-Wesley, 1988, + * ISBN 0-201-06673-4, chapter 11. + */ + +#include <stdbool.h> + +#include <isc/heap.h> +#include <isc/magic.h> +#include <isc/mem.h> +#include <isc/string.h> /* Required for memmove. */ +#include <isc/util.h> + +/*@{*/ +/*% + * Note: to make heap_parent and heap_left easy to compute, the first + * element of the heap array is not used; i.e. heap subscripts are 1-based, + * not 0-based. The parent is index/2, and the left-child is index*2. + * The right child is index*2+1. + */ +#define heap_parent(i) ((i) >> 1) +#define heap_left(i) ((i) << 1) +/*@}*/ + +#define SIZE_INCREMENT 1024 + +#define HEAP_MAGIC ISC_MAGIC('H', 'E', 'A', 'P') +#define VALID_HEAP(h) ISC_MAGIC_VALID(h, HEAP_MAGIC) + +/*% + * When the heap is in a consistent state, the following invariant + * holds true: for every element i > 1, heap_parent(i) has a priority + * higher than or equal to that of i. + */ +#define HEAPCONDITION(i) \ + ((i) == 1 || \ + !heap->compare(heap->array[(i)], heap->array[heap_parent(i)])) + +/*% ISC heap structure. */ +struct isc_heap { + unsigned int magic; + isc_mem_t *mctx; + unsigned int size; + unsigned int size_increment; + unsigned int last; + void **array; + isc_heapcompare_t compare; + isc_heapindex_t index; +}; + +#ifdef ISC_HEAP_CHECK +static void +heap_check(isc_heap_t *heap) { + unsigned int i; + for (i = 1; i <= heap->last; i++) { + INSIST(HEAPCONDITION(i)); + } +} +#else /* ifdef ISC_HEAP_CHECK */ +#define heap_check(x) (void)0 +#endif /* ifdef ISC_HEAP_CHECK */ + +void +isc_heap_create(isc_mem_t *mctx, isc_heapcompare_t compare, isc_heapindex_t idx, + unsigned int size_increment, isc_heap_t **heapp) { + isc_heap_t *heap; + + REQUIRE(heapp != NULL && *heapp == NULL); + REQUIRE(compare != NULL); + + heap = isc_mem_get(mctx, sizeof(*heap)); + heap->magic = HEAP_MAGIC; + heap->size = 0; + heap->mctx = NULL; + isc_mem_attach(mctx, &heap->mctx); + if (size_increment == 0) { + heap->size_increment = SIZE_INCREMENT; + } else { + heap->size_increment = size_increment; + } + heap->last = 0; + heap->array = NULL; + heap->compare = compare; + heap->index = idx; + + *heapp = heap; +} + +void +isc_heap_destroy(isc_heap_t **heapp) { + isc_heap_t *heap; + + REQUIRE(heapp != NULL); + heap = *heapp; + *heapp = NULL; + REQUIRE(VALID_HEAP(heap)); + + if (heap->array != NULL) { + isc_mem_put(heap->mctx, heap->array, + heap->size * sizeof(void *)); + } + heap->magic = 0; + isc_mem_putanddetach(&heap->mctx, heap, sizeof(*heap)); +} + +static void +resize(isc_heap_t *heap) { + void **new_array; + unsigned int new_size; + + REQUIRE(VALID_HEAP(heap)); + + new_size = heap->size + heap->size_increment; + new_array = isc_mem_get(heap->mctx, new_size * sizeof(void *)); + if (heap->array != NULL) { + memmove(new_array, heap->array, heap->size * sizeof(void *)); + isc_mem_put(heap->mctx, heap->array, + heap->size * sizeof(void *)); + } + heap->size = new_size; + heap->array = new_array; +} + +static void +float_up(isc_heap_t *heap, unsigned int i, void *elt) { + unsigned int p; + + for (p = heap_parent(i); i > 1 && heap->compare(elt, heap->array[p]); + i = p, p = heap_parent(i)) + { + heap->array[i] = heap->array[p]; + if (heap->index != NULL) { + (heap->index)(heap->array[i], i); + } + } + heap->array[i] = elt; + if (heap->index != NULL) { + (heap->index)(heap->array[i], i); + } + + INSIST(HEAPCONDITION(i)); + heap_check(heap); +} + +static void +sink_down(isc_heap_t *heap, unsigned int i, void *elt) { + unsigned int j, size, half_size; + size = heap->last; + half_size = size / 2; + while (i <= half_size) { + /* Find the smallest of the (at most) two children. */ + j = heap_left(i); + if (j < size && + heap->compare(heap->array[j + 1], heap->array[j])) + { + j++; + } + if (heap->compare(elt, heap->array[j])) { + break; + } + heap->array[i] = heap->array[j]; + if (heap->index != NULL) { + (heap->index)(heap->array[i], i); + } + i = j; + } + heap->array[i] = elt; + if (heap->index != NULL) { + (heap->index)(heap->array[i], i); + } + + INSIST(HEAPCONDITION(i)); + heap_check(heap); +} + +void +isc_heap_insert(isc_heap_t *heap, void *elt) { + unsigned int new_last; + + REQUIRE(VALID_HEAP(heap)); + + heap_check(heap); + new_last = heap->last + 1; + RUNTIME_CHECK(new_last > 0); /* overflow check */ + if (new_last >= heap->size) { + resize(heap); + } + heap->last = new_last; + + float_up(heap, new_last, elt); +} + +void +isc_heap_delete(isc_heap_t *heap, unsigned int idx) { + void *elt; + bool less; + + REQUIRE(VALID_HEAP(heap)); + REQUIRE(idx >= 1 && idx <= heap->last); + + heap_check(heap); + if (heap->index != NULL) { + (heap->index)(heap->array[idx], 0); + } + if (idx == heap->last) { + heap->array[heap->last] = NULL; + heap->last--; + heap_check(heap); + } else { + elt = heap->array[heap->last]; + heap->array[heap->last] = NULL; + heap->last--; + + less = heap->compare(elt, heap->array[idx]); + heap->array[idx] = elt; + if (less) { + float_up(heap, idx, heap->array[idx]); + } else { + sink_down(heap, idx, heap->array[idx]); + } + } +} + +void +isc_heap_increased(isc_heap_t *heap, unsigned int idx) { + REQUIRE(VALID_HEAP(heap)); + REQUIRE(idx >= 1 && idx <= heap->last); + + float_up(heap, idx, heap->array[idx]); +} + +void +isc_heap_decreased(isc_heap_t *heap, unsigned int idx) { + REQUIRE(VALID_HEAP(heap)); + REQUIRE(idx >= 1 && idx <= heap->last); + + sink_down(heap, idx, heap->array[idx]); +} + +void * +isc_heap_element(isc_heap_t *heap, unsigned int idx) { + REQUIRE(VALID_HEAP(heap)); + REQUIRE(idx >= 1); + + heap_check(heap); + if (idx <= heap->last) { + return (heap->array[idx]); + } + return (NULL); +} + +void +isc_heap_foreach(isc_heap_t *heap, isc_heapaction_t action, void *uap) { + unsigned int i; + + REQUIRE(VALID_HEAP(heap)); + REQUIRE(action != NULL); + + for (i = 1; i <= heap->last; i++) { + (action)(heap->array[i], uap); + } +} diff --git a/lib/isc/hex.c b/lib/isc/hex.c new file mode 100644 index 0000000..be67f03 --- /dev/null +++ b/lib/isc/hex.c @@ -0,0 +1,212 @@ +/* + * 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. + */ + +/*! \file */ + +#include <ctype.h> +#include <stdbool.h> + +#include <isc/buffer.h> +#include <isc/hex.h> +#include <isc/lex.h> +#include <isc/string.h> +#include <isc/util.h> + +#define RETERR(x) \ + do { \ + isc_result_t _r = (x); \ + if (_r != ISC_R_SUCCESS) \ + return ((_r)); \ + } while (0) + +/* + * BEW: These static functions are copied from lib/dns/rdata.c. + */ +static isc_result_t +str_totext(const char *source, isc_buffer_t *target); + +static isc_result_t +mem_tobuffer(isc_buffer_t *target, void *base, unsigned int length); + +static const char hex[] = "0123456789ABCDEF"; + +isc_result_t +isc_hex_totext(isc_region_t *source, int wordlength, const char *wordbreak, + isc_buffer_t *target) { + char buf[3]; + unsigned int loops = 0; + + if (wordlength < 2) { + wordlength = 2; + } + + memset(buf, 0, sizeof(buf)); + while (source->length > 0) { + buf[0] = hex[(source->base[0] >> 4) & 0xf]; + buf[1] = hex[(source->base[0]) & 0xf]; + RETERR(str_totext(buf, target)); + isc_region_consume(source, 1); + + loops++; + if (source->length != 0 && (int)((loops + 1) * 2) >= wordlength) + { + loops = 0; + RETERR(str_totext(wordbreak, target)); + } + } + return (ISC_R_SUCCESS); +} + +/*% + * State of a hex decoding process in progress. + */ +typedef struct { + int length; /*%< Desired length of binary data or -1 */ + isc_buffer_t *target; /*%< Buffer for resulting binary data */ + int digits; /*%< Number of buffered hex digits */ + int val[2]; +} hex_decode_ctx_t; + +static void +hex_decode_init(hex_decode_ctx_t *ctx, int length, isc_buffer_t *target) { + ctx->digits = 0; + ctx->length = length; + ctx->target = target; +} + +static isc_result_t +hex_decode_char(hex_decode_ctx_t *ctx, int c) { + const char *s; + + if ((s = strchr(hex, toupper(c))) == NULL) { + return (ISC_R_BADHEX); + } + ctx->val[ctx->digits++] = (int)(s - hex); + if (ctx->digits == 2) { + unsigned char num; + + num = (ctx->val[0] << 4) + (ctx->val[1]); + RETERR(mem_tobuffer(ctx->target, &num, 1)); + if (ctx->length >= 0) { + if (ctx->length == 0) { + return (ISC_R_BADHEX); + } else { + ctx->length -= 1; + } + } + ctx->digits = 0; + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +hex_decode_finish(hex_decode_ctx_t *ctx) { + if (ctx->length > 0) { + return (ISC_R_UNEXPECTEDEND); + } + if (ctx->digits != 0) { + return (ISC_R_BADHEX); + } + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_hex_tobuffer(isc_lex_t *lexer, isc_buffer_t *target, int length) { + unsigned int before, after; + hex_decode_ctx_t ctx; + isc_textregion_t *tr; + isc_token_t token; + bool eol; + + REQUIRE(length >= -2); + + hex_decode_init(&ctx, length, target); + + before = isc_buffer_usedlength(target); + while (ctx.length != 0) { + unsigned int i; + + if (length > 0) { + eol = false; + } else { + eol = true; + } + RETERR(isc_lex_getmastertoken(lexer, &token, + isc_tokentype_string, eol)); + if (token.type != isc_tokentype_string) { + break; + } + tr = &token.value.as_textregion; + for (i = 0; i < tr->length; i++) { + RETERR(hex_decode_char(&ctx, tr->base[i])); + } + } + after = isc_buffer_usedlength(target); + if (ctx.length < 0) { + isc_lex_ungettoken(lexer, &token); + } + RETERR(hex_decode_finish(&ctx)); + if (length == -2 && before == after) { + return (ISC_R_UNEXPECTEDEND); + } + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_hex_decodestring(const char *cstr, isc_buffer_t *target) { + hex_decode_ctx_t ctx; + + hex_decode_init(&ctx, -1, target); + for (;;) { + int c = *cstr++; + if (c == '\0') { + break; + } + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { + continue; + } + RETERR(hex_decode_char(&ctx, c)); + } + RETERR(hex_decode_finish(&ctx)); + return (ISC_R_SUCCESS); +} + +static isc_result_t +str_totext(const char *source, isc_buffer_t *target) { + unsigned int l; + isc_region_t region; + + isc_buffer_availableregion(target, ®ion); + l = strlen(source); + + if (l > region.length) { + return (ISC_R_NOSPACE); + } + + memmove(region.base, source, l); + isc_buffer_add(target, l); + return (ISC_R_SUCCESS); +} + +static isc_result_t +mem_tobuffer(isc_buffer_t *target, void *base, unsigned int length) { + isc_region_t tr; + + isc_buffer_availableregion(target, &tr); + if (length > tr.length) { + return (ISC_R_NOSPACE); + } + memmove(tr.base, base, length); + isc_buffer_add(target, length); + return (ISC_R_SUCCESS); +} diff --git a/lib/isc/hmac.c b/lib/isc/hmac.c new file mode 100644 index 0000000..442ae7f --- /dev/null +++ b/lib/isc/hmac.c @@ -0,0 +1,146 @@ +/* + * 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. + */ + +#include <openssl/hmac.h> +#include <openssl/opensslv.h> + +#include <isc/assertions.h> +#include <isc/hmac.h> +#include <isc/md.h> +#include <isc/platform.h> +#include <isc/safe.h> +#include <isc/string.h> +#include <isc/types.h> +#include <isc/util.h> + +#include "openssl_shim.h" + +isc_hmac_t * +isc_hmac_new(void) { + HMAC_CTX *hmac = HMAC_CTX_new(); + RUNTIME_CHECK(hmac != NULL); + return ((struct hmac *)hmac); +} + +void +isc_hmac_free(isc_hmac_t *hmac) { + if (ISC_UNLIKELY(hmac == NULL)) { + return; + } + + HMAC_CTX_free(hmac); +} + +isc_result_t +isc_hmac_init(isc_hmac_t *hmac, const void *key, size_t keylen, + const isc_md_type_t *md_type) { + REQUIRE(hmac != NULL); + REQUIRE(key != NULL); + + if (md_type == NULL) { + return (ISC_R_NOTIMPLEMENTED); + } + + if (HMAC_Init_ex(hmac, key, keylen, md_type, NULL) != 1) { + return (ISC_R_CRYPTOFAILURE); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_hmac_reset(isc_hmac_t *hmac) { + REQUIRE(hmac != NULL); + + if (HMAC_CTX_reset(hmac) != 1) { + return (ISC_R_CRYPTOFAILURE); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_hmac_update(isc_hmac_t *hmac, const unsigned char *buf, const size_t len) { + REQUIRE(hmac != NULL); + + if (ISC_UNLIKELY(buf == NULL || len == 0)) { + return (ISC_R_SUCCESS); + } + + if (HMAC_Update(hmac, buf, len) != 1) { + return (ISC_R_CRYPTOFAILURE); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_hmac_final(isc_hmac_t *hmac, unsigned char *digest, + unsigned int *digestlen) { + REQUIRE(hmac != NULL); + REQUIRE(digest != NULL); + + if (HMAC_Final(hmac, digest, digestlen) != 1) { + return (ISC_R_CRYPTOFAILURE); + } + + return (ISC_R_SUCCESS); +} + +const isc_md_type_t * +isc_hmac_get_md_type(isc_hmac_t *hmac) { + REQUIRE(hmac != NULL); + + return (HMAC_CTX_get_md(hmac)); +} + +size_t +isc_hmac_get_size(isc_hmac_t *hmac) { + REQUIRE(hmac != NULL); + + return ((size_t)EVP_MD_size(HMAC_CTX_get_md(hmac))); +} + +int +isc_hmac_get_block_size(isc_hmac_t *hmac) { + REQUIRE(hmac != NULL); + + return (EVP_MD_block_size(HMAC_CTX_get_md(hmac))); +} + +isc_result_t +isc_hmac(const isc_md_type_t *type, const void *key, const int keylen, + const unsigned char *buf, const size_t len, unsigned char *digest, + unsigned int *digestlen) { + isc_result_t res; + isc_hmac_t *hmac = isc_hmac_new(); + + res = isc_hmac_init(hmac, key, keylen, type); + if (res != ISC_R_SUCCESS) { + goto end; + } + + res = isc_hmac_update(hmac, buf, len); + if (res != ISC_R_SUCCESS) { + goto end; + } + + res = isc_hmac_final(hmac, digest, digestlen); + if (res != ISC_R_SUCCESS) { + goto end; + } +end: + isc_hmac_free(hmac); + + return (res); +} diff --git a/lib/isc/ht.c b/lib/isc/ht.c new file mode 100644 index 0000000..07a36b4 --- /dev/null +++ b/lib/isc/ht.c @@ -0,0 +1,342 @@ +/* + * 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. + */ + +#include <inttypes.h> +#include <string.h> + +#include <isc/hash.h> +#include <isc/ht.h> +#include <isc/magic.h> +#include <isc/mem.h> +#include <isc/result.h> +#include <isc/types.h> +#include <isc/util.h> + +typedef struct isc_ht_node isc_ht_node_t; + +#define ISC_HT_MAGIC ISC_MAGIC('H', 'T', 'a', 'b') +#define ISC_HT_VALID(ht) ISC_MAGIC_VALID(ht, ISC_HT_MAGIC) + +struct isc_ht_node { + void *value; + isc_ht_node_t *next; + size_t keysize; + unsigned char key[FLEXIBLE_ARRAY_MEMBER]; +}; + +struct isc_ht { + unsigned int magic; + isc_mem_t *mctx; + size_t size; + size_t mask; + unsigned int count; + isc_ht_node_t **table; +}; + +struct isc_ht_iter { + isc_ht_t *ht; + size_t i; + isc_ht_node_t *cur; +}; + +void +isc_ht_init(isc_ht_t **htp, isc_mem_t *mctx, uint8_t bits) { + isc_ht_t *ht = NULL; + size_t i; + + REQUIRE(htp != NULL && *htp == NULL); + REQUIRE(mctx != NULL); + REQUIRE(bits >= 1 && bits <= (sizeof(size_t) * 8 - 1)); + + ht = isc_mem_get(mctx, sizeof(struct isc_ht)); + + ht->mctx = NULL; + isc_mem_attach(mctx, &ht->mctx); + + ht->size = ((size_t)1 << bits); + ht->mask = ((size_t)1 << bits) - 1; + ht->count = 0; + + ht->table = isc_mem_get(ht->mctx, ht->size * sizeof(isc_ht_node_t *)); + + for (i = 0; i < ht->size; i++) { + ht->table[i] = NULL; + } + + ht->magic = ISC_HT_MAGIC; + + *htp = ht; +} + +void +isc_ht_destroy(isc_ht_t **htp) { + isc_ht_t *ht; + size_t i; + + REQUIRE(htp != NULL); + + ht = *htp; + *htp = NULL; + + REQUIRE(ISC_HT_VALID(ht)); + + ht->magic = 0; + + for (i = 0; i < ht->size; i++) { + isc_ht_node_t *node = ht->table[i]; + while (node != NULL) { + isc_ht_node_t *next = node->next; + ht->count--; + isc_mem_put(ht->mctx, node, + offsetof(isc_ht_node_t, key) + + node->keysize); + node = next; + } + } + + INSIST(ht->count == 0); + + isc_mem_put(ht->mctx, ht->table, ht->size * sizeof(isc_ht_node_t *)); + isc_mem_putanddetach(&ht->mctx, ht, sizeof(struct isc_ht)); +} + +isc_result_t +isc_ht_add(isc_ht_t *ht, const unsigned char *key, uint32_t keysize, + void *value) { + isc_ht_node_t *node; + uint32_t hash; + + REQUIRE(ISC_HT_VALID(ht)); + REQUIRE(key != NULL && keysize > 0); + + hash = isc_hash_function(key, keysize, true); + node = ht->table[hash & ht->mask]; + while (node != NULL) { + if (keysize == node->keysize && + memcmp(key, node->key, keysize) == 0) + { + return (ISC_R_EXISTS); + } + node = node->next; + } + + node = isc_mem_get(ht->mctx, offsetof(isc_ht_node_t, key) + keysize); + + memmove(node->key, key, keysize); + node->keysize = keysize; + node->next = ht->table[hash & ht->mask]; + node->value = value; + + ht->count++; + ht->table[hash & ht->mask] = node; + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_ht_find(const isc_ht_t *ht, const unsigned char *key, uint32_t keysize, + void **valuep) { + isc_ht_node_t *node; + uint32_t hash; + + REQUIRE(ISC_HT_VALID(ht)); + REQUIRE(key != NULL && keysize > 0); + REQUIRE(valuep == NULL || *valuep == NULL); + + hash = isc_hash_function(key, keysize, true); + node = ht->table[hash & ht->mask]; + while (node != NULL) { + if (keysize == node->keysize && + memcmp(key, node->key, keysize) == 0) + { + if (valuep != NULL) { + *valuep = node->value; + } + return (ISC_R_SUCCESS); + } + node = node->next; + } + + return (ISC_R_NOTFOUND); +} + +isc_result_t +isc_ht_delete(isc_ht_t *ht, const unsigned char *key, uint32_t keysize) { + isc_ht_node_t *node, *prev; + uint32_t hash; + + REQUIRE(ISC_HT_VALID(ht)); + REQUIRE(key != NULL && keysize > 0); + + prev = NULL; + hash = isc_hash_function(key, keysize, true); + node = ht->table[hash & ht->mask]; + while (node != NULL) { + if (keysize == node->keysize && + memcmp(key, node->key, keysize) == 0) + { + if (prev == NULL) { + ht->table[hash & ht->mask] = node->next; + } else { + prev->next = node->next; + } + isc_mem_put(ht->mctx, node, + offsetof(isc_ht_node_t, key) + + node->keysize); + ht->count--; + + return (ISC_R_SUCCESS); + } + + prev = node; + node = node->next; + } + return (ISC_R_NOTFOUND); +} + +void +isc_ht_iter_create(isc_ht_t *ht, isc_ht_iter_t **itp) { + isc_ht_iter_t *it; + + REQUIRE(ISC_HT_VALID(ht)); + REQUIRE(itp != NULL && *itp == NULL); + + it = isc_mem_get(ht->mctx, sizeof(isc_ht_iter_t)); + + it->ht = ht; + it->i = 0; + it->cur = NULL; + + *itp = it; +} + +void +isc_ht_iter_destroy(isc_ht_iter_t **itp) { + isc_ht_iter_t *it; + isc_ht_t *ht; + + REQUIRE(itp != NULL && *itp != NULL); + + it = *itp; + *itp = NULL; + ht = it->ht; + isc_mem_put(ht->mctx, it, sizeof(isc_ht_iter_t)); +} + +isc_result_t +isc_ht_iter_first(isc_ht_iter_t *it) { + REQUIRE(it != NULL); + + it->i = 0; + while (it->i < it->ht->size && it->ht->table[it->i] == NULL) { + it->i++; + } + + if (it->i == it->ht->size) { + return (ISC_R_NOMORE); + } + + it->cur = it->ht->table[it->i]; + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_ht_iter_next(isc_ht_iter_t *it) { + REQUIRE(it != NULL); + REQUIRE(it->cur != NULL); + + it->cur = it->cur->next; + if (it->cur == NULL) { + do { + it->i++; + } while (it->i < it->ht->size && it->ht->table[it->i] == NULL); + if (it->i >= it->ht->size) { + return (ISC_R_NOMORE); + } + it->cur = it->ht->table[it->i]; + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_ht_iter_delcurrent_next(isc_ht_iter_t *it) { + isc_result_t result = ISC_R_SUCCESS; + isc_ht_node_t *to_delete = NULL; + isc_ht_node_t *prev = NULL; + isc_ht_node_t *node = NULL; + uint32_t hash; + isc_ht_t *ht; + REQUIRE(it != NULL); + REQUIRE(it->cur != NULL); + to_delete = it->cur; + ht = it->ht; + + it->cur = it->cur->next; + if (it->cur == NULL) { + do { + it->i++; + } while (it->i < ht->size && ht->table[it->i] == NULL); + if (it->i >= ht->size) { + result = ISC_R_NOMORE; + } else { + it->cur = ht->table[it->i]; + } + } + + hash = isc_hash_function(to_delete->key, to_delete->keysize, true); + node = ht->table[hash & ht->mask]; + while (node != to_delete) { + prev = node; + node = node->next; + INSIST(node != NULL); + } + + if (prev == NULL) { + ht->table[hash & ht->mask] = node->next; + } else { + prev->next = node->next; + } + isc_mem_put(ht->mctx, node, + offsetof(isc_ht_node_t, key) + node->keysize); + ht->count--; + + return (result); +} + +void +isc_ht_iter_current(isc_ht_iter_t *it, void **valuep) { + REQUIRE(it != NULL); + REQUIRE(it->cur != NULL); + REQUIRE(valuep != NULL && *valuep == NULL); + + *valuep = it->cur->value; +} + +void +isc_ht_iter_currentkey(isc_ht_iter_t *it, unsigned char **key, + size_t *keysize) { + REQUIRE(it != NULL); + REQUIRE(it->cur != NULL); + REQUIRE(key != NULL && *key == NULL); + + *key = it->cur->key; + *keysize = it->cur->keysize; +} + +unsigned int +isc_ht_count(isc_ht_t *ht) { + REQUIRE(ISC_HT_VALID(ht)); + + return (ht->count); +} diff --git a/lib/isc/httpd.c b/lib/isc/httpd.c new file mode 100644 index 0000000..b5ac5e4 --- /dev/null +++ b/lib/isc/httpd.c @@ -0,0 +1,1347 @@ +/* + * 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. + */ + +/*! \file */ + +#include <inttypes.h> +#include <stdbool.h> +#include <string.h> + +#include <isc/buffer.h> +#include <isc/httpd.h> +#include <isc/mem.h> +#include <isc/print.h> +#include <isc/refcount.h> +#include <isc/socket.h> +#include <isc/string.h> +#include <isc/task.h> +#include <isc/time.h> +#include <isc/util.h> + +#ifdef HAVE_ZLIB +#include <zlib.h> +#endif /* ifdef HAVE_ZLIB */ + +/*% + * TODO: + * + * o Make the URL processing external functions which will fill-in a buffer + * structure we provide, or return an error and we will render a generic + * page and close the client. + */ + +#define MSHUTTINGDOWN(cm) ((cm->flags & ISC_HTTPDMGR_FLAGSHUTTINGDOWN) != 0) +#define MSETSHUTTINGDOWN(cm) (cm->flags |= ISC_HTTPDMGR_FLAGSHUTTINGDOWN) + +#define HTTP_RECVLEN 1024 +#define HTTP_SENDGROW 1024 +#define HTTP_SEND_MAXLEN 10240 + +#define HTTPD_CLOSE 0x0001 /* Got a Connection: close header */ +#define HTTPD_FOUNDHOST 0x0002 /* Got a Host: header */ +#define HTTPD_KEEPALIVE 0x0004 /* Got a Connection: Keep-Alive */ +#define HTTPD_ACCEPT_DEFLATE 0x0008 + +#define HTTPD_MAGIC ISC_MAGIC('H', 't', 'p', 'd') +#define VALID_HTTPD(m) ISC_MAGIC_VALID(m, HTTPD_MAGIC) + +#define HTTPDMGR_MAGIC ISC_MAGIC('H', 'p', 'd', 'm') +#define VALID_HTTPDMGR(m) ISC_MAGIC_VALID(m, HTTPDMGR_MAGIC) + +/*% http client */ +struct isc_httpd { + unsigned int magic; /* HTTPD_MAGIC */ + isc_refcount_t references; + isc_httpdmgr_t *mgr; /*%< our parent */ + ISC_LINK(isc_httpd_t) link; + unsigned int state; + isc_socket_t *sock; + + /*% + * Received data state. + */ + char recvbuf[HTTP_RECVLEN]; /*%< receive buffer */ + uint32_t recvlen; /*%< length recv'd */ + char *headers; /*%< set in process_request() */ + unsigned int method; + char *url; + char *querystring; + char *protocol; + + /* + * Flags on the httpd client. + */ + int flags; + + /*% + * Transmit data state. + * + * This is the data buffer we will transmit. + * + * This free function pointer is filled in by the rendering function + * we call. The free function is called after the data is transmitted + * to the client. + * + * The bufflist is the list of buffers we are currently transmitting. + * The headerbuffer is where we render our headers to. If we run out + * of space when rendering a header, we will change the size of our + * buffer. We will not free it until we are finished, and will + * allocate an additional HTTP_SENDGROW bytes per header space grow. + * + * We currently use three buffers total, one for the headers (which + * we manage), another for the client to fill in (which it manages, + * it provides the space for it, etc) -- we will pass that buffer + * structure back to the caller, who is responsible for managing the + * space it may have allocated as backing store for it. This second + * buffer is bodybuffer, and we only allocate the buffer itself, not + * the backing store. + * The third buffer is compbuffer, managed by us, that contains the + * compressed HTTP data, if compression is used. + * + */ + isc_buffer_t headerbuffer; + isc_buffer_t compbuffer; + isc_buffer_t *sendbuffer; + + const char *mimetype; + unsigned int retcode; + const char *retmsg; + isc_buffer_t bodybuffer; + isc_httpdfree_t *freecb; + void *freecb_arg; +}; + +/*% lightweight socket manager for httpd output */ +struct isc_httpdmgr { + unsigned int magic; /* HTTPDMGR_MAGIC */ + isc_refcount_t references; + isc_mem_t *mctx; + isc_socket_t *sock; /*%< listening socket */ + isc_task_t *task; /*%< owning task */ + isc_timermgr_t *timermgr; + + isc_httpdclientok_t *client_ok; /*%< client validator */ + isc_httpdondestroy_t *ondestroy; /*%< cleanup callback */ + void *cb_arg; /*%< argument for the above */ + + unsigned int flags; + ISC_LIST(isc_httpd_t) running; /*%< running clients */ + + isc_mutex_t lock; + + ISC_LIST(isc_httpdurl_t) urls; /*%< urls we manage */ + isc_httpdaction_t *render_404; + isc_httpdaction_t *render_500; +}; + +/*% + * HTTP methods. + */ +#define ISC_HTTPD_METHODUNKNOWN 0 +#define ISC_HTTPD_METHODGET 1 +#define ISC_HTTPD_METHODPOST 2 + +/*% + * Client states. + * + * _IDLE The client is not doing anything at all. This state should + * only occur just after creation, and just before being + * destroyed. + * + * _RECV The client is waiting for data after issuing a socket recv(). + * + * _RECVDONE Data has been received, and is being processed. + * + * _SEND All data for a response has completed, and a reply was + * sent via a socket send() call. + * + * _SENDDONE Send is completed. + * + * Badly formatted state table: + * + * IDLE -> RECV when client has a recv() queued. + * + * RECV -> RECVDONE when recvdone event received. + * + * RECVDONE -> SEND if the data for a reply is at hand. + * + * SEND -> RECV when a senddone event was received. + * + * At any time -> RECV on error. If RECV fails, the client will + * self-destroy, closing the socket and freeing memory. + */ +#define ISC_HTTPD_STATEIDLE 0 +#define ISC_HTTPD_STATERECV 1 +#define ISC_HTTPD_STATERECVDONE 2 +#define ISC_HTTPD_STATESEND 3 +#define ISC_HTTPD_STATESENDDONE 4 + +#define ISC_HTTPD_ISRECV(c) ((c)->state == ISC_HTTPD_STATERECV) +#define ISC_HTTPD_ISRECVDONE(c) ((c)->state == ISC_HTTPD_STATERECVDONE) +#define ISC_HTTPD_ISSEND(c) ((c)->state == ISC_HTTPD_STATESEND) +#define ISC_HTTPD_ISSENDDONE(c) ((c)->state == ISC_HTTPD_STATESENDDONE) + +/*% + * Overall magic test that means we're not idle. + */ +#define ISC_HTTPD_SETRECV(c) ((c)->state = ISC_HTTPD_STATERECV) +#define ISC_HTTPD_SETRECVDONE(c) ((c)->state = ISC_HTTPD_STATERECVDONE) +#define ISC_HTTPD_SETSEND(c) ((c)->state = ISC_HTTPD_STATESEND) +#define ISC_HTTPD_SETSENDDONE(c) ((c)->state = ISC_HTTPD_STATESENDDONE) + +static void +isc_httpd_accept(isc_task_t *, isc_event_t *); +static void +isc_httpd_recvdone(isc_task_t *, isc_event_t *); +static void +isc_httpd_senddone(isc_task_t *, isc_event_t *); +static isc_result_t +process_request(isc_httpd_t *, int); +static isc_result_t +grow_headerspace(isc_httpd_t *); +static void +reset_client(isc_httpd_t *httpd); + +static isc_httpdaction_t render_404; +static isc_httpdaction_t render_500; + +#if ENABLE_AFL +static void (*finishhook)(void) = NULL; +#endif /* ENABLE_AFL */ + +static void +maybe_destroy_httpd(isc_httpd_t *); +static void +destroy_httpd(isc_httpd_t *); +static void +maybe_destroy_httpdmgr(isc_httpdmgr_t *); +static void +destroy_httpdmgr(isc_httpdmgr_t *); + +static void +isc_httpdmgr_attach(isc_httpdmgr_t *, isc_httpdmgr_t **); +static void +isc_httpdmgr_detach(isc_httpdmgr_t **); + +static void +maybe_destroy_httpd(isc_httpd_t *httpd) { + if (isc_refcount_decrement(&httpd->references) == 1) { + destroy_httpd(httpd); + } +} + +static void +free_buffer(isc_mem_t *mctx, isc_buffer_t *buffer) { + isc_region_t r; + + isc_buffer_region(buffer, &r); + if (r.length > 0) { + isc_mem_put(mctx, r.base, r.length); + } + + isc_buffer_initnull(buffer); +} + +static void +destroy_httpd(isc_httpd_t *httpd) { + isc_httpdmgr_t *httpdmgr; + + REQUIRE(VALID_HTTPD(httpd)); + + httpdmgr = httpd->mgr; + REQUIRE(VALID_HTTPDMGR(httpdmgr)); + + /* + * Unlink before calling isc_socket_detach so + * isc_httpdmgr_shutdown does not dereference a NULL pointer + * when calling isc_socket_cancel(). + */ + LOCK(&httpdmgr->lock); + ISC_LIST_UNLINK(httpdmgr->running, httpd, link); + UNLOCK(&httpdmgr->lock); + + httpd->magic = 0; + isc_refcount_destroy(&httpd->references); + isc_socket_detach(&httpd->sock); + + free_buffer(httpdmgr->mctx, &httpd->headerbuffer); + free_buffer(httpdmgr->mctx, &httpd->compbuffer); + + isc_mem_put(httpdmgr->mctx, httpd, sizeof(isc_httpd_t)); + +#if ENABLE_AFL + if (finishhook != NULL) { + finishhook(); + } +#endif /* ENABLE_AFL */ + + isc_httpdmgr_detach(&httpdmgr); +} + +static isc_result_t +httpdmgr_socket_accept(isc_task_t *task, isc_httpdmgr_t *httpdmgr) { + isc_result_t result = ISC_R_SUCCESS; + + /* decremented in isc_httpd_accept */ + isc_refcount_increment(&httpdmgr->references); + result = isc_socket_accept(httpdmgr->sock, task, isc_httpd_accept, + httpdmgr); + if (result != ISC_R_SUCCESS) { + INSIST(isc_refcount_decrement(&httpdmgr->references) > 1); + } + return (result); +} + +static void +httpd_socket_recv(isc_httpd_t *httpd, isc_region_t *region, isc_task_t *task) { + isc_result_t result = ISC_R_SUCCESS; + + /* decremented in isc_httpd_recvdone */ + (void)isc_refcount_increment(&httpd->references); + result = isc_socket_recv(httpd->sock, region, 1, task, + isc_httpd_recvdone, httpd); + if (result != ISC_R_SUCCESS) { + INSIST(isc_refcount_decrement(&httpd->references) > 1); + } +} + +static void +httpd_socket_send(isc_httpd_t *httpd, isc_region_t *region, isc_task_t *task) { + isc_result_t result = ISC_R_SUCCESS; + + /* decremented in isc_httpd_senddone */ + (void)isc_refcount_increment(&httpd->references); + result = isc_socket_send(httpd->sock, region, task, isc_httpd_senddone, + httpd); + if (result != ISC_R_SUCCESS) { + INSIST(isc_refcount_decrement(&httpd->references) > 1); + } +} + +isc_result_t +isc_httpdmgr_create(isc_mem_t *mctx, isc_socket_t *sock, isc_task_t *task, + isc_httpdclientok_t *client_ok, + isc_httpdondestroy_t *ondestroy, void *cb_arg, + isc_timermgr_t *tmgr, isc_httpdmgr_t **httpdmgrp) { + isc_result_t result; + isc_httpdmgr_t *httpdmgr; + + REQUIRE(mctx != NULL); + REQUIRE(sock != NULL); + REQUIRE(task != NULL); + REQUIRE(tmgr != NULL); + REQUIRE(httpdmgrp != NULL && *httpdmgrp == NULL); + + httpdmgr = isc_mem_get(mctx, sizeof(isc_httpdmgr_t)); + + *httpdmgr = (isc_httpdmgr_t){ .timermgr = tmgr, /* XXXMLG no attach + * function? */ + .client_ok = client_ok, + .ondestroy = ondestroy, + .cb_arg = cb_arg, + .render_404 = render_404, + .render_500 = render_500 }; + + isc_mutex_init(&httpdmgr->lock); + isc_mem_attach(mctx, &httpdmgr->mctx); + isc_socket_attach(sock, &httpdmgr->sock); + isc_task_attach(task, &httpdmgr->task); + + ISC_LIST_INIT(httpdmgr->running); + ISC_LIST_INIT(httpdmgr->urls); + + isc_refcount_init(&httpdmgr->references, 1); + + /* XXXMLG ignore errors on isc_socket_listen() */ + result = isc_socket_listen(sock, SOMAXCONN); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_socket_listen() failed: %s", + isc_result_totext(result)); + goto cleanup; + } + + (void)isc_socket_filter(sock, "httpready"); + + httpdmgr->magic = HTTPDMGR_MAGIC; + + result = httpdmgr_socket_accept(task, httpdmgr); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + *httpdmgrp = httpdmgr; + return (ISC_R_SUCCESS); + +cleanup: + httpdmgr->magic = 0; + isc_refcount_decrementz(&httpdmgr->references); + isc_refcount_destroy(&httpdmgr->references); + isc_task_detach(&httpdmgr->task); + isc_socket_detach(&httpdmgr->sock); + isc_mem_detach(&httpdmgr->mctx); + isc_mutex_destroy(&httpdmgr->lock); + isc_mem_put(mctx, httpdmgr, sizeof(isc_httpdmgr_t)); + return (result); +} + +static void +isc_httpdmgr_attach(isc_httpdmgr_t *source, isc_httpdmgr_t **targetp) { + REQUIRE(VALID_HTTPDMGR(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + isc_refcount_increment(&source->references); + + *targetp = source; +} + +static void +isc_httpdmgr_detach(isc_httpdmgr_t **httpdmgrp) { + REQUIRE(httpdmgrp != NULL && VALID_HTTPDMGR(*httpdmgrp)); + isc_httpdmgr_t *httpdmgr = *httpdmgrp; + *httpdmgrp = NULL; + + maybe_destroy_httpdmgr(httpdmgr); +} + +static void +maybe_destroy_httpdmgr(isc_httpdmgr_t *httpdmgr) { + if (isc_refcount_decrement(&httpdmgr->references) == 1) { + destroy_httpdmgr(httpdmgr); + } +} + +static void +destroy_httpdmgr(isc_httpdmgr_t *httpdmgr) { + isc_httpdurl_t *url; + + isc_refcount_destroy(&httpdmgr->references); + + LOCK(&httpdmgr->lock); + + httpdmgr->magic = 0; + + INSIST(MSHUTTINGDOWN(httpdmgr)); + INSIST(ISC_LIST_EMPTY(httpdmgr->running)); + + isc_socket_detach(&httpdmgr->sock); + isc_task_detach(&httpdmgr->task); + httpdmgr->timermgr = NULL; + + /* + * Clear out the list of all actions we know about. Just free the + * memory. + */ + url = ISC_LIST_HEAD(httpdmgr->urls); + while (url != NULL) { + isc_mem_free(httpdmgr->mctx, url->url); + ISC_LIST_UNLINK(httpdmgr->urls, url, link); + isc_mem_put(httpdmgr->mctx, url, sizeof(isc_httpdurl_t)); + url = ISC_LIST_HEAD(httpdmgr->urls); + } + + UNLOCK(&httpdmgr->lock); + isc_mutex_destroy(&httpdmgr->lock); + + if (httpdmgr->ondestroy != NULL) { + (httpdmgr->ondestroy)(httpdmgr->cb_arg); + } + isc_mem_putanddetach(&httpdmgr->mctx, httpdmgr, sizeof(isc_httpdmgr_t)); +} + +#define LENGTHOK(s) (httpd->recvbuf - (s) < (int)httpd->recvlen) +#define BUFLENOK(s) (httpd->recvbuf - (s) < HTTP_RECVLEN) + +/* + * Look for the given header in headers. + * If value is specified look for it terminated with a character in eov. + */ +static bool +have_header(isc_httpd_t *httpd, const char *header, const char *value, + const char *eov) { + char *cr, *nl, *h; + size_t hlen, vlen = 0; + + h = httpd->headers; + hlen = strlen(header); + if (value != NULL) { + INSIST(eov != NULL); + vlen = strlen(value); + } + + for (;;) { + if (strncasecmp(h, header, hlen) != 0) { + /* + * Skip to next line; + */ + cr = strchr(h, '\r'); + if (cr != NULL && cr[1] == '\n') { + cr++; + } + nl = strchr(h, '\n'); + + /* last header? */ + h = cr; + if (h == NULL || (nl != NULL && nl < h)) { + h = nl; + } + if (h == NULL) { + return (false); + } + h++; + continue; + } + + if (value == NULL) { + return (true); + } + + /* + * Skip optional leading white space. + */ + h += hlen; + while (*h == ' ' || *h == '\t') { + h++; + } + /* + * Terminate token search on NULL or EOL. + */ + while (*h != 0 && *h != '\r' && *h != '\n') { + if (strncasecmp(h, value, vlen) == 0) { + if (strchr(eov, h[vlen]) != NULL) { + return (true); + /* + * Skip to next token. + */ + } + } + /* + * Skip to next token. + */ + h += strcspn(h, eov); + if (h[0] == '\r' && h[1] == '\n') { + h++; + } + if (h[0] != 0) { + h++; + } + } + return (false); + } +} + +static isc_result_t +process_request(isc_httpd_t *httpd, int length) { + char *s; + char *p; + int delim; + + httpd->recvlen += length; + + httpd->recvbuf[httpd->recvlen] = 0; + httpd->headers = NULL; + + /* + * If we don't find a blank line in our buffer, return that we need + * more data. + */ + s = strstr(httpd->recvbuf, "\r\n\r\n"); + delim = 2; + if (s == NULL) { + s = strstr(httpd->recvbuf, "\n\n"); + delim = 1; + } + if (s == NULL) { + return (ISC_R_NOTFOUND); + } + + /* + * NUL terminate request at the blank line. + */ + s[delim] = 0; + + /* + * Determine if this is a POST or GET method. Any other values will + * cause an error to be returned. + */ + if (strncmp(httpd->recvbuf, "GET ", 4) == 0) { + httpd->method = ISC_HTTPD_METHODGET; + p = httpd->recvbuf + 4; + } else if (strncmp(httpd->recvbuf, "POST ", 5) == 0) { + httpd->method = ISC_HTTPD_METHODPOST; + p = httpd->recvbuf + 5; + } else { + return (ISC_R_RANGE); + } + + /* + * From now on, p is the start of our buffer. + */ + + /* + * Extract the URL. + */ + s = p; + while (LENGTHOK(s) && BUFLENOK(s) && + (*s != '\n' && *s != '\r' && *s != '\0' && *s != ' ')) + { + s++; + } + if (!LENGTHOK(s)) { + return (ISC_R_NOTFOUND); + } + if (!BUFLENOK(s)) { + return (ISC_R_NOMEMORY); + } + *s = 0; + + /* + * Make the URL relative. + */ + if ((strncmp(p, "http:/", 6) == 0) || (strncmp(p, "https:/", 7) == 0)) { + /* Skip first / */ + while (*p != '/' && *p != 0) { + p++; + } + if (*p == 0) { + return (ISC_R_RANGE); + } + p++; + /* Skip second / */ + while (*p != '/' && *p != 0) { + p++; + } + if (*p == 0) { + return (ISC_R_RANGE); + } + p++; + /* Find third / */ + while (*p != '/' && *p != 0) { + p++; + } + if (*p == 0) { + p--; + *p = '/'; + } + } + + httpd->url = p; + p = s + 1; + s = p; + + /* + * Now, see if there is a ? mark in the URL. If so, this is + * part of the query string, and we will split it from the URL. + */ + httpd->querystring = strchr(httpd->url, '?'); + if (httpd->querystring != NULL) { + *(httpd->querystring) = 0; + httpd->querystring++; + } + + /* + * Extract the HTTP/1.X protocol. We will bounce on anything but + * HTTP/1.0 or HTTP/1.1 for now. + */ + while (LENGTHOK(s) && BUFLENOK(s) && + (*s != '\n' && *s != '\r' && *s != '\0')) + { + s++; + } + if (!LENGTHOK(s)) { + return (ISC_R_NOTFOUND); + } + if (!BUFLENOK(s)) { + return (ISC_R_NOMEMORY); + } + /* + * Check that we have the expected eol delimiter. + */ + if (strncmp(s, delim == 1 ? "\n" : "\r\n", delim) != 0) { + return (ISC_R_RANGE); + } + *s = 0; + if ((strncmp(p, "HTTP/1.0", 8) != 0) && + (strncmp(p, "HTTP/1.1", 8) != 0)) + { + return (ISC_R_RANGE); + } + httpd->protocol = p; + p = s + delim; /* skip past eol */ + s = p; + + httpd->headers = s; + + if (have_header(httpd, "Connection:", "close", ", \t\r\n")) { + httpd->flags |= HTTPD_CLOSE; + } + + if (have_header(httpd, "Host:", NULL, NULL)) { + httpd->flags |= HTTPD_FOUNDHOST; + } + + if (strncmp(httpd->protocol, "HTTP/1.0", 8) == 0) { + if (have_header(httpd, "Connection:", "Keep-Alive", ", \t\r\n")) + { + httpd->flags |= HTTPD_KEEPALIVE; + } else { + httpd->flags |= HTTPD_CLOSE; + } + } + + /* + * Check for Accept-Encoding: + */ +#ifdef HAVE_ZLIB + if (have_header(httpd, "Accept-Encoding:", "deflate", ";, \t\r\n")) { + httpd->flags |= HTTPD_ACCEPT_DEFLATE; + } +#endif /* ifdef HAVE_ZLIB */ + + /* + * Standards compliance hooks here. + */ + if (strcmp(httpd->protocol, "HTTP/1.1") == 0 && + ((httpd->flags & HTTPD_FOUNDHOST) == 0)) + { + return (ISC_R_RANGE); + } + + return (ISC_R_SUCCESS); +} + +static void +isc_httpd_create(isc_httpdmgr_t *httpdmgr, isc_socket_t *sock, + isc_httpd_t **httpdp) { + isc_httpd_t *httpd; + char *headerdata; + + REQUIRE(VALID_HTTPDMGR(httpdmgr)); + REQUIRE(httpdp != NULL && *httpdp == NULL); + + httpd = isc_mem_get(httpdmgr->mctx, sizeof(isc_httpd_t)); + + *httpd = (isc_httpd_t){ .sock = sock }; + + isc_httpdmgr_attach(httpdmgr, &httpd->mgr); + + isc_refcount_init(&httpd->references, 1); + ISC_HTTPD_SETRECV(httpd); + isc_socket_setname(httpd->sock, "httpd", NULL); + + /* + * Initialize the buffer for our headers. + */ + headerdata = isc_mem_get(httpdmgr->mctx, HTTP_SENDGROW); + isc_buffer_init(&httpd->headerbuffer, headerdata, HTTP_SENDGROW); + + isc_buffer_initnull(&httpd->compbuffer); + isc_buffer_initnull(&httpd->bodybuffer); + reset_client(httpd); + + ISC_LINK_INIT(httpd, link); + ISC_LIST_APPEND(httpdmgr->running, httpd, link); + + httpd->magic = HTTPD_MAGIC; + + *httpdp = httpd; +} + +static void +isc_httpd_accept(isc_task_t *task, isc_event_t *ev) { + isc_httpdmgr_t *httpdmgr = ev->ev_arg; + isc_httpd_t *httpd = NULL; + isc_region_t r; + isc_socket_newconnev_t *nev = (isc_socket_newconnev_t *)ev; + isc_sockaddr_t peeraddr; + + REQUIRE(VALID_HTTPDMGR(httpdmgr)); + + LOCK(&httpdmgr->lock); + if (MSHUTTINGDOWN(httpdmgr)) { + goto out; + } + + if (nev->result == ISC_R_CANCELED) { + goto out; + } + + if (nev->result != ISC_R_SUCCESS) { + /* XXXMLG log failure */ + goto requeue; + } + + (void)isc_socket_getpeername(nev->newsocket, &peeraddr); + if (httpdmgr->client_ok != NULL && + !(httpdmgr->client_ok)(&peeraddr, httpdmgr->cb_arg)) + { + isc_socket_detach(&nev->newsocket); + goto requeue; + } + + isc_httpd_create(httpdmgr, nev->newsocket, &httpd); + + r.base = (unsigned char *)httpd->recvbuf; + r.length = HTTP_RECVLEN - 1; + + httpd_socket_recv(httpd, &r, task); + +requeue: + (void)httpdmgr_socket_accept(task, httpdmgr); + +out: + UNLOCK(&httpdmgr->lock); + + if (httpd != NULL) { + maybe_destroy_httpd(httpd); + } + maybe_destroy_httpdmgr(httpdmgr); + + isc_event_free(&ev); +} + +static isc_result_t +render_404(const char *url, isc_httpdurl_t *urlinfo, const char *querystring, + const char *headers, void *arg, unsigned int *retcode, + const char **retmsg, const char **mimetype, isc_buffer_t *b, + isc_httpdfree_t **freecb, void **freecb_args) { + static char msg[] = "No such URL.\r\n"; + + UNUSED(url); + UNUSED(urlinfo); + UNUSED(querystring); + UNUSED(headers); + UNUSED(arg); + + *retcode = 404; + *retmsg = "No such URL"; + *mimetype = "text/plain"; + isc_buffer_reinit(b, msg, strlen(msg)); + isc_buffer_add(b, strlen(msg)); + *freecb = NULL; + *freecb_args = NULL; + + return (ISC_R_SUCCESS); +} + +static isc_result_t +render_500(const char *url, isc_httpdurl_t *urlinfo, const char *querystring, + const char *headers, void *arg, unsigned int *retcode, + const char **retmsg, const char **mimetype, isc_buffer_t *b, + isc_httpdfree_t **freecb, void **freecb_args) { + static char msg[] = "Internal server failure.\r\n"; + + UNUSED(url); + UNUSED(urlinfo); + UNUSED(querystring); + UNUSED(headers); + UNUSED(arg); + + *retcode = 500; + *retmsg = "Internal server failure"; + *mimetype = "text/plain"; + isc_buffer_reinit(b, msg, strlen(msg)); + isc_buffer_add(b, strlen(msg)); + *freecb = NULL; + *freecb_args = NULL; + + return (ISC_R_SUCCESS); +} + +#ifdef HAVE_ZLIB +/*%< + * Reallocates compbuffer to size, does nothing if compbuffer is already + * larger than size. + * + * Requires: + *\li httpd a valid isc_httpd_t object + * + * Returns: + *\li #ISC_R_SUCCESS -- all is well. + *\li #ISC_R_NOMEMORY -- not enough memory to extend buffer + */ +static isc_result_t +alloc_compspace(isc_httpd_t *httpd, unsigned int size) { + char *newspace; + isc_region_t r; + + isc_buffer_region(&httpd->compbuffer, &r); + if (size < r.length) { + return (ISC_R_SUCCESS); + } + + newspace = isc_mem_get(httpd->mgr->mctx, size); + isc_buffer_reinit(&httpd->compbuffer, newspace, size); + + if (r.base != NULL) { + isc_mem_put(httpd->mgr->mctx, r.base, r.length); + } + + return (ISC_R_SUCCESS); +} + +/*%< + * Tries to compress httpd->bodybuffer to httpd->compbuffer, extending it + * if necessary. + * + * Requires: + *\li httpd a valid isc_httpd_t object + * + * Returns: + *\li #ISC_R_SUCCESS -- all is well. + *\li #ISC_R_NOMEMORY -- not enough memory to compress data + *\li #ISC_R_FAILURE -- error during compression or compressed + * data would be larger than input data + */ +static isc_result_t +isc_httpd_compress(isc_httpd_t *httpd) { + z_stream zstr; + isc_region_t r; + isc_result_t result; + int ret; + int inputlen; + + inputlen = isc_buffer_usedlength(&httpd->bodybuffer); + result = alloc_compspace(httpd, inputlen); + if (result != ISC_R_SUCCESS) { + return (result); + } + isc_buffer_clear(&httpd->compbuffer); + isc_buffer_region(&httpd->compbuffer, &r); + + /* + * We're setting output buffer size to input size so it fails if the + * compressed data size would be bigger than the input size. + */ + memset(&zstr, 0, sizeof(zstr)); + zstr.total_in = zstr.avail_in = zstr.total_out = zstr.avail_out = + inputlen; + + zstr.next_in = isc_buffer_base(&httpd->bodybuffer); + zstr.next_out = r.base; + + ret = deflateInit(&zstr, Z_DEFAULT_COMPRESSION); + if (ret == Z_OK) { + ret = deflate(&zstr, Z_FINISH); + } + deflateEnd(&zstr); + if (ret == Z_STREAM_END) { + isc_buffer_add(&httpd->compbuffer, inputlen - zstr.avail_out); + return (ISC_R_SUCCESS); + } else { + return (ISC_R_FAILURE); + } +} +#endif /* ifdef HAVE_ZLIB */ + +static void +isc_httpd_recvdone(isc_task_t *task, isc_event_t *ev) { + isc_result_t result; + isc_httpd_t *httpd = ev->ev_arg; + isc_socketevent_t *sev = (isc_socketevent_t *)ev; + isc_buffer_t *databuffer; + isc_httpdurl_t *url; + isc_time_t now; + isc_region_t r; + bool is_compressed = false; + char datebuf[ISC_FORMATHTTPTIMESTAMP_SIZE]; + + REQUIRE(VALID_HTTPD(httpd)); + + INSIST(ISC_HTTPD_ISRECV(httpd)); + + if (sev->result != ISC_R_SUCCESS) { + goto out; + } + + result = process_request(httpd, sev->n); + if (result == ISC_R_NOTFOUND) { + if (httpd->recvlen >= HTTP_RECVLEN - 1) { + goto out; + } + r.base = (unsigned char *)httpd->recvbuf + httpd->recvlen; + r.length = HTTP_RECVLEN - httpd->recvlen - 1; + + httpd_socket_recv(httpd, &r, task); + goto out; + } else if (result != ISC_R_SUCCESS) { + goto out; + } + + ISC_HTTPD_SETSEND(httpd); + + /* + * XXXMLG Call function here. Provide an add-header function + * which will append the common headers to a response we generate. + */ + isc_buffer_initnull(&httpd->bodybuffer); + isc_time_now(&now); + isc_time_formathttptimestamp(&now, datebuf, sizeof(datebuf)); + LOCK(&httpd->mgr->lock); + url = ISC_LIST_HEAD(httpd->mgr->urls); + while (url != NULL) { + if (strcmp(httpd->url, url->url) == 0) { + break; + } + url = ISC_LIST_NEXT(url, link); + } + UNLOCK(&httpd->mgr->lock); + + if (url == NULL) { + result = httpd->mgr->render_404( + httpd->url, NULL, httpd->querystring, NULL, NULL, + &httpd->retcode, &httpd->retmsg, &httpd->mimetype, + &httpd->bodybuffer, &httpd->freecb, &httpd->freecb_arg); + } else { + result = url->action(httpd->url, url, httpd->querystring, + httpd->headers, url->action_arg, + &httpd->retcode, &httpd->retmsg, + &httpd->mimetype, &httpd->bodybuffer, + &httpd->freecb, &httpd->freecb_arg); + } + if (result != ISC_R_SUCCESS) { + result = httpd->mgr->render_500( + httpd->url, url, httpd->querystring, NULL, NULL, + &httpd->retcode, &httpd->retmsg, &httpd->mimetype, + &httpd->bodybuffer, &httpd->freecb, &httpd->freecb_arg); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } + +#ifdef HAVE_ZLIB + if ((httpd->flags & HTTPD_ACCEPT_DEFLATE) != 0) { + result = isc_httpd_compress(httpd); + if (result == ISC_R_SUCCESS) { + is_compressed = true; + } + } +#endif /* ifdef HAVE_ZLIB */ + + isc_httpd_response(httpd); + if ((httpd->flags & HTTPD_KEEPALIVE) != 0) { + isc_httpd_addheader(httpd, "Connection", "Keep-Alive"); + } + isc_httpd_addheader(httpd, "Content-Type", httpd->mimetype); + isc_httpd_addheader(httpd, "Date", datebuf); + isc_httpd_addheader(httpd, "Expires", datebuf); + + if (url != NULL && url->isstatic) { + char loadbuf[ISC_FORMATHTTPTIMESTAMP_SIZE]; + isc_time_formathttptimestamp(&url->loadtime, loadbuf, + sizeof(loadbuf)); + isc_httpd_addheader(httpd, "Last-Modified", loadbuf); + isc_httpd_addheader(httpd, "Cache-Control: public", NULL); + } else { + isc_httpd_addheader(httpd, "Last-Modified", datebuf); + isc_httpd_addheader(httpd, "Pragma: no-cache", NULL); + isc_httpd_addheader(httpd, "Cache-Control: no-cache", NULL); + } + + isc_httpd_addheader(httpd, "Server: libisc", NULL); + + if (is_compressed) { + isc_httpd_addheader(httpd, "Content-Encoding", "deflate"); + isc_httpd_addheaderuint( + httpd, "Content-Length", + isc_buffer_usedlength(&httpd->compbuffer)); + } else { + isc_httpd_addheaderuint( + httpd, "Content-Length", + isc_buffer_usedlength(&httpd->bodybuffer)); + } + + isc_httpd_endheaders(httpd); /* done */ + + /* + * Append either the compressed or the non-compressed response body to + * the response headers and store the result in httpd->sendbuffer. + */ + isc_buffer_dup(httpd->mgr->mctx, &httpd->sendbuffer, + &httpd->headerbuffer); + isc_buffer_setautorealloc(httpd->sendbuffer, true); + databuffer = (is_compressed ? &httpd->compbuffer : &httpd->bodybuffer); + isc_buffer_usedregion(databuffer, &r); + result = isc_buffer_copyregion(httpd->sendbuffer, &r); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + /* + * Determine total response size. + */ + isc_buffer_usedregion(httpd->sendbuffer, &r); + + httpd_socket_send(httpd, &r, task); + +out: + maybe_destroy_httpd(httpd); + isc_event_free(&ev); +} + +void +isc_httpdmgr_shutdown(isc_httpdmgr_t **httpdmgrp) { + isc_httpdmgr_t *httpdmgr; + isc_httpd_t *httpd; + + REQUIRE(httpdmgrp != NULL); + httpdmgr = *httpdmgrp; + *httpdmgrp = NULL; + REQUIRE(VALID_HTTPDMGR(httpdmgr)); + + LOCK(&httpdmgr->lock); + + MSETSHUTTINGDOWN(httpdmgr); + + isc_socket_cancel(httpdmgr->sock, httpdmgr->task, ISC_SOCKCANCEL_ALL); + + httpd = ISC_LIST_HEAD(httpdmgr->running); + while (httpd != NULL) { + isc_socket_cancel(httpd->sock, httpdmgr->task, + ISC_SOCKCANCEL_ALL); + httpd = ISC_LIST_NEXT(httpd, link); + } + + UNLOCK(&httpdmgr->lock); + + maybe_destroy_httpdmgr(httpdmgr); +} + +static isc_result_t +grow_headerspace(isc_httpd_t *httpd) { + char *newspace; + unsigned int newlen; + isc_region_t r; + + isc_buffer_region(&httpd->headerbuffer, &r); + newlen = r.length + HTTP_SENDGROW; + if (newlen > HTTP_SEND_MAXLEN) { + return (ISC_R_NOSPACE); + } + + newspace = isc_mem_get(httpd->mgr->mctx, newlen); + + isc_buffer_reinit(&httpd->headerbuffer, newspace, newlen); + + isc_mem_put(httpd->mgr->mctx, r.base, r.length); + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_httpd_response(isc_httpd_t *httpd) { + isc_result_t result; + unsigned int needlen; + + REQUIRE(VALID_HTTPD(httpd)); + + needlen = strlen(httpd->protocol) + 1; /* protocol + space */ + needlen += 3 + 1; /* room for response code, always 3 bytes */ + needlen += strlen(httpd->retmsg) + 2; /* return msg + CRLF */ + + while (isc_buffer_availablelength(&httpd->headerbuffer) < needlen) { + result = grow_headerspace(httpd); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + + return (isc_buffer_printf(&httpd->headerbuffer, "%s %03u %s\r\n", + httpd->protocol, httpd->retcode, + httpd->retmsg)); +} + +isc_result_t +isc_httpd_addheader(isc_httpd_t *httpd, const char *name, const char *val) { + isc_result_t result; + unsigned int needlen; + + REQUIRE(VALID_HTTPD(httpd)); + + needlen = strlen(name); /* name itself */ + if (val != NULL) { + needlen += 2 + strlen(val); /* :<space> and val */ + } + needlen += 2; /* CRLF */ + + while (isc_buffer_availablelength(&httpd->headerbuffer) < needlen) { + result = grow_headerspace(httpd); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + + if (val != NULL) { + return (isc_buffer_printf(&httpd->headerbuffer, "%s: %s\r\n", + name, val)); + } else { + return (isc_buffer_printf(&httpd->headerbuffer, "%s\r\n", + name)); + } +} + +isc_result_t +isc_httpd_endheaders(isc_httpd_t *httpd) { + isc_result_t result; + + REQUIRE(VALID_HTTPD(httpd)); + + while (isc_buffer_availablelength(&httpd->headerbuffer) < 2) { + result = grow_headerspace(httpd); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + + return (isc_buffer_printf(&httpd->headerbuffer, "\r\n")); +} + +isc_result_t +isc_httpd_addheaderuint(isc_httpd_t *httpd, const char *name, int val) { + isc_result_t result; + unsigned int needlen; + char buf[sizeof "18446744073709551616"]; + + REQUIRE(VALID_HTTPD(httpd)); + + snprintf(buf, sizeof(buf), "%d", val); + + needlen = strlen(name); /* name itself */ + needlen += 2 + strlen(buf); /* :<space> and val */ + needlen += 2; /* CRLF */ + + while (isc_buffer_availablelength(&httpd->headerbuffer) < needlen) { + result = grow_headerspace(httpd); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + + return (isc_buffer_printf(&httpd->headerbuffer, "%s: %s\r\n", name, + buf)); +} + +static void +isc_httpd_senddone(isc_task_t *task, isc_event_t *ev) { + isc_httpd_t *httpd = ev->ev_arg; + isc_region_t r; + isc_socketevent_t *sev = (isc_socketevent_t *)ev; + + REQUIRE(VALID_HTTPD(httpd)); + + INSIST(ISC_HTTPD_ISSEND(httpd)); + + isc_buffer_free(&httpd->sendbuffer); + + /* + * We will always want to clean up our receive buffer, even if we + * got an error on send or we are shutting down. + * + * We will pass in the buffer only if there is data in it. If + * there is no data, we will pass in a NULL. + */ + if (httpd->freecb != NULL) { + isc_buffer_t *b = NULL; + if (isc_buffer_length(&httpd->bodybuffer) > 0) { + b = &httpd->bodybuffer; + httpd->freecb(b, httpd->freecb_arg); + } + } + + if (sev->result != ISC_R_SUCCESS) { + goto out; + } + + if ((httpd->flags & HTTPD_CLOSE) != 0) { + goto out; + } + + ISC_HTTPD_SETRECV(httpd); + + reset_client(httpd); + + r.base = (unsigned char *)httpd->recvbuf; + r.length = HTTP_RECVLEN - 1; + + httpd_socket_recv(httpd, &r, task); + +out: + maybe_destroy_httpd(httpd); + isc_event_free(&ev); +} + +static void +reset_client(isc_httpd_t *httpd) { + /* + * Catch errors here. We MUST be in RECV mode, and we MUST NOT have + * any outstanding buffers. If we have buffers, we have a leak. + */ + INSIST(ISC_HTTPD_ISRECV(httpd)); + INSIST(!ISC_LINK_LINKED(&httpd->headerbuffer, link)); + INSIST(!ISC_LINK_LINKED(&httpd->bodybuffer, link)); + + httpd->recvbuf[0] = 0; + httpd->recvlen = 0; + httpd->headers = NULL; + httpd->method = ISC_HTTPD_METHODUNKNOWN; + httpd->url = NULL; + httpd->querystring = NULL; + httpd->protocol = NULL; + httpd->flags = 0; + + isc_buffer_clear(&httpd->headerbuffer); + isc_buffer_clear(&httpd->compbuffer); + isc_buffer_invalidate(&httpd->bodybuffer); +} + +isc_result_t +isc_httpdmgr_addurl(isc_httpdmgr_t *httpdmgr, const char *url, + isc_httpdaction_t *func, void *arg) { + /* REQUIRE(VALID_HTTPDMGR(httpdmgr)); Dummy function */ + + return (isc_httpdmgr_addurl2(httpdmgr, url, false, func, arg)); +} + +isc_result_t +isc_httpdmgr_addurl2(isc_httpdmgr_t *httpdmgr, const char *url, bool isstatic, + isc_httpdaction_t *func, void *arg) { + isc_httpdurl_t *item; + + REQUIRE(VALID_HTTPDMGR(httpdmgr)); + + if (url == NULL) { + httpdmgr->render_404 = func; + return (ISC_R_SUCCESS); + } + + item = isc_mem_get(httpdmgr->mctx, sizeof(isc_httpdurl_t)); + + item->url = isc_mem_strdup(httpdmgr->mctx, url); + + item->action = func; + item->action_arg = arg; + item->isstatic = isstatic; + isc_time_now(&item->loadtime); + + ISC_LINK_INIT(item, link); + + LOCK(&httpdmgr->lock); + ISC_LIST_APPEND(httpdmgr->urls, item, link); + UNLOCK(&httpdmgr->lock); + + return (ISC_R_SUCCESS); +} + +void +isc_httpd_setfinishhook(void (*fn)(void)) { +#if ENABLE_AFL + finishhook = fn; +#else /* ENABLE_AFL */ + UNUSED(fn); +#endif /* ENABLE_AFL */ +} diff --git a/lib/isc/include/.clang-format b/lib/isc/include/.clang-format new file mode 120000 index 0000000..0e62f72 --- /dev/null +++ b/lib/isc/include/.clang-format @@ -0,0 +1 @@ +../../../.clang-format.headers
\ No newline at end of file diff --git a/lib/isc/include/Makefile.in b/lib/isc/include/Makefile.in new file mode 100644 index 0000000..b052e73 --- /dev/null +++ b/lib/isc/include/Makefile.in @@ -0,0 +1,19 @@ +# 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. + +srcdir = @srcdir@ +VPATH = @srcdir@ +top_srcdir = @top_srcdir@ + +SUBDIRS = isc pk11 pkcs11 +TARGETS = + +@BIND9_MAKE_RULES@ diff --git a/lib/isc/include/isc/Makefile.in b/lib/isc/include/isc/Makefile.in new file mode 100644 index 0000000..a42c9e0 --- /dev/null +++ b/lib/isc/include/isc/Makefile.in @@ -0,0 +1,63 @@ +# 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. + +srcdir = @srcdir@ +VPATH = @srcdir@ +top_srcdir = @top_srcdir@ + +VERSION=@BIND9_VERSION@ + +# +# Only list headers that are to be installed and are not +# machine generated. The latter are handled specially in the +# install target below. +# +HEADERS = aes.h app.h assertions.h astack.h atomic.h backtrace.h \ + barrier.h base32.h base64.h bind9.h buffer.h bufferlist.h \ + cmocka.h commandline.h counter.h crc64.h deprecated.h \ + endian.h errno.h error.h event.h eventclass.h \ + file.h formatcheck.h fsaccess.h fuzz.h \ + hash.h heap.h hex.h hmac.h ht.h httpd.h \ + interfaceiter.h iterated_hash.h \ + lang.h lex.h lfsr.h lib.h likely.h list.h log.h \ + magic.h managers.h md.h mem.h meminfo.h \ + mutexblock.h \ + netaddr.h netmgr.h netscope.h nonce.h os.h parseint.h \ + pool.h portset.h print.h quota.h \ + radix.h random.h ratelimiter.h refcount.h regex.h \ + region.h resource.h result.h resultclass.h rwlock.h \ + safe.h serial.h siphash.h sockaddr.h socket.h \ + stats.h stdio.h strerr.h string.h symtab.h \ + task.h taskpool.h timer.h tm.h types.h \ + url.h utf8.h util.h version.h + +SUBDIRS = +TARGETS = + +@BIND9_MAKE_RULES@ + +installdirs: + $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${includedir}/isc + +install:: installdirs + for i in ${HEADERS}; do \ + ${INSTALL_DATA} ${srcdir}/$$i ${DESTDIR}${includedir}/isc || exit 1; \ + done + ${INSTALL_DATA} platform.h ${DESTDIR}${includedir}/isc + +uninstall:: + rm -f ${DESTDIR}${includedir}/isc/platform.h + for i in ${HEADERS}; do \ + rm -f ${DESTDIR}${includedir}/isc/$$i || exit 1; \ + done + +distclean:: + rm -f platform.h diff --git a/lib/isc/include/isc/aes.h b/lib/isc/include/isc/aes.h new file mode 100644 index 0000000..00449b5 --- /dev/null +++ b/lib/isc/include/isc/aes.h @@ -0,0 +1,44 @@ +/* + * 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. + */ + +/*! \file isc/aes.h */ + +#ifndef ISC_AES_H +#define ISC_AES_H 1 + +#include <isc/lang.h> +#include <isc/platform.h> +#include <isc/types.h> + +#define ISC_AES128_KEYLENGTH 16U +#define ISC_AES192_KEYLENGTH 24U +#define ISC_AES256_KEYLENGTH 32U +#define ISC_AES_BLOCK_LENGTH 16U + +ISC_LANG_BEGINDECLS + +void +isc_aes128_crypt(const unsigned char *key, const unsigned char *in, + unsigned char *out); + +void +isc_aes192_crypt(const unsigned char *key, const unsigned char *in, + unsigned char *out); + +void +isc_aes256_crypt(const unsigned char *key, const unsigned char *in, + unsigned char *out); + +ISC_LANG_ENDDECLS + +#endif /* ISC_AES_H */ diff --git a/lib/isc/include/isc/app.h b/lib/isc/include/isc/app.h new file mode 100644 index 0000000..9e8140b --- /dev/null +++ b/lib/isc/include/isc/app.h @@ -0,0 +1,283 @@ +/* + * 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. + */ + +#ifndef ISC_APP_H +#define ISC_APP_H 1 + +/***** +***** Module Info +*****/ + +/*! \file isc/app.h + * \brief ISC Application Support + * + * Dealing with program termination can be difficult, especially in a + * multithreaded program. The routines in this module help coordinate + * the shutdown process. They are used as follows by the initial (main) + * thread of the application: + * + *\li isc_app_start(); Call very early in main(), before + * any other threads have been created. + * + *\li isc_app_run(); This will post any on-run events, + * and then block until application + * shutdown is requested. A shutdown + * request is made by calling + * isc_app_shutdown(), or by sending + * SIGINT or SIGTERM to the process. + * After isc_app_run() returns, the + * application should shutdown itself. + * + *\li isc_app_finish(); Call very late in main(). + * + * Applications that want to use SIGHUP/isc_app_reload() to trigger reloading + * should check the result of isc_app_run() and call the reload routine if + * the result is ISC_R_RELOAD. They should then call isc_app_run() again + * to resume waiting for reload or termination. + * + * Use of this module is not required. In particular, isc_app_start() is + * NOT an ISC library initialization routine. + * + * This module also supports per-thread 'application contexts'. With this + * mode, a thread-based application will have a separate context, in which + * it uses other ISC library services such as tasks or timers. Signals are + * not caught in this mode, so that the application can handle the signals + * in its preferred way. + * + * \li MP: + * Clients must ensure that isc_app_start(), isc_app_run(), and + * isc_app_finish() are called at most once. isc_app_shutdown() + * is safe to use by any thread (provided isc_app_start() has been + * called previously). + * + * The same note applies to isc_app_ctxXXX() functions, but in this case + * it's a per-thread restriction. For example, a thread with an + * application context must ensure that isc_app_ctxstart() with the + * context is called at most once. + * + * \li Reliability: + * No anticipated impact. + * + * \li Resources: + * None. + * + * \li Security: + * No anticipated impact. + * + * \li Standards: + * None. + */ + +#include <stdbool.h> + +#include <isc/eventclass.h> +#include <isc/lang.h> +#include <isc/magic.h> +#include <isc/result.h> + +/*** + *** Types + ***/ + +typedef isc_event_t isc_appevent_t; + +#define ISC_APPEVENT_FIRSTEVENT (ISC_EVENTCLASS_APP + 0) +#define ISC_APPEVENT_SHUTDOWN (ISC_EVENTCLASS_APP + 1) +#define ISC_APPEVENT_LASTEVENT (ISC_EVENTCLASS_APP + 65535) + +ISC_LANG_BEGINDECLS + +isc_result_t +isc_app_ctxstart(isc_appctx_t *ctx); + +isc_result_t +isc_app_start(void); +/*!< + * \brief Start an ISC library application. + * + * Notes: + * This call should be made before any other ISC library call, and as + * close to the beginning of the application as possible. + * + * Requires: + *\li 'ctx' is a valid application context (for app_ctxstart()). + */ + +isc_result_t +isc_app_ctxonrun(isc_appctx_t *ctx, isc_mem_t *mctx, isc_task_t *task, + isc_taskaction_t action, void *arg); +isc_result_t +isc_app_onrun(isc_mem_t *mctx, isc_task_t *task, isc_taskaction_t action, + void *arg); +/*!< + * \brief Request delivery of an event when the application is run. + * + * Requires: + *\li isc_app_start() has been called. + *\li 'ctx' is a valid application context (for app_ctxonrun()). + * + * Returns: + * ISC_R_SUCCESS + * ISC_R_NOMEMORY + */ + +isc_result_t +isc_app_ctxrun(isc_appctx_t *ctx); + +isc_result_t +isc_app_run(void); +/*!< + * \brief Run an ISC library application. + * + * Notes: + *\li The caller (typically the initial thread of an application) will + * block until shutdown is requested. When the call returns, the + * caller should start shutting down the application. + * + * Requires: + *\li isc_app_[ctx]start() has been called. + * + * Ensures: + *\li Any events requested via isc_app_onrun() will have been posted (in + * FIFO order) before isc_app_run() blocks. + *\li 'ctx' is a valid application context (for app_ctxrun()). + * + * Returns: + *\li ISC_R_SUCCESS Shutdown has been requested. + *\li ISC_R_RELOAD Reload has been requested. + */ + +bool +isc_app_isrunning(void); +/*!< + * \brief Return if the ISC library application is running. + * + * Returns: + *\li true App is running. + *\li false App is not running. + */ + +void +isc_app_ctxshutdown(isc_appctx_t *ctx); + +void +isc_app_shutdown(void); +/*!< + * \brief Request application shutdown. + * + * Notes: + *\li It is safe to call isc_app_shutdown() multiple times. Shutdown will + * only be triggered once. + * + * Requires: + *\li isc_app_[ctx]run() has been called. + *\li 'ctx' is a valid application context (for app_ctxshutdown()). + * + * Returns: + *\li ISC_R_SUCCESS + *\li ISC_R_UNEXPECTED + */ + +void +isc_app_ctxsuspend(isc_appctx_t *ctx); +/*!< + * \brief This has the same behavior as isc_app_ctxsuspend(). + */ + +void +isc_app_reload(void); +/*!< + * \brief Request application reload. + * + * Requires: + *\li isc_app_run() has been called. + * + * Returns: + *\li ISC_R_SUCCESS + *\li ISC_R_UNEXPECTED + */ + +void +isc_app_ctxfinish(isc_appctx_t *ctx); + +void +isc_app_finish(void); +/*!< + * \brief Finish an ISC library application. + * + * Notes: + *\li This call should be made at or near the end of main(). + * + * Requires: + *\li isc_app_start() has been called. + *\li 'ctx' is a valid application context (for app_ctxfinish()). + * + * Ensures: + *\li Any resources allocated by isc_app_start() have been released. + */ + +void +isc_app_block(void); +/*!< + * \brief Indicate that a blocking operation will be performed. + * + * Notes: + *\li If a blocking operation is in process, a call to isc_app_shutdown() + * or an external signal will abort the program, rather than allowing + * clean shutdown. This is primarily useful for reading user input. + * + * Requires: + * \li isc_app_start() has been called. + * \li No other blocking operations are in progress. + */ + +void +isc_app_unblock(void); +/*!< + * \brief Indicate that a blocking operation is complete. + * + * Notes: + * \li When a blocking operation has completed, return the program to a + * state where a call to isc_app_shutdown() or an external signal will + * shutdown normally. + * + * Requires: + * \li isc_app_start() has been called. + * \li isc_app_block() has been called by the same thread. + */ + +isc_result_t +isc_appctx_create(isc_mem_t *mctx, isc_appctx_t **ctxp); +/*!< + * \brief Create an application context. + * + * Requires: + *\li 'mctx' is a valid memory context. + *\li 'ctxp' != NULL && *ctxp == NULL. + */ + +void +isc_appctx_destroy(isc_appctx_t **ctxp); +/*!< + * \brief Destroy an application context. + * + * Requires: + *\li '*ctxp' is a valid application context. + * + * Ensures: + *\li *ctxp == NULL. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_APP_H */ diff --git a/lib/isc/include/isc/assertions.h b/lib/isc/include/isc/assertions.h new file mode 100644 index 0000000..e68adfa --- /dev/null +++ b/lib/isc/include/isc/assertions.h @@ -0,0 +1,79 @@ +/* + * 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. + */ + +/*! \file isc/assertions.h + */ + +#ifndef ISC_ASSERTIONS_H +#define ISC_ASSERTIONS_H 1 + +#include <isc/lang.h> +#include <isc/likely.h> +#include <isc/platform.h> + +ISC_LANG_BEGINDECLS + +/*% isc assertion type */ +typedef enum { + isc_assertiontype_require, + isc_assertiontype_ensure, + isc_assertiontype_insist, + isc_assertiontype_invariant +} isc_assertiontype_t; + +typedef void (*isc_assertioncallback_t)(const char *, int, isc_assertiontype_t, + const char *); + +/* coverity[+kill] */ +ISC_PLATFORM_NORETURN_PRE +void +isc_assertion_failed(const char *, int, isc_assertiontype_t, + const char *) ISC_PLATFORM_NORETURN_POST; + +void isc_assertion_setcallback(isc_assertioncallback_t); + +const char * +isc_assertion_typetotext(isc_assertiontype_t type); + +#define ISC_REQUIRE(cond) \ + ((void)(ISC_LIKELY(cond) || \ + ((isc_assertion_failed)(__FILE__, __LINE__, \ + isc_assertiontype_require, #cond), \ + 0))) + +#define ISC_ENSURE(cond) \ + ((void)(ISC_LIKELY(cond) || \ + ((isc_assertion_failed)(__FILE__, __LINE__, \ + isc_assertiontype_ensure, #cond), \ + 0))) + +#define ISC_INSIST(cond) \ + ((void)(ISC_LIKELY(cond) || \ + ((isc_assertion_failed)(__FILE__, __LINE__, \ + isc_assertiontype_insist, #cond), \ + 0))) + +#define ISC_INVARIANT(cond) \ + ((void)(ISC_LIKELY(cond) || \ + ((isc_assertion_failed)(__FILE__, __LINE__, \ + isc_assertiontype_invariant, #cond), \ + 0))) + +#define ISC_UNREACHABLE() \ + (isc_assertion_failed(__FILE__, __LINE__, isc_assertiontype_insist, \ + "unreachable"), \ + __builtin_unreachable()) + +ISC_LANG_ENDDECLS + +#endif /* ISC_ASSERTIONS_H */ diff --git a/lib/isc/include/isc/astack.h b/lib/isc/include/isc/astack.h new file mode 100644 index 0000000..a4f6762 --- /dev/null +++ b/lib/isc/include/isc/astack.h @@ -0,0 +1,49 @@ +/* + * 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. + */ + +#pragma once + +#include <inttypes.h> + +#include <isc/mem.h> +#include <isc/types.h> + +isc_astack_t * +isc_astack_new(isc_mem_t *mctx, size_t size); +/*%< + * Allocate and initialize a new array stack of size 'size'. + */ + +void +isc_astack_destroy(isc_astack_t *stack); +/*%< + * Free an array stack 'stack'. + * + * Requires: + * \li 'stack' is empty. + */ + +bool +isc_astack_trypush(isc_astack_t *stack, void *obj); +/*%< + * Try to push 'obj' onto array stack 'astack'. On failure, either + * because the stack size limit has been reached or because another + * thread has already changed the stack pointer, return 'false'. + */ + +void * +isc_astack_pop(isc_astack_t *stack); +/*%< + * Pop an object off of array stack 'stack'. If the stack is empty, + * return NULL. + */ diff --git a/lib/isc/include/isc/atomic.h b/lib/isc/include/isc/atomic.h new file mode 100644 index 0000000..fd29202 --- /dev/null +++ b/lib/isc/include/isc/atomic.h @@ -0,0 +1,73 @@ +/* + * 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. + */ + +#pragma once + +#if HAVE_STDATOMIC_H +#include <stdatomic.h> +#else /* if HAVE_STDATOMIC_H */ +#include <isc/stdatomic.h> +#endif /* if HAVE_STDATOMIC_H */ + +/* + * We define a few additional macros to make things easier + */ + +/* Relaxed Memory Ordering */ + +#define atomic_store_relaxed(o, v) \ + atomic_store_explicit((o), (v), memory_order_relaxed) +#define atomic_load_relaxed(o) atomic_load_explicit((o), memory_order_relaxed) +#define atomic_fetch_add_relaxed(o, v) \ + atomic_fetch_add_explicit((o), (v), memory_order_relaxed) +#define atomic_fetch_sub_relaxed(o, v) \ + atomic_fetch_sub_explicit((o), (v), memory_order_relaxed) +#define atomic_fetch_or_relaxed(o, v) \ + atomic_fetch_or_explicit((o), (v), memory_order_relaxed) +#define atomic_fetch_and_relaxed(o, v) \ + atomic_fetch_and_explicit((o), (v), memory_order_relaxed) +#define atomic_exchange_relaxed(o, v) \ + atomic_exchange_explicit((o), (v), memory_order_relaxed) +#define atomic_compare_exchange_weak_relaxed(o, e, d) \ + atomic_compare_exchange_weak_explicit( \ + (o), (e), (d), memory_order_relaxed, memory_order_relaxed) +#define atomic_compare_exchange_strong_relaxed(o, e, d) \ + atomic_compare_exchange_strong_explicit( \ + (o), (e), (d), memory_order_relaxed, memory_order_relaxed) +#define atomic_compare_exchange_strong_acq_rel(o, e, d) \ + atomic_compare_exchange_strong_explicit( \ + (o), (e), (d), memory_order_acq_rel, memory_order_acquire) + +/* Acquire-Release Memory Ordering */ + +#define atomic_store_release(o, v) \ + atomic_store_explicit((o), (v), memory_order_release) +#define atomic_load_acquire(o) atomic_load_explicit((o), memory_order_acquire) +#define atomic_fetch_add_release(o, v) \ + atomic_fetch_add_explicit((o), (v), memory_order_release) +#define atomic_fetch_sub_release(o, v) \ + atomic_fetch_sub_explicit((o), (v), memory_order_release) +#define atomic_fetch_and_release(o, v) \ + atomic_fetch_and_explicit((o), (v), memory_order_release) +#define atomic_fetch_or_release(o, v) \ + atomic_fetch_or_explicit((o), (v), memory_order_release) +#define atomic_exchange_acq_rel(o, v) \ + atomic_exchange_explicit((o), (v), memory_order_acq_rel) +#define atomic_fetch_sub_acq_rel(o, v) \ + atomic_fetch_sub_explicit((o), (v), memory_order_acq_rel) +#define atomic_compare_exchange_weak_acq_rel(o, e, d) \ + atomic_compare_exchange_weak_explicit( \ + (o), (e), (d), memory_order_acq_rel, memory_order_acquire) +#define atomic_compare_exchange_strong_acq_rel(o, e, d) \ + atomic_compare_exchange_strong_explicit( \ + (o), (e), (d), memory_order_acq_rel, memory_order_acquire) diff --git a/lib/isc/include/isc/backtrace.h b/lib/isc/include/isc/backtrace.h new file mode 100644 index 0000000..d32748a --- /dev/null +++ b/lib/isc/include/isc/backtrace.h @@ -0,0 +1,127 @@ +/* + * 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. + */ + +/*! \file isc/backtrace.h + * \brief provide a back trace of the running process to help debug problems. + * + * This module tries to get a back trace of the process using some platform + * dependent way when available. It also manages an internal symbol table + * that maps function addresses used in the process to their textual symbols. + * This module is expected to be used to help debug when some fatal error + * happens. + * + * IMPORTANT NOTE: since the (major) intended use case of this module is + * dumping a back trace on a fatal error, normally followed by self termination, + * functions defined in this module generally doesn't employ assertion checks + * (if it did, a program bug could cause infinite recursive calls to a + * backtrace function). These functions still perform minimal checks and return + * ISC_R_FAILURE if they detect an error, but the caller should therefore be + * very careful about the use of these functions, and generally discouraged to + * use them except in an exit path. The exception is + * isc_backtrace_getsymbolfromindex(), which is expected to be used in a + * non-error-handling context and validates arguments with assertion checks. + */ + +#ifndef ISC_BACKTRACE_H +#define ISC_BACKTRACE_H 1 + +/*** + *** Imports + ***/ + +#include <isc/types.h> + +/*** + *** Types + ***/ +struct isc_backtrace_symmap { + void *addr; + const char *symbol; +}; + +LIBISC_EXTERNAL_DATA extern const int isc__backtrace_nsymbols; +LIBISC_EXTERNAL_DATA extern const isc_backtrace_symmap_t + isc__backtrace_symtable[]; + +/*** + *** Functions + ***/ + +ISC_LANG_BEGINDECLS +isc_result_t +isc_backtrace_gettrace(void **addrs, int maxaddrs, int *nframes); +/*%< + * Get a back trace of the running process above this function itself. On + * success, addrs[i] will store the address of the call point of the i-th + * stack frame (addrs[0] is the caller of this function). *nframes will store + * the total number of frames. + * + * Requires (note that these are not ensured by assertion checks, see above): + * + *\li 'addrs' is a valid array containing at least 'maxaddrs' void * entries. + * + *\li 'nframes' must be non NULL. + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_FAILURE + *\li #ISC_R_NOTFOUND + *\li #ISC_R_NOTIMPLEMENTED + */ + +isc_result_t +isc_backtrace_getsymbolfromindex(int index, const void **addrp, + const char **symbolp); +/*%< + * Returns the content of the internal symbol table of the given index. + * On success, *addrsp and *symbolp point to the address and the symbol of + * the 'index'th entry of the table, respectively. If 'index' is not in the + * range of the symbol table, ISC_R_RANGE will be returned. + * + * Requires + * + *\li 'addrp' must be non NULL && '*addrp' == NULL. + * + *\li 'symbolp' must be non NULL && '*symbolp' == NULL. + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_RANGE + */ + +isc_result_t +isc_backtrace_getsymbol(const void *addr, const char **symbolp, + unsigned long *offsetp); +/*%< + * Searches the internal symbol table for the symbol that most matches the + * given 'addr'. On success, '*symbolp' will point to the name of function + * to which the address 'addr' belong, and '*offsetp' will store the offset + * from the function's entry address to 'addr'. + * + * Requires (note that these are not ensured by assertion checks, see above): + * + *\li 'symbolp' must be non NULL && '*symbolp' == NULL. + * + *\li 'offsetp' must be non NULL. + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_FAILURE + *\li #ISC_R_NOTFOUND + */ +ISC_LANG_ENDDECLS + +#endif /* ISC_BACKTRACE_H */ diff --git a/lib/isc/include/isc/barrier.h b/lib/isc/include/isc/barrier.h new file mode 100644 index 0000000..1a2a90b --- /dev/null +++ b/lib/isc/include/isc/barrier.h @@ -0,0 +1,39 @@ +/* + * 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. + */ + +#pragma once + +#include <isc/util.h> + +#if __SANITIZE_THREAD__ && !defined(WIN32) + +#include <pthread.h> + +#define isc_barrier_t pthread_barrier_t + +#define isc_barrier_init(barrier, count) \ + pthread_barrier_init(barrier, NULL, count) +#define isc_barrier_destroy(barrier) pthread_barrier_destroy(barrier) +#define isc_barrier_wait(barrier) pthread_barrier_wait(barrier) + +#else + +#include <uv.h> + +#define isc_barrier_t uv_barrier_t + +#define isc_barrier_init(barrier, count) uv_barrier_init(barrier, count) +#define isc_barrier_destroy(barrier) uv_barrier_destroy(barrier) +#define isc_barrier_wait(barrier) uv_barrier_wait(barrier) + +#endif /* __SANITIZE_THREAD__ */ diff --git a/lib/isc/include/isc/base32.h b/lib/isc/include/isc/base32.h new file mode 100644 index 0000000..befe047 --- /dev/null +++ b/lib/isc/include/isc/base32.h @@ -0,0 +1,146 @@ +/* + * 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. + */ + +#ifndef ISC_BASE32_H +#define ISC_BASE32_H 1 + +/*! \file */ + +/* + * Routines for manipulating base 32 and base 32 hex encoded data. + * Based on RFC 4648. + * + * Base 32 hex preserves the sort order of data when it is encoded / + * decoded. + * + * Base 32 hex "np" is base 32 hex but no padding is produced or accepted. + */ + +#include <isc/lang.h> +#include <isc/types.h> + +ISC_LANG_BEGINDECLS + +/*** + *** Functions + ***/ + +isc_result_t +isc_base32_totext(isc_region_t *source, int wordlength, const char *wordbreak, + isc_buffer_t *target); +isc_result_t +isc_base32hex_totext(isc_region_t *source, int wordlength, + const char *wordbreak, isc_buffer_t *target); +isc_result_t +isc_base32hexnp_totext(isc_region_t *source, int wordlength, + const char *wordbreak, isc_buffer_t *target); +/*!< + * \brief Convert data into base32 encoded text. + * + * Notes: + *\li The base32 encoded text in 'target' will be divided into + * words of at most 'wordlength' characters, separated by + * the 'wordbreak' string. No parentheses will surround + * the text. + * + * Requires: + *\li 'source' is a region containing binary data + *\li 'target' is a text buffer containing available space + *\li 'wordbreak' points to a null-terminated string of + * zero or more whitespace characters + * + * Ensures: + *\li target will contain the base32 encoded version of the data + * in source. The 'used' pointer in target will be advanced as + * necessary. + */ + +isc_result_t +isc_base32_decodestring(const char *cstr, isc_buffer_t *target); +isc_result_t +isc_base32hex_decodestring(const char *cstr, isc_buffer_t *target); +isc_result_t +isc_base32hexnp_decodestring(const char *cstr, isc_buffer_t *target); +/*!< + * \brief Decode a null-terminated string in base32, base32hex, or + * base32hex non-padded. + * + * Requires: + *\li 'cstr' is non-null. + *\li 'target' is a valid buffer. + * + * Returns: + *\li #ISC_R_SUCCESS -- the entire decoded representation of 'cstring' + * fit in 'target'. + *\li #ISC_R_BADBASE32 -- 'cstr' is not a valid base32 encoding. + * + * Other error returns are any possible error code from: + *\li isc_lex_create(), + *\li isc_lex_openbuffer(), + *\li isc_base32_tobuffer(). + */ + +isc_result_t +isc_base32_tobuffer(isc_lex_t *lexer, isc_buffer_t *target, int length); +isc_result_t +isc_base32hex_tobuffer(isc_lex_t *lexer, isc_buffer_t *target, int length); +isc_result_t +isc_base32hexnp_tobuffer(isc_lex_t *lexer, isc_buffer_t *target, int length); +/*!< + * \brief Convert text encoded in base32, base32hex, or base32hex + * non-padded from a lexer context into `target`. If 'length' is + * non-negative, it is the expected number of encoded octets to convert. + * + * If 'length' is -1 then 0 or more encoded octets are expected. + * If 'length' is -2 then 1 or more encoded octets are expected. + * + * Returns: + *\li #ISC_R_BADBASE32 -- invalid base32 encoding. + *\li #ISC_R_UNEXPECTEDEND: the text does not contain the expected + * number of encoded octets. + * + * Requires: + *\li 'lexer' is a valid lexer context + *\li 'target' is a buffer containing binary data + *\li 'length' is -2, -1, or non-negative + * + * Ensures: + *\li target will contain the data represented by the base32 encoded + * string parsed by the lexer. No more than `length` octets will + * be read, if `length` is non-negative. The 'used' pointer in + * 'target' will be advanced as necessary. + */ + +isc_result_t +isc_base32_decoderegion(isc_region_t *source, isc_buffer_t *target); +isc_result_t +isc_base32hex_decoderegion(isc_region_t *source, isc_buffer_t *target); +isc_result_t +isc_base32hexnp_decoderegion(isc_region_t *source, isc_buffer_t *target); +/*!< + * \brief Decode a packed (no white space permitted) region in + * base32, base32hex or base32hex non-padded. + * + * Requires: + *\li 'source' is a valid region. + *\li 'target' is a valid buffer. + * + * Returns: + *\li #ISC_R_SUCCESS -- the entire decoded representation of 'cstring' + * fit in 'target'. + *\li #ISC_R_BADBASE32 -- 'source' is not a valid base32 encoding. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_BASE32_H */ diff --git a/lib/isc/include/isc/base64.h b/lib/isc/include/isc/base64.h new file mode 100644 index 0000000..057eabb --- /dev/null +++ b/lib/isc/include/isc/base64.h @@ -0,0 +1,101 @@ +/* + * 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. + */ + +#ifndef ISC_BASE64_H +#define ISC_BASE64_H 1 + +/*! \file isc/base64.h */ + +#include <isc/lang.h> +#include <isc/types.h> + +ISC_LANG_BEGINDECLS + +/*** + *** Functions + ***/ + +isc_result_t +isc_base64_totext(isc_region_t *source, int wordlength, const char *wordbreak, + isc_buffer_t *target); +/*!< + * \brief Convert data into base64 encoded text. + * + * Notes: + *\li The base64 encoded text in 'target' will be divided into + * words of at most 'wordlength' characters, separated by + * the 'wordbreak' string. No parentheses will surround + * the text. + * + * Requires: + *\li 'source' is a region containing binary data + *\li 'target' is a text buffer containing available space + *\li 'wordbreak' points to a null-terminated string of + * zero or more whitespace characters + * + * Ensures: + *\li target will contain the base64 encoded version of the data + * in source. The 'used' pointer in target will be advanced as + * necessary. + */ + +isc_result_t +isc_base64_decodestring(const char *cstr, isc_buffer_t *target); +/*!< + * \brief Decode a null-terminated base64 string. + * + * Requires: + *\li 'cstr' is non-null. + *\li 'target' is a valid buffer. + * + * Returns: + *\li #ISC_R_SUCCESS -- the entire decoded representation of 'cstring' + * fit in 'target'. + *\li #ISC_R_BADBASE64 -- 'cstr' is not a valid base64 encoding. + * + * Other error returns are any possible error code from: + *\li isc_lex_create(), + *\li isc_lex_openbuffer(), + *\li isc_base64_tobuffer(). + */ + +isc_result_t +isc_base64_tobuffer(isc_lex_t *lexer, isc_buffer_t *target, int length); +/*!< + * \brief Convert base64 encoded text from a lexer context into + * `target`. If 'length' is non-negative, it is the expected number of + * encoded octets to convert. + * + * If 'length' is -1 then 0 or more encoded octets are expected. + * If 'length' is -2 then 1 or more encoded octets are expected. + * + * Returns: + *\li #ISC_R_BADBASE64 -- invalid base64 encoding. + *\li #ISC_R_UNEXPECTEDEND: the text does not contain the expected + * number of encoded octets. + * + * Requires: + *\li 'lexer' is a valid lexer context + *\li 'target' is a buffer containing binary data + *\li 'length' is -2, -1, or non-negative + * + * Ensures: + *\li target will contain the data represented by the base64 encoded + * string parsed by the lexer. No more than `length` octets will + * be read, if `length` is non-negative. The 'used' pointer in + * 'target' will be advanced as necessary. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_BASE64_H */ diff --git a/lib/isc/include/isc/bind9.h b/lib/isc/include/isc/bind9.h new file mode 100644 index 0000000..3c25a76 --- /dev/null +++ b/lib/isc/include/isc/bind9.h @@ -0,0 +1,29 @@ +/* + * 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. + */ + +#ifndef ISC_BIND9_H +#define ISC_BIND9_H 1 + +#include <stdbool.h> + +#include <isc/platform.h> + +/* + * This determines whether we are using the libisc/libdns libraries + * in BIND9 or in some other application. For BIND9 (named and related + * tools) it must be set to true at runtime. Export library clients + * will call isc_lib_register(), which will set it to false. + */ +LIBISC_EXTERNAL_DATA extern bool isc_bind9; + +#endif /* ISC_BIND9_H */ diff --git a/lib/isc/include/isc/buffer.h b/lib/isc/include/isc/buffer.h new file mode 100644 index 0000000..f3becae --- /dev/null +++ b/lib/isc/include/isc/buffer.h @@ -0,0 +1,1103 @@ +/* + * 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. + */ + +#ifndef ISC_BUFFER_H +#define ISC_BUFFER_H 1 + +/***** +***** Module Info +*****/ + +/*! \file isc/buffer.h + * + * \brief A buffer is a region of memory, together with a set of related + * subregions. Buffers are used for parsing and I/O operations. + * + * The 'used region' and the 'available region' are disjoint, and their + * union is the buffer's region. The used region extends from the beginning + * of the buffer region to the last used byte. The available region + * extends from one byte greater than the last used byte to the end of the + * buffer's region. The size of the used region can be changed using various + * buffer commands. Initially, the used region is empty. + * + * The used region is further subdivided into two disjoint regions: the + * 'consumed region' and the 'remaining region'. The union of these two + * regions is the used region. The consumed region extends from the beginning + * of the used region to the byte before the 'current' offset (if any). The + * 'remaining' region extends from the current offset to the end of the used + * region. The size of the consumed region can be changed using various + * buffer commands. Initially, the consumed region is empty. + * + * The 'active region' is an (optional) subregion of the remaining region. + * It extends from the current offset to an offset in the remaining region + * that is selected with isc_buffer_setactive(). Initially, the active region + * is empty. If the current offset advances beyond the chosen offset, the + * active region will also be empty. + * + * \verbatim + * /------------entire length---------------\ + * /----- used region -----\/-- available --\ + * +----------------------------------------+ + * | consumed | remaining | | + * +----------------------------------------+ + * a b c d e + * + * a == base of buffer. + * b == current pointer. Can be anywhere between a and d. + * c == active pointer. Meaningful between b and d. + * d == used pointer. + * e == length of buffer. + * + * a-e == entire length of buffer. + * a-d == used region. + * a-b == consumed region. + * b-d == remaining region. + * b-c == optional active region. + *\endverbatim + * + * The following invariants are maintained by all routines: + * + *\code + * length > 0 + * + * base is a valid pointer to length bytes of memory + * + * 0 <= used <= length + * + * 0 <= current <= used + * + * 0 <= active <= used + * (although active < current implies empty active region) + *\endcode + * + * \li MP: + * Buffers have no synchronization. Clients must ensure exclusive + * access. + * + * \li Reliability: + * No anticipated impact. + * + * \li Resources: + * Memory: 1 pointer + 6 unsigned integers per buffer. + * + * \li Security: + * No anticipated impact. + * + * \li Standards: + * None. + */ + +/*** + *** Imports + ***/ + +#include <inttypes.h> +#include <stdbool.h> + +#include <isc/assertions.h> +#include <isc/formatcheck.h> +#include <isc/lang.h> +#include <isc/likely.h> +#include <isc/list.h> +#include <isc/magic.h> +#include <isc/types.h> + +/*! + * To make many functions be inline macros (via \#define) define this. + * If it is undefined, a function will be used. + */ +/* #define ISC_BUFFER_USEINLINE */ + +ISC_LANG_BEGINDECLS + +/*@{*/ +/*! + *** Magic numbers + ***/ +#define ISC_BUFFER_MAGIC 0x42756621U /* Buf!. */ +#define ISC_BUFFER_VALID(b) ISC_MAGIC_VALID(b, ISC_BUFFER_MAGIC) +/*@}*/ + +/*! + * Size granularity for dynamically resizable buffers; when reserving + * space in a buffer, we round the allocated buffer length up to the + * nearest * multiple of this value. + */ +#define ISC_BUFFER_INCR 2048 + +/* + * The following macros MUST be used only on valid buffers. It is the + * caller's responsibility to ensure this by using the ISC_BUFFER_VALID + * check above, or by calling another isc_buffer_*() function (rather than + * another macro.) + */ + +/*@{*/ +/*! + * Fundamental buffer elements. (A through E in the introductory comment.) + */ +#define isc_buffer_base(b) ((void *)(b)->base) /*a*/ +#define isc_buffer_current(b) \ + ((void *)((unsigned char *)(b)->base + (b)->current)) /*b*/ +#define isc_buffer_active(b) \ + ((void *)((unsigned char *)(b)->base + (b)->active)) /*c*/ +#define isc_buffer_used(b) \ + ((void *)((unsigned char *)(b)->base + (b)->used)) /*d*/ +#define isc_buffer_length(b) ((b)->length) /*e*/ +/*@}*/ + +/*@{*/ +/*! + * Derived lengths. (Described in the introductory comment.) + */ +#define isc_buffer_usedlength(b) ((b)->used) /* d-a */ +#define isc_buffer_consumedlength(b) ((b)->current) /* b-a */ +#define isc_buffer_remaininglength(b) ((b)->used - (b)->current) /* d-b */ +#define isc_buffer_activelength(b) ((b)->active - (b)->current) /* c-b */ +#define isc_buffer_availablelength(b) ((b)->length - (b)->used) /* e-d */ +/*@}*/ + +/*! + * Note that the buffer structure is public. This is principally so buffer + * operations can be implemented using macros. Applications are strongly + * discouraged from directly manipulating the structure. + */ + +struct isc_buffer { + unsigned int magic; + void *base; + /*@{*/ + /*! The following integers are byte offsets from 'base'. */ + unsigned int length; + unsigned int used; + unsigned int current; + unsigned int active; + /*@}*/ + /*! linkable */ + ISC_LINK(isc_buffer_t) link; + /*! private internal elements */ + isc_mem_t *mctx; + /* automatically realloc buffer at put* */ + bool autore; +}; + +/*** + *** Functions + ***/ + +void +isc_buffer_allocate(isc_mem_t *mctx, isc_buffer_t **dynbuffer, + unsigned int length); +/*!< + * \brief Allocate a dynamic linkable buffer which has "length" bytes in the + * data region. + * + * Requires: + *\li "mctx" is valid. + * + *\li "dynbuffer" is non-NULL, and "*dynbuffer" is NULL. + * + * Note: + *\li Changing the buffer's length field is not permitted. + */ + +isc_result_t +isc_buffer_reserve(isc_buffer_t **dynbuffer, unsigned int size); +/*!< + * \brief Make "size" bytes of space available in the buffer. The buffer + * pointer may move when you call this function. + * + * Requires: + *\li "dynbuffer" is not NULL. + * + *\li "*dynbuffer" is a valid dynamic buffer. + * + * Returns: + *\li ISC_R_SUCCESS - success + *\li ISC_R_NOMEMORY - no memory available + * + * Ensures: + *\li "*dynbuffer" will be valid on return and will contain all the + * original data. However, the buffer pointer may be moved during + * reallocation. + */ + +void +isc_buffer_free(isc_buffer_t **dynbuffer); +/*!< + * \brief Release resources allocated for a dynamic buffer. + * + * Requires: + *\li "dynbuffer" is not NULL. + * + *\li "*dynbuffer" is a valid dynamic buffer. + * + * Ensures: + *\li "*dynbuffer" will be NULL on return, and all memory associated with + * the dynamic buffer is returned to the memory context used in + * isc_buffer_allocate(). + */ + +void +isc__buffer_init(isc_buffer_t *b, void *base, unsigned int length); +/*!< + * \brief Make 'b' refer to the 'length'-byte region starting at base. + * + * Requires: + * + *\li 'length' > 0 + * + *\li 'base' is a pointer to a sequence of 'length' bytes. + * + */ + +void +isc__buffer_initnull(isc_buffer_t *b); +/*!< + *\brief Initialize a buffer 'b' with a null data and zero length/ + */ + +void +isc_buffer_reinit(isc_buffer_t *b, void *base, unsigned int length); +/*!< + * \brief Make 'b' refer to the 'length'-byte region starting at base. + * Any existing data will be copied. + * + * Requires: + * + *\li 'length' > 0 AND length >= previous length + * + *\li 'base' is a pointer to a sequence of 'length' bytes. + * + */ + +void +isc__buffer_invalidate(isc_buffer_t *b); +/*!< + * \brief Make 'b' an invalid buffer. + * + * Requires: + *\li 'b' is a valid buffer. + * + * Ensures: + *\li If assertion checking is enabled, future attempts to use 'b' without + * calling isc_buffer_init() on it will cause an assertion failure. + */ + +void +isc_buffer_setautorealloc(isc_buffer_t *b, bool enable); +/*!< + * \brief Enable or disable autoreallocation on 'b'. + * + * Requires: + *\li 'b' is a valid dynamic buffer (b->mctx != NULL). + * + */ + +void +isc__buffer_region(isc_buffer_t *b, isc_region_t *r); +/*!< + * \brief Make 'r' refer to the region of 'b'. + * + * Requires: + * + *\li 'b' is a valid buffer. + * + *\li 'r' points to a region structure. + */ + +void +isc__buffer_usedregion(const isc_buffer_t *b, isc_region_t *r); +/*!< + * \brief Make 'r' refer to the used region of 'b'. + * + * Requires: + * + *\li 'b' is a valid buffer. + * + *\li 'r' points to a region structure. + */ + +void +isc__buffer_availableregion(isc_buffer_t *b, isc_region_t *r); +/*!< + * \brief Make 'r' refer to the available region of 'b'. + * + * Requires: + * + *\li 'b' is a valid buffer. + * + *\li 'r' points to a region structure. + */ + +void +isc__buffer_add(isc_buffer_t *b, unsigned int n); +/*!< + * \brief Increase the 'used' region of 'b' by 'n' bytes. + * + * Requires: + * + *\li 'b' is a valid buffer + * + *\li used + n <= length + * + */ + +void +isc__buffer_subtract(isc_buffer_t *b, unsigned int n); +/*!< + * \brief Decrease the 'used' region of 'b' by 'n' bytes. + * + * Requires: + * + *\li 'b' is a valid buffer + * + *\li used >= n + * + */ + +void +isc__buffer_clear(isc_buffer_t *b); +/*!< + * \brief Make the used region empty. + * + * Requires: + * + *\li 'b' is a valid buffer + * + * Ensures: + * + *\li used = 0 + * + */ + +void +isc__buffer_consumedregion(isc_buffer_t *b, isc_region_t *r); +/*!< + * \brief Make 'r' refer to the consumed region of 'b'. + * + * Requires: + * + *\li 'b' is a valid buffer. + * + *\li 'r' points to a region structure. + */ + +void +isc__buffer_remainingregion(isc_buffer_t *b, isc_region_t *r); +/*!< + * \brief Make 'r' refer to the remaining region of 'b'. + * + * Requires: + * + *\li 'b' is a valid buffer. + * + *\li 'r' points to a region structure. + */ + +void +isc__buffer_activeregion(isc_buffer_t *b, isc_region_t *r); +/*!< + * \brief Make 'r' refer to the active region of 'b'. + * + * Requires: + * + *\li 'b' is a valid buffer. + * + *\li 'r' points to a region structure. + */ + +void +isc__buffer_setactive(isc_buffer_t *b, unsigned int n); +/*!< + * \brief Sets the end of the active region 'n' bytes after current. + * + * Requires: + * + *\li 'b' is a valid buffer. + * + *\li current + n <= used + */ + +void +isc__buffer_first(isc_buffer_t *b); +/*!< + * \brief Make the consumed region empty. + * + * Requires: + * + *\li 'b' is a valid buffer + * + * Ensures: + * + *\li current == 0 + * + */ + +void +isc__buffer_forward(isc_buffer_t *b, unsigned int n); +/*!< + * \brief Increase the 'consumed' region of 'b' by 'n' bytes. + * + * Requires: + * + *\li 'b' is a valid buffer + * + *\li current + n <= used + * + */ + +void +isc__buffer_back(isc_buffer_t *b, unsigned int n); +/*!< + * \brief Decrease the 'consumed' region of 'b' by 'n' bytes. + * + * Requires: + * + *\li 'b' is a valid buffer + * + *\li n <= current + * + */ + +void +isc_buffer_compact(isc_buffer_t *b); +/*!< + * \brief Compact the used region by moving the remaining region so it occurs + * at the start of the buffer. The used region is shrunk by the size of + * the consumed region, and the consumed region is then made empty. + * + * Requires: + * + *\li 'b' is a valid buffer + * + * Ensures: + * + *\li current == 0 + * + *\li The size of the used region is now equal to the size of the remaining + * region (as it was before the call). The contents of the used region + * are those of the remaining region (as it was before the call). + */ + +uint8_t +isc_buffer_getuint8(isc_buffer_t *b); +/*!< + * \brief Read an unsigned 8-bit integer from 'b' and return it. + * + * Requires: + * + *\li 'b' is a valid buffer. + * + *\li The length of the remaining region of 'b' is at least 1. + * + * Ensures: + * + *\li The current pointer in 'b' is advanced by 1. + * + * Returns: + * + *\li A 8-bit unsigned integer. + */ + +void +isc__buffer_putuint8(isc_buffer_t *b, uint8_t val); +/*!< + * \brief Store an unsigned 8-bit integer from 'val' into 'b'. + * + * Requires: + *\li 'b' is a valid buffer. + * + *\li The length of the available region of 'b' is at least 1 + * or the buffer has autoreallocation enabled. + * + * Ensures: + *\li The used pointer in 'b' is advanced by 1. + */ + +uint16_t +isc_buffer_getuint16(isc_buffer_t *b); +/*!< + * \brief Read an unsigned 16-bit integer in network byte order from 'b', + * convert it to host byte order, and return it. + * + * Requires: + * + *\li 'b' is a valid buffer. + * + *\li The length of the remaining region of 'b' is at least 2. + * + * Ensures: + * + *\li The current pointer in 'b' is advanced by 2. + * + * Returns: + * + *\li A 16-bit unsigned integer. + */ + +void +isc__buffer_putuint16(isc_buffer_t *b, uint16_t val); +/*!< + * \brief Store an unsigned 16-bit integer in host byte order from 'val' + * into 'b' in network byte order. + * + * Requires: + *\li 'b' is a valid buffer. + * + *\li The length of the available region of 'b' is at least 2 + * or the buffer has autoreallocation enabled. + * + * Ensures: + *\li The used pointer in 'b' is advanced by 2. + */ + +uint32_t +isc_buffer_getuint32(isc_buffer_t *b); +/*!< + * \brief Read an unsigned 32-bit integer in network byte order from 'b', + * convert it to host byte order, and return it. + * + * Requires: + * + *\li 'b' is a valid buffer. + * + *\li The length of the remaining region of 'b' is at least 4. + * + * Ensures: + * + *\li The current pointer in 'b' is advanced by 4. + * + * Returns: + * + *\li A 32-bit unsigned integer. + */ + +void +isc__buffer_putuint32(isc_buffer_t *b, uint32_t val); +/*!< + * \brief Store an unsigned 32-bit integer in host byte order from 'val' + * into 'b' in network byte order. + * + * Requires: + *\li 'b' is a valid buffer. + * + *\li The length of the available region of 'b' is at least 4 + * or the buffer has autoreallocation enabled. + * + * Ensures: + *\li The used pointer in 'b' is advanced by 4. + */ + +uint64_t +isc_buffer_getuint48(isc_buffer_t *b); +/*!< + * \brief Read an unsigned 48-bit integer in network byte order from 'b', + * convert it to host byte order, and return it. + * + * Requires: + * + *\li 'b' is a valid buffer. + * + *\li The length of the remaining region of 'b' is at least 6. + * + * Ensures: + * + *\li The current pointer in 'b' is advanced by 6. + * + * Returns: + * + *\li A 48-bit unsigned integer (stored in a 64-bit integer). + */ + +void +isc__buffer_putuint48(isc_buffer_t *b, uint64_t val); +/*!< + * \brief Store an unsigned 48-bit integer in host byte order from 'val' + * into 'b' in network byte order. + * + * Requires: + *\li 'b' is a valid buffer. + * + *\li The length of the available region of 'b' is at least 6 + * or the buffer has autoreallocation enabled. + * + * Ensures: + *\li The used pointer in 'b' is advanced by 6. + */ + +void +isc__buffer_putuint24(isc_buffer_t *b, uint32_t val); +/*!< + * Store an unsigned 24-bit integer in host byte order from 'val' + * into 'b' in network byte order. + * + * Requires: + *\li 'b' is a valid buffer. + * + *\li The length of the available region of 'b' is at least 3 + * or the buffer has autoreallocation enabled. + * + * Ensures: + *\li The used pointer in 'b' is advanced by 3. + */ + +void +isc__buffer_putmem(isc_buffer_t *b, const unsigned char *base, + unsigned int length); +/*!< + * \brief Copy 'length' bytes of memory at 'base' into 'b'. + * + * Requires: + *\li 'b' is a valid buffer. + * + *\li 'base' points to 'length' bytes of valid memory. + * + *\li The length of the available region of 'b' is at least 'length' + * or the buffer has autoreallocation enabled. + * + * Ensures: + *\li The used pointer in 'b' is advanced by 'length'. + */ + +void +isc__buffer_putstr(isc_buffer_t *b, const char *source); +/*!< + * \brief Copy 'source' into 'b', not including terminating NUL. + * + * Requires: + *\li 'b' is a valid buffer. + * + *\li 'source' is a valid NULL terminated string. + * + *\li The length of the available region of 'b' is at least strlen('source') + * or the buffer has autoreallocation enabled. + * + * Ensures: + *\li The used pointer in 'b' is advanced by strlen('source'). + */ + +void +isc_buffer_putdecint(isc_buffer_t *b, int64_t v); +/*!< + * \brief Put decimal representation of 'v' in b + * + * Requires: + *\li 'b' is a valid buffer. + * + *\li The length of the available region of 'b' is at least strlen(dec('v')) + * or the buffer has autoreallocation enabled. + * + * Ensures: + *\li The used pointer in 'b' is advanced by strlen(dec('v')). + */ + +isc_result_t +isc_buffer_copyregion(isc_buffer_t *b, const isc_region_t *r); +/*!< + * \brief Copy the contents of 'r' into 'b'. + * + * Notes: + *\li If 'b' has autoreallocation enabled, and the length of 'r' is greater + * than the length of the available region of 'b', 'b' is reallocated. + * + * Requires: + *\li 'b' is a valid buffer. + * + *\li 'r' is a valid region. + * + * Returns: + *\li ISC_R_SUCCESS + *\li ISC_R_NOSPACE The available region of 'b' is not + * big enough. + */ + +isc_result_t +isc_buffer_dup(isc_mem_t *mctx, isc_buffer_t **dstp, const isc_buffer_t *src); +/*!< + * \brief Allocate 'dst' and copy used contents of 'src' into it. + * + * Requires: + *\li 'dstp' is not NULL and *dst is NULL. + *\li 'src' is a valid buffer. + * + * Returns: + *\li ISC_R_SUCCESS + */ + +isc_result_t +isc_buffer_printf(isc_buffer_t *b, const char *format, ...) + ISC_FORMAT_PRINTF(2, 3); +/*!< + * \brief Append a formatted string to the used region of 'b'. + * + * Notes: + * + *\li The 'format' argument is a printf(3) string, with additional arguments + * as necessary. + * + *\li If 'b' has autoreallocation enabled, and the length of the formatted + * string is greater than the length of the available region of 'b', 'b' + * is reallocated. + * + * Requires: + * + *\li 'b' is a valid buffer. + * + * Ensures: + * + *\li The used pointer in 'b' is advanced by the number of bytes appended + * (excluding the terminating NULL byte). + * + * Returns: + * + *\li #ISC_R_SUCCESS Operation succeeded. + *\li #ISC_R_NOSPACE 'b' does not allow reallocation and appending the + * formatted string to it would cause it to overflow. + *\li #ISC_R_NOMEMORY Reallocation failed. + *\li #ISC_R_FAILURE Other error occurred. + */ + +ISC_LANG_ENDDECLS + +/* + * Inline macro versions of the functions. These should never be called + * directly by an application, but will be used by the functions within + * buffer.c. The callers should always use "isc_buffer_*()" names, never + * ones beginning with "isc__" + */ + +/*! \note + * XXXDCL Something more could be done with initializing buffers that + * point to const data. For example, isc_buffer_constinit() could + * set a new boolean flag in the buffer structure indicating whether + * the buffer was initialized with that function. * Then if the + * boolean were true, the isc_buffer_put* functions could assert a + * contractual requirement for a non-const buffer. + * + * One drawback is that the isc_buffer_* functions (macros) that return + * pointers would still need to return non-const pointers to avoid compiler + * warnings, so it would be up to code that uses them to have to deal + * with the possibility that the buffer was initialized as const -- + * a problem that they *already* have to deal with but have absolutely + * no ability to. With a new isc_buffer_isconst() function returning + * true/false, they could at least assert a contractual requirement for + * non-const buffers when needed. + */ +#define ISC__BUFFER_INIT(_b, _base, _length) \ + do { \ + ISC_REQUIRE((_b) != NULL); \ + (_b)->base = _base; \ + (_b)->length = (_length); \ + (_b)->used = 0; \ + (_b)->current = 0; \ + (_b)->active = 0; \ + (_b)->mctx = NULL; \ + ISC_LINK_INIT(_b, link); \ + (_b)->magic = ISC_BUFFER_MAGIC; \ + (_b)->autore = false; \ + } while (0) + +#define ISC__BUFFER_INITNULL(_b) ISC__BUFFER_INIT(_b, NULL, 0) + +#define ISC__BUFFER_INVALIDATE(_b) \ + do { \ + ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \ + ISC_REQUIRE(!ISC_LINK_LINKED((_b), link)); \ + ISC_REQUIRE((_b)->mctx == NULL); \ + (_b)->magic = 0; \ + (_b)->base = NULL; \ + (_b)->length = 0; \ + (_b)->used = 0; \ + (_b)->current = 0; \ + (_b)->active = 0; \ + } while (0) + +#define ISC__BUFFER_REGION(_b, _r) \ + do { \ + ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \ + ISC_REQUIRE((_r) != NULL); \ + (_r)->base = (_b)->base; \ + (_r)->length = (_b)->length; \ + } while (0) + +#define ISC__BUFFER_USEDREGION(_b, _r) \ + do { \ + ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \ + ISC_REQUIRE((_r) != NULL); \ + (_r)->base = (_b)->base; \ + (_r)->length = (_b)->used; \ + } while (0) + +#define ISC__BUFFER_AVAILABLEREGION(_b, _r) \ + do { \ + ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \ + ISC_REQUIRE((_r) != NULL); \ + (_r)->base = isc_buffer_used(_b); \ + (_r)->length = isc_buffer_availablelength(_b); \ + } while (0) + +#define ISC__BUFFER_ADD(_b, _n) \ + do { \ + ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \ + ISC_REQUIRE((_b)->used + (_n) <= (_b)->length); \ + (_b)->used += (_n); \ + } while (0) + +#define ISC__BUFFER_SUBTRACT(_b, _n) \ + do { \ + ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \ + ISC_REQUIRE((_b)->used >= (_n)); \ + (_b)->used -= (_n); \ + if ((_b)->current > (_b)->used) \ + (_b)->current = (_b)->used; \ + if ((_b)->active > (_b)->used) \ + (_b)->active = (_b)->used; \ + } while (0) + +#define ISC__BUFFER_CLEAR(_b) \ + do { \ + ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \ + (_b)->used = 0; \ + (_b)->current = 0; \ + (_b)->active = 0; \ + } while (0) + +#define ISC__BUFFER_CONSUMEDREGION(_b, _r) \ + do { \ + ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \ + ISC_REQUIRE((_r) != NULL); \ + (_r)->base = (_b)->base; \ + (_r)->length = (_b)->current; \ + } while (0) + +#define ISC__BUFFER_REMAININGREGION(_b, _r) \ + do { \ + ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \ + ISC_REQUIRE((_r) != NULL); \ + (_r)->base = isc_buffer_current(_b); \ + (_r)->length = isc_buffer_remaininglength(_b); \ + } while (0) + +#define ISC__BUFFER_ACTIVEREGION(_b, _r) \ + do { \ + ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \ + ISC_REQUIRE((_r) != NULL); \ + if ((_b)->current < (_b)->active) { \ + (_r)->base = isc_buffer_current(_b); \ + (_r)->length = isc_buffer_activelength(_b); \ + } else { \ + (_r)->base = NULL; \ + (_r)->length = 0; \ + } \ + } while (0) + +#define ISC__BUFFER_SETACTIVE(_b, _n) \ + do { \ + ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \ + ISC_REQUIRE((_b)->current + (_n) <= (_b)->used); \ + (_b)->active = (_b)->current + (_n); \ + } while (0) + +#define ISC__BUFFER_FIRST(_b) \ + do { \ + ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \ + (_b)->current = 0; \ + } while (0) + +#define ISC__BUFFER_FORWARD(_b, _n) \ + do { \ + ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \ + ISC_REQUIRE((_b)->current + (_n) <= (_b)->used); \ + (_b)->current += (_n); \ + } while (0) + +#define ISC__BUFFER_BACK(_b, _n) \ + do { \ + ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \ + ISC_REQUIRE((_n) <= (_b)->current); \ + (_b)->current -= (_n); \ + } while (0) + +#define ISC__BUFFER_PUTMEM(_b, _base, _length) \ + do { \ + ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \ + if (ISC_UNLIKELY((_b)->autore)) { \ + isc_buffer_t *_tmp = _b; \ + ISC_REQUIRE(isc_buffer_reserve(&_tmp, _length) == \ + ISC_R_SUCCESS); \ + } \ + ISC_REQUIRE(isc_buffer_availablelength(_b) >= \ + (unsigned int)_length); \ + if (_length > 0U) { \ + memmove(isc_buffer_used(_b), (_base), (_length)); \ + (_b)->used += (_length); \ + } \ + } while (0) + +#define ISC__BUFFER_PUTSTR(_b, _source) \ + do { \ + unsigned int _length; \ + unsigned char *_cp; \ + ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \ + ISC_REQUIRE((_source) != NULL); \ + _length = (unsigned int)strlen(_source); \ + if (ISC_UNLIKELY((_b)->autore)) { \ + isc_buffer_t *_tmp = _b; \ + ISC_REQUIRE(isc_buffer_reserve(&_tmp, _length) == \ + ISC_R_SUCCESS); \ + } \ + ISC_REQUIRE(isc_buffer_availablelength(_b) >= _length); \ + _cp = isc_buffer_used(_b); \ + memmove(_cp, (_source), _length); \ + (_b)->used += (_length); \ + } while (0) + +#define ISC__BUFFER_PUTUINT8(_b, _val) \ + do { \ + unsigned char *_cp; \ + /* evaluate (_val) only once */ \ + uint8_t _val2 = (_val); \ + ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \ + if (ISC_UNLIKELY((_b)->autore)) { \ + isc_buffer_t *_tmp = _b; \ + ISC_REQUIRE(isc_buffer_reserve(&_tmp, 1) == \ + ISC_R_SUCCESS); \ + } \ + ISC_REQUIRE(isc_buffer_availablelength(_b) >= 1U); \ + _cp = isc_buffer_used(_b); \ + (_b)->used++; \ + _cp[0] = _val2; \ + } while (0) + +#define ISC__BUFFER_PUTUINT16(_b, _val) \ + do { \ + unsigned char *_cp; \ + /* evaluate (_val) only once */ \ + uint16_t _val2 = (_val); \ + ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \ + if (ISC_UNLIKELY((_b)->autore)) { \ + isc_buffer_t *_tmp = _b; \ + ISC_REQUIRE(isc_buffer_reserve(&_tmp, 2) == \ + ISC_R_SUCCESS); \ + } \ + ISC_REQUIRE(isc_buffer_availablelength(_b) >= 2U); \ + _cp = isc_buffer_used(_b); \ + (_b)->used += 2; \ + _cp[0] = (unsigned char)(_val2 >> 8); \ + _cp[1] = (unsigned char)_val2; \ + } while (0) + +#define ISC__BUFFER_PUTUINT24(_b, _val) \ + do { \ + unsigned char *_cp; \ + /* evaluate (_val) only once */ \ + uint32_t _val2 = (_val); \ + ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \ + if (ISC_UNLIKELY((_b)->autore)) { \ + isc_buffer_t *_tmp = _b; \ + ISC_REQUIRE(isc_buffer_reserve(&_tmp, 3) == \ + ISC_R_SUCCESS); \ + } \ + ISC_REQUIRE(isc_buffer_availablelength(_b) >= 3U); \ + _cp = isc_buffer_used(_b); \ + (_b)->used += 3; \ + _cp[0] = (unsigned char)(_val2 >> 16); \ + _cp[1] = (unsigned char)(_val2 >> 8); \ + _cp[2] = (unsigned char)_val2; \ + } while (0) + +#define ISC__BUFFER_PUTUINT32(_b, _val) \ + do { \ + unsigned char *_cp; \ + /* evaluate (_val) only once */ \ + uint32_t _val2 = (_val); \ + ISC_REQUIRE(ISC_BUFFER_VALID(_b)); \ + if (ISC_UNLIKELY((_b)->autore)) { \ + isc_buffer_t *_tmp = _b; \ + ISC_REQUIRE(isc_buffer_reserve(&_tmp, 4) == \ + ISC_R_SUCCESS); \ + } \ + ISC_REQUIRE(isc_buffer_availablelength(_b) >= 4U); \ + _cp = isc_buffer_used(_b); \ + (_b)->used += 4; \ + _cp[0] = (unsigned char)(_val2 >> 24); \ + _cp[1] = (unsigned char)(_val2 >> 16); \ + _cp[2] = (unsigned char)(_val2 >> 8); \ + _cp[3] = (unsigned char)_val2; \ + } while (0) + +#if defined(ISC_BUFFER_USEINLINE) +#define isc_buffer_init ISC__BUFFER_INIT +#define isc_buffer_initnull ISC__BUFFER_INITNULL +#define isc_buffer_invalidate ISC__BUFFER_INVALIDATE +#define isc_buffer_region ISC__BUFFER_REGION +#define isc_buffer_usedregion ISC__BUFFER_USEDREGION +#define isc_buffer_availableregion ISC__BUFFER_AVAILABLEREGION +#define isc_buffer_add ISC__BUFFER_ADD +#define isc_buffer_subtract ISC__BUFFER_SUBTRACT +#define isc_buffer_clear ISC__BUFFER_CLEAR +#define isc_buffer_consumedregion ISC__BUFFER_CONSUMEDREGION +#define isc_buffer_remainingregion ISC__BUFFER_REMAININGREGION +#define isc_buffer_activeregion ISC__BUFFER_ACTIVEREGION +#define isc_buffer_setactive ISC__BUFFER_SETACTIVE +#define isc_buffer_first ISC__BUFFER_FIRST +#define isc_buffer_forward ISC__BUFFER_FORWARD +#define isc_buffer_back ISC__BUFFER_BACK +#define isc_buffer_putmem ISC__BUFFER_PUTMEM +#define isc_buffer_putstr ISC__BUFFER_PUTSTR +#define isc_buffer_putuint8 ISC__BUFFER_PUTUINT8 +#define isc_buffer_putuint16 ISC__BUFFER_PUTUINT16 +#define isc_buffer_putuint24 ISC__BUFFER_PUTUINT24 +#define isc_buffer_putuint32 ISC__BUFFER_PUTUINT32 +#else /* if defined(ISC_BUFFER_USEINLINE) */ +#define isc_buffer_init isc__buffer_init +#define isc_buffer_initnull isc__buffer_initnull +#define isc_buffer_invalidate isc__buffer_invalidate +#define isc_buffer_region isc__buffer_region +#define isc_buffer_usedregion isc__buffer_usedregion +#define isc_buffer_availableregion isc__buffer_availableregion +#define isc_buffer_add isc__buffer_add +#define isc_buffer_subtract isc__buffer_subtract +#define isc_buffer_clear isc__buffer_clear +#define isc_buffer_consumedregion isc__buffer_consumedregion +#define isc_buffer_remainingregion isc__buffer_remainingregion +#define isc_buffer_activeregion isc__buffer_activeregion +#define isc_buffer_setactive isc__buffer_setactive +#define isc_buffer_first isc__buffer_first +#define isc_buffer_forward isc__buffer_forward +#define isc_buffer_back isc__buffer_back +#define isc_buffer_putmem isc__buffer_putmem +#define isc_buffer_putstr isc__buffer_putstr +#define isc_buffer_putuint8 isc__buffer_putuint8 +#define isc_buffer_putuint16 isc__buffer_putuint16 +#define isc_buffer_putuint24 isc__buffer_putuint24 +#define isc_buffer_putuint32 isc__buffer_putuint32 +#endif /* if defined(ISC_BUFFER_USEINLINE) */ + +#define isc_buffer_constinit(_b, _d, _l) \ + do { \ + union { \ + void *_var; \ + const void *_const; \ + } _deconst; \ + _deconst._const = (_d); \ + isc_buffer_init((_b), _deconst._var, (_l)); \ + } while (0) + +/* + * No inline method for this one (yet). + */ +#define isc_buffer_putuint48 isc__buffer_putuint48 + +#endif /* ISC_BUFFER_H */ diff --git a/lib/isc/include/isc/bufferlist.h b/lib/isc/include/isc/bufferlist.h new file mode 100644 index 0000000..95ad3ef --- /dev/null +++ b/lib/isc/include/isc/bufferlist.h @@ -0,0 +1,80 @@ +/* + * 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. + */ + +#ifndef ISC_BUFFERLIST_H +#define ISC_BUFFERLIST_H 1 + +/***** +***** Module Info +*****/ + +/*! \file isc/bufferlist.h + * + * + *\brief Buffer lists have no synchronization. Clients must ensure + * exclusive * access. + * + * \li Reliability: + * No anticipated impact. + * + * \li Security: + * No anticipated impact. + * + * \li Standards: + * None. + */ + +/*** + *** Imports + ***/ + +#include <isc/lang.h> +#include <isc/types.h> + +ISC_LANG_BEGINDECLS + +/*** + *** Functions + ***/ + +unsigned int +isc_bufferlist_usedcount(isc_bufferlist_t *bl); +/*!< + * \brief Return the length of the sum of all used regions of all buffers in + * the buffer list 'bl' + * + * Requires: + * + *\li 'bl' is not NULL. + * + * Returns: + *\li sum of all used regions' lengths. + */ + +unsigned int +isc_bufferlist_availablecount(isc_bufferlist_t *bl); +/*!< + * \brief Return the length of the sum of all available regions of all buffers + * in the buffer list 'bl' + * + * Requires: + * + *\li 'bl' is not NULL. + * + * Returns: + *\li sum of all available regions' lengths. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_BUFFERLIST_H */ diff --git a/lib/isc/include/isc/cmocka.h b/lib/isc/include/isc/cmocka.h new file mode 100644 index 0000000..de86d5a --- /dev/null +++ b/lib/isc/include/isc/cmocka.h @@ -0,0 +1,55 @@ +/* + * 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. + */ + +/*! \file isc/cmocka.h */ + +#pragma once + +#include <cmocka.h> + +#include <isc/lang.h> + +ISC_LANG_BEGINDECLS + +/* + * Copy the test identified by 'name' from 'tests' to 'selected'. + */ +#define cmocka_add_test_byname(tests, name, selected) \ + _cmocka_add_test_byname(tests, sizeof(tests) / sizeof(tests[0]), name, \ + selected, \ + sizeof(selected) / sizeof(selected[0])) + +static inline bool +_cmocka_add_test_byname(const struct CMUnitTest *tests, size_t ntests, + const char *name, struct CMUnitTest *selected, + size_t nselected) { + size_t i, j; + + for (i = 0; i < ntests && tests[i].name != NULL; i++) { + if (strcmp(tests[i].name, name) != 0) { + continue; + } + for (j = 0; j < nselected && selected[j].name != NULL; j++) { + if (strcmp(tests[j].name, name) == 0) { + break; + } + } + if (j < nselected && selected[j].name == NULL) { + selected[j] = tests[i]; + } + return (true); + } + return (false); +} + +ISC_LANG_ENDDECLS diff --git a/lib/isc/include/isc/commandline.h b/lib/isc/include/isc/commandline.h new file mode 100644 index 0000000..69143ce --- /dev/null +++ b/lib/isc/include/isc/commandline.h @@ -0,0 +1,59 @@ +/* + * 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. + */ + +#ifndef ISC_COMMANDLINE_H +#define ISC_COMMANDLINE_H 1 + +/*! \file isc/commandline.h */ + +#include <stdbool.h> + +#include <isc/lang.h> +#include <isc/platform.h> +#include <isc/result.h> + +/*% Index into parent argv vector. */ +LIBISC_EXTERNAL_DATA extern int isc_commandline_index; +/*% Character checked for validity. */ +LIBISC_EXTERNAL_DATA extern int isc_commandline_option; +/*% Argument associated with option. */ +LIBISC_EXTERNAL_DATA extern char *isc_commandline_argument; +/*% For printing error messages. */ +LIBISC_EXTERNAL_DATA extern char *isc_commandline_progname; +/*% Print error message. */ +LIBISC_EXTERNAL_DATA extern bool isc_commandline_errprint; +/*% Reset getopt. */ +LIBISC_EXTERNAL_DATA extern bool isc_commandline_reset; + +ISC_LANG_BEGINDECLS + +int +isc_commandline_parse(int argc, char *const *argv, const char *options); +/*%< + * Parse a command line (similar to getopt()) + */ + +isc_result_t +isc_commandline_strtoargv(isc_mem_t *mctx, char *s, unsigned int *argcp, + char ***argvp, unsigned int n); +/*%< + * Tokenize the string "s" into whitespace-separated words, + * returning the number of words in '*argcp' and an array + * of pointers to the words in '*argvp'. The caller + * must free the array using isc_mem_free(). The string + * is modified in-place. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_COMMANDLINE_H */ diff --git a/lib/isc/include/isc/counter.h b/lib/isc/include/isc/counter.h new file mode 100644 index 0000000..f61a55b --- /dev/null +++ b/lib/isc/include/isc/counter.h @@ -0,0 +1,87 @@ +/* + * 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. + */ + +#ifndef ISC_COUNTER_H +#define ISC_COUNTER_H 1 + +/***** +***** Module Info +*****/ + +/*! \file isc/counter.h + * + * \brief The isc_counter_t object is a simplified version of the + * isc_quota_t object; it tracks the consumption of limited + * resources, returning an error condition when the quota is + * exceeded. However, unlike isc_quota_t, attaching and detaching + * from a counter object does not increment or decrement the counter. + */ + +/*** + *** Imports. + ***/ + +#include <isc/lang.h> +#include <isc/mutex.h> +#include <isc/types.h> + +/***** +***** Types. +*****/ + +ISC_LANG_BEGINDECLS + +isc_result_t +isc_counter_create(isc_mem_t *mctx, int limit, isc_counter_t **counterp); +/*%< + * Allocate and initialize a counter object. + */ + +isc_result_t +isc_counter_increment(isc_counter_t *counter); +/*%< + * Increment the counter. + * + * If the counter limit is nonzero and has been reached, then + * return ISC_R_QUOTA, otherwise ISC_R_SUCCESS. (The counter is + * incremented regardless of return value.) + */ + +unsigned int +isc_counter_used(isc_counter_t *counter); +/*%< + * Return the current counter value. + */ + +void +isc_counter_setlimit(isc_counter_t *counter, int limit); +/*%< + * Set the counter limit. + */ + +void +isc_counter_attach(isc_counter_t *source, isc_counter_t **targetp); +/*%< + * Attach to a counter object, increasing its reference counter. + */ + +void +isc_counter_detach(isc_counter_t **counterp); +/*%< + * Detach (and destroy if reference counter has dropped to zero) + * a counter object. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_COUNTER_H */ diff --git a/lib/isc/include/isc/crc64.h b/lib/isc/include/isc/crc64.h new file mode 100644 index 0000000..a3fdfb4 --- /dev/null +++ b/lib/isc/include/isc/crc64.h @@ -0,0 +1,58 @@ +/* + * 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. + */ + +#ifndef ISC_CRC64_H +#define ISC_CRC64_H 1 + +/*! \file isc/crc64.h + * \brief CRC64 in C + */ + +#include <inttypes.h> + +#include <isc/lang.h> +#include <isc/types.h> + +ISC_LANG_BEGINDECLS + +void +isc_crc64_init(uint64_t *crc); +/*% + * Initialize a new CRC. + * + * Requires: + * * 'crc' is not NULL. + */ + +void +isc_crc64_update(uint64_t *crc, const void *data, size_t len); +/*% + * Add data to the CRC. + * + * Requires: + * * 'crc' is not NULL. + * * 'data' is not NULL. + */ + +void +isc_crc64_final(uint64_t *crc); +/*% + * Finalize the CRC. + * + * Requires: + * * 'crc' is not NULL. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_CRC64_H */ diff --git a/lib/isc/include/isc/deprecated.h b/lib/isc/include/isc/deprecated.h new file mode 100644 index 0000000..73ce584 --- /dev/null +++ b/lib/isc/include/isc/deprecated.h @@ -0,0 +1,23 @@ +/* + * 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. + */ + +#ifndef ISC_DEPRECATED_H +#define ISC_DEPRECATED_H + +#if (__GNUC__ + 0) > 3 +#define ISC_DEPRECATED __attribute__((deprecated)) +#else /* if (__GNUC__ + 0) > 3 */ +#define ISC_DEPRECATED /* none */ +#endif /* __GNUC__ > 3*/ + +#endif /* ifndef ISC_DEPRECATED_H */ diff --git a/lib/isc/include/isc/endian.h b/lib/isc/include/isc/endian.h new file mode 100644 index 0000000..e598a7b --- /dev/null +++ b/lib/isc/include/isc/endian.h @@ -0,0 +1,190 @@ +/* + * 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. + */ + +#pragma once + +#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || \ + defined(__OpenBSD__) || defined(__bsdi__) + +#include <sys/endian.h> + +/* + * Recent BSDs should have [bl]e{16,32,64}toh() defined in <sys/endian.h>. + * Older ones might not, but these should have the alternatively named + * [bl]etoh{16,32,64}() functions defined. + */ +#ifndef be16toh +#define be16toh(x) betoh16(x) +#define le16toh(x) letoh16(x) +#define be32toh(x) betoh32(x) +#define le32toh(x) letoh32(x) +#define be64toh(x) betoh64(x) +#define le64toh(x) letoh64(x) +#endif /* !be16toh */ + +#elif defined(_WIN32) + +/* + * Windows is always little-endian and has its own byte-swapping routines, so + * use these. + */ + +#include <stdlib.h> + +#define htobe16(x) _byteswap_ushort(x) +#define htole16(x) (x) +#define be16toh(x) _byteswap_ushort(x) +#define le16toh(x) (x) + +#define htobe32(x) _byteswap_ulong(x) +#define htole32(x) (x) +#define be32toh(x) _byteswap_ulong(x) +#define le32toh(x) (x) + +#define htobe64(x) _byteswap_uint64(x) +#define htole64(x) (x) +#define be64toh(x) _byteswap_uint64(x) +#define le64toh(x) (x) + +#elif defined __APPLE__ + +/* + * macOS has its own byte-swapping routines, so use these. + */ + +#include <libkern/OSByteOrder.h> + +#define htobe16(x) OSSwapHostToBigInt16(x) +#define htole16(x) OSSwapHostToLittleInt16(x) +#define be16toh(x) OSSwapBigToHostInt16(x) +#define le16toh(x) OSSwapLittleToHostInt16(x) + +#define htobe32(x) OSSwapHostToBigInt32(x) +#define htole32(x) OSSwapHostToLittleInt32(x) +#define be32toh(x) OSSwapBigToHostInt32(x) +#define le32toh(x) OSSwapLittleToHostInt32(x) + +#define htobe64(x) OSSwapHostToBigInt64(x) +#define htole64(x) OSSwapHostToLittleInt64(x) +#define be64toh(x) OSSwapBigToHostInt64(x) +#define le64toh(x) OSSwapLittleToHostInt64(x) + +#elif defined(sun) || defined(__sun) || defined(__SVR4) + +/* + * For Solaris, rely on the fallback definitions below, though use + * Solaris-specific versions of bswap_{16,32,64}(). + */ + +#include <sys/byteorder.h> + +#define bswap_16(x) BSWAP_16(x) +#define bswap_32(x) BSWAP_32(x) +#define bswap_64(x) BSWAP_64(x) + +#elif defined(__ANDROID__) || defined(__CYGWIN__) || defined(__GNUC__) || \ + defined(__GNU__) + +#include <byteswap.h> +#include <endian.h> + +#else /* if defined(__DragonFly__) || defined(__FreeBSD__) || \ + * defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__) */ + +#endif /* Specific platform support */ + +/* + * Fallback definitions. + */ + +#include <inttypes.h> + +#ifndef bswap_16 +#define bswap_16(x) \ + ((uint16_t)((((uint16_t)(x)&0xff00) >> 8) | \ + (((uint16_t)(x)&0x00ff) << 8))) +#endif /* !bswap_16 */ + +#ifndef bswap_32 +#define bswap_32(x) \ + ((uint32_t)((((uint32_t)(x)&0xff000000) >> 24) | \ + (((uint32_t)(x)&0x00ff0000) >> 8) | \ + (((uint32_t)(x)&0x0000ff00) << 8) | \ + (((uint32_t)(x)&0x000000ff) << 24))) +#endif /* !bswap_32 */ + +#ifndef bswap_64 +#define bswap_64(x) \ + ((uint64_t)((((uint64_t)(x)&0xff00000000000000ULL) >> 56) | \ + (((uint64_t)(x)&0x00ff000000000000ULL) >> 40) | \ + (((uint64_t)(x)&0x0000ff0000000000ULL) >> 24) | \ + (((uint64_t)(x)&0x000000ff00000000ULL) >> 8) | \ + (((uint64_t)(x)&0x00000000ff000000ULL) << 8) | \ + (((uint64_t)(x)&0x0000000000ff0000ULL) << 24) | \ + (((uint64_t)(x)&0x000000000000ff00ULL) << 40) | \ + (((uint64_t)(x)&0x00000000000000ffULL) << 56))) +#endif /* !bswap_64 */ + +#ifndef htobe16 +#if WORDS_BIGENDIAN + +#define htobe16(x) (x) +#define htole16(x) bswap_16(x) +#define be16toh(x) (x) +#define le16toh(x) bswap_16(x) + +#else /* WORDS_BIGENDIAN */ + +#define htobe16(x) bswap_16(x) +#define htole16(x) (x) +#define be16toh(x) bswap_16(x) +#define le16toh(x) (x) + +#endif /* WORDS_BIGENDIAN */ +#endif /* !htobe16 */ + +#ifndef htobe32 +#if WORDS_BIGENDIAN + +#define htobe32(x) (x) +#define htole32(x) bswap_32(x) +#define be32toh(x) (x) +#define le32toh(x) bswap_32(x) + +#else /* WORDS_BIGENDIAN */ + +#define htobe32(x) bswap_32(x) +#define htole32(x) (x) +#define be32toh(x) bswap_32(x) +#define le32toh(x) (x) + +#endif /* WORDS_BIGENDIAN */ +#endif /* !htobe32 */ + +#ifndef htobe64 +#if WORDS_BIGENDIAN + +#define htobe64(x) (x) +#define htole64(x) bswap_64(x) +#define be64toh(x) (x) +#define le64toh(x) bswap_64(x) + +#else /* WORDS_BIGENDIAN */ + +#define htobe64(x) bswap_64(x) +#define htole64(x) (x) +#define be64toh(x) bswap_64(x) +#define le64toh(x) (x) + +#endif /* WORDS_BIGENDIAN */ +#endif /* !htobe64 */ diff --git a/lib/isc/include/isc/errno.h b/lib/isc/include/isc/errno.h new file mode 100644 index 0000000..1b058dd --- /dev/null +++ b/lib/isc/include/isc/errno.h @@ -0,0 +1,30 @@ +/* + * 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. + */ + +#ifndef ISC_ERRNO_H +#define ISC_ERRNO_H 1 + +/*! \file isc/file.h */ + +#include <isc/types.h> + +ISC_LANG_BEGINDECLS + +isc_result_t +isc_errno_toresult(int err); +/*!< + * \brief Convert a POSIX errno value to an ISC result code. + */ +ISC_LANG_ENDDECLS + +#endif /* ISC_ERRNO_H */ diff --git a/lib/isc/include/isc/error.h b/lib/isc/include/isc/error.h new file mode 100644 index 0000000..4f6fba3 --- /dev/null +++ b/lib/isc/include/isc/error.h @@ -0,0 +1,57 @@ +/* + * 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. + */ + +#ifndef ISC_ERROR_H +#define ISC_ERROR_H 1 + +/*! \file isc/error.h */ + +#include <stdarg.h> + +#include <isc/formatcheck.h> +#include <isc/lang.h> +#include <isc/likely.h> +#include <isc/platform.h> + +ISC_LANG_BEGINDECLS + +typedef void (*isc_errorcallback_t)(const char *, int, const char *, va_list); + +/*% set unexpected error */ +void isc_error_setunexpected(isc_errorcallback_t); + +/*% set fatal error */ +void isc_error_setfatal(isc_errorcallback_t); + +/*% unexpected error */ +void +isc_error_unexpected(const char *, int, const char *, ...) + ISC_FORMAT_PRINTF(3, 4); + +/*% fatal error */ +ISC_PLATFORM_NORETURN_PRE void +isc_error_fatal(const char *, int, const char *, ...) + ISC_FORMAT_PRINTF(3, 4) ISC_PLATFORM_NORETURN_POST; + +/*% runtimecheck error */ +ISC_PLATFORM_NORETURN_PRE void +isc_error_runtimecheck(const char *, int, + const char *) ISC_PLATFORM_NORETURN_POST; + +#define ISC_ERROR_RUNTIMECHECK(cond) \ + ((void)(ISC_LIKELY(cond) || \ + ((isc_error_runtimecheck)(__FILE__, __LINE__, #cond), 0))) + +ISC_LANG_ENDDECLS + +#endif /* ISC_ERROR_H */ diff --git a/lib/isc/include/isc/event.h b/lib/isc/include/isc/event.h new file mode 100644 index 0000000..e79a8d8 --- /dev/null +++ b/lib/isc/include/isc/event.h @@ -0,0 +1,120 @@ +/* + * 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. + */ + +#ifndef ISC_EVENT_H +#define ISC_EVENT_H 1 + +/*! \file isc/event.h */ + +#include <isc/lang.h> +#include <isc/types.h> + +/***** +***** Events. +*****/ + +typedef void (*isc_eventdestructor_t)(isc_event_t *); + +#define ISC_EVENT_COMMON(ltype) \ + size_t ev_size; \ + unsigned int ev_attributes; \ + void *ev_tag; \ + isc_eventtype_t ev_type; \ + isc_taskaction_t ev_action; \ + void *ev_arg; \ + void *ev_sender; \ + isc_eventdestructor_t ev_destroy; \ + void *ev_destroy_arg; \ + ISC_LINK(ltype) ev_link; \ + ISC_LINK(ltype) ev_ratelink + +/*% + * Attributes matching a mask of 0x000000ff are reserved for the task library's + * definition. Attributes of 0xffffff00 may be used by the application + * or non-ISC libraries. + */ +#define ISC_EVENTATTR_NOPURGE 0x00000001 + +/*% + * The ISC_EVENTATTR_CANCELED attribute is intended to indicate + * that an event is delivered as a result of a canceled operation + * rather than successful completion, by mutual agreement + * between the sender and receiver. It is not set or used by + * the task system. + */ +#define ISC_EVENTATTR_CANCELED 0x00000002 + +#define ISC_EVENT_INIT(event, sz, at, ta, ty, ac, ar, sn, df, da) \ + do { \ + (event)->ev_size = (sz); \ + (event)->ev_attributes = (at); \ + (event)->ev_tag = (ta); \ + (event)->ev_type = (ty); \ + (event)->ev_action = (ac); \ + (event)->ev_arg = (ar); \ + (event)->ev_sender = (sn); \ + (event)->ev_destroy = (df); \ + (event)->ev_destroy_arg = (da); \ + ISC_LINK_INIT((event), ev_link); \ + ISC_LINK_INIT((event), ev_ratelink); \ + } while (0) + +/*% + * This structure is public because "subclassing" it may be useful when + * defining new event types. + */ +struct isc_event { + ISC_EVENT_COMMON(struct isc_event); +}; + +#define ISC_EVENTTYPE_FIRSTEVENT 0x00000000 +#define ISC_EVENTTYPE_LASTEVENT 0xffffffff + +#define ISC_EVENT_PTR(p) ((isc_event_t **)(void *)(p)) + +ISC_LANG_BEGINDECLS + +isc_event_t * +isc_event_allocate(isc_mem_t *mctx, void *sender, isc_eventtype_t type, + isc_taskaction_t action, void *arg, size_t size); +isc_event_t * +isc_event_constallocate(isc_mem_t *mctx, void *sender, isc_eventtype_t type, + isc_taskaction_t action, const void *arg, size_t size); +/*%< + * Allocate an event structure. + * + * Allocate and initialize in a structure with initial elements + * defined by: + * + * \code + * struct { + * ISC_EVENT_COMMON(struct isc_event); + * ... + * }; + * \endcode + * + * Requires: + *\li 'size' >= sizeof(struct isc_event) + *\li 'action' to be non NULL + * + * Returns: + *\li a pointer to a initialized structure of the requested size. + *\li NULL if unable to allocate memory. + */ + +void +isc_event_free(isc_event_t **); + +ISC_LANG_ENDDECLS + +#endif /* ISC_EVENT_H */ diff --git a/lib/isc/include/isc/eventclass.h b/lib/isc/include/isc/eventclass.h new file mode 100644 index 0000000..300848a --- /dev/null +++ b/lib/isc/include/isc/eventclass.h @@ -0,0 +1,48 @@ +/* + * 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. + */ + +#ifndef ISC_EVENTCLASS_H +#define ISC_EVENTCLASS_H 1 + +/*! \file isc/eventclass.h + ***** Registry of Predefined Event Type Classes + *****/ + +/*% + * An event class is an unsigned 16 bit number. Each class may contain up + * to 65536 events. An event type is formed by adding the event number + * within the class to the class number. + * + */ + +#define ISC_EVENTCLASS(eclass) ((eclass) << 16) + +/*@{*/ +/*! + * Classes < 1024 are reserved for ISC use. + * Event classes >= 1024 and <= 65535 are reserved for application use. + */ + +#define ISC_EVENTCLASS_TASK ISC_EVENTCLASS(0) +#define ISC_EVENTCLASS_TIMER ISC_EVENTCLASS(1) +#define ISC_EVENTCLASS_SOCKET ISC_EVENTCLASS(2) +#define ISC_EVENTCLASS_FILE ISC_EVENTCLASS(3) +#define ISC_EVENTCLASS_DNS ISC_EVENTCLASS(4) +#define ISC_EVENTCLASS_APP ISC_EVENTCLASS(5) +#define ISC_EVENTCLASS_OMAPI ISC_EVENTCLASS(6) +#define ISC_EVENTCLASS_RATELIMITER ISC_EVENTCLASS(7) +#define ISC_EVENTCLASS_ISCCC ISC_EVENTCLASS(8) +#define ISC_EVENTCLASS_NS ISC_EVENTCLASS(9) +/*@}*/ + +#endif /* ISC_EVENTCLASS_H */ diff --git a/lib/isc/include/isc/file.h b/lib/isc/include/isc/file.h new file mode 100644 index 0000000..6ef2a39 --- /dev/null +++ b/lib/isc/include/isc/file.h @@ -0,0 +1,400 @@ +/* + * 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. + */ + +#ifndef ISC_FILE_H +#define ISC_FILE_H 1 + +/*! \file isc/file.h */ + +#include <stdbool.h> +#include <stdio.h> + +#include <isc/lang.h> +#include <isc/stat.h> +#include <isc/types.h> + +ISC_LANG_BEGINDECLS + +isc_result_t +isc_file_settime(const char *file, isc_time_t *time); + +isc_result_t +isc_file_mode(const char *file, mode_t *modep); + +isc_result_t +isc_file_getmodtime(const char *file, isc_time_t *time); +/*!< + * \brief Get the time of last modification of a file. + * + * Notes: + *\li The time that is set is relative to the (OS-specific) epoch, as are + * all isc_time_t structures. + * + * Requires: + *\li file != NULL. + *\li time != NULL. + * + * Ensures: + *\li If the file could not be accessed, 'time' is unchanged. + * + * Returns: + *\li #ISC_R_SUCCESS + * Success. + *\li #ISC_R_NOTFOUND + * No such file exists. + *\li #ISC_R_INVALIDFILE + * The path specified was not usable by the operating system. + *\li #ISC_R_NOPERM + * The file's metainformation could not be retrieved because + * permission was denied to some part of the file's path. + *\li #ISC_R_IOERROR + * Hardware error interacting with the filesystem. + *\li #ISC_R_UNEXPECTED + * Something totally unexpected happened. + * + */ + +isc_result_t +isc_file_mktemplate(const char *path, char *buf, size_t buflen); +/*!< + * \brief Generate a template string suitable for use with + * isc_file_openunique(). + * + * Notes: + *\li This function is intended to make creating temporary files + * portable between different operating systems. + * + *\li The path is prepended to an implementation-defined string and + * placed into buf. The string has no path characters in it, + * and its maximum length is 14 characters plus a NUL. Thus + * buflen should be at least strlen(path) + 15 characters or + * an error will be returned. + * + * Requires: + *\li buf != NULL. + * + * Ensures: + *\li If result == #ISC_R_SUCCESS: + * buf contains a string suitable for use as the template argument + * to isc_file_openunique(). + * + *\li If result != #ISC_R_SUCCESS: + * buf is unchanged. + * + * Returns: + *\li #ISC_R_SUCCESS Success. + *\li #ISC_R_NOSPACE buflen indicates buf is too small for the catenation + * of the path with the internal template string. + */ + +isc_result_t +isc_file_openunique(char *templet, FILE **fp); +isc_result_t +isc_file_openuniqueprivate(char *templet, FILE **fp); +isc_result_t +isc_file_openuniquemode(char *templet, int mode, FILE **fp); +isc_result_t +isc_file_bopenunique(char *templet, FILE **fp); +isc_result_t +isc_file_bopenuniqueprivate(char *templet, FILE **fp); +isc_result_t +isc_file_bopenuniquemode(char *templet, int mode, FILE **fp); +/*!< + * \brief Create and open a file with a unique name based on 'templet'. + * isc_file_bopen*() open the file in binary mode in Windows. + * isc_file_open*() open the file in text mode in Windows. + * + * Notes: + *\li 'template' is a reserved work in C++. If you want to complain + * about the spelling of 'templet', first look it up in the + * Merriam-Webster English dictionary. (http://www.m-w.com/) + * + *\li This function works by using the template to generate file names. + * The template must be a writable string, as it is modified in place. + * Trailing X characters in the file name (full file name on Unix, + * basename on Win32 -- eg, tmp-XXXXXX vs XXXXXX.tmp, respectively) + * are replaced with ASCII characters until a non-existent filename + * is found. If the template does not include pathname information, + * the files in the working directory of the program are searched. + * + *\li isc_file_mktemplate is a good, portable way to get a template. + * + * Requires: + *\li 'fp' is non-NULL and '*fp' is NULL. + * + *\li 'template' is non-NULL, and of a form suitable for use by + * the system as described above. + * + * Ensures: + *\li If result is #ISC_R_SUCCESS: + * *fp points to an stream opening in stdio's "w+" mode. + * + *\li If result is not #ISC_R_SUCCESS: + * *fp is NULL. + * + * No file is open. Even if one was created (but unable + * to be reopened as a stdio FILE pointer) then it has been + * removed. + * + *\li This function does *not* ensure that the template string has not been + * modified, even if the operation was unsuccessful. + * + * Returns: + *\li #ISC_R_SUCCESS + * Success. + *\li #ISC_R_EXISTS + * No file with a unique name could be created based on the + * template. + *\li #ISC_R_INVALIDFILE + * The path specified was not usable by the operating system. + *\li #ISC_R_NOPERM + * The file could not be created because permission was denied + * to some part of the file's path. + *\li #ISC_R_IOERROR + * Hardware error interacting with the filesystem. + *\li #ISC_R_UNEXPECTED + * Something totally unexpected happened. + */ + +isc_result_t +isc_file_remove(const char *filename); +/*!< + * \brief Remove the file named by 'filename'. + */ + +isc_result_t +isc_file_rename(const char *oldname, const char *newname); +/*!< + * \brief Rename the file 'oldname' to 'newname'. + */ + +bool +isc_file_exists(const char *pathname); +/*!< + * \brief Return #true if the calling process can tell that the given file + * exists. Will not return true if the calling process has insufficient + * privileges to search the entire path. + */ + +bool +isc_file_isabsolute(const char *filename); +/*!< + * \brief Return #true if the given file name is absolute. + */ + +isc_result_t +isc_file_isplainfile(const char *name); + +isc_result_t +isc_file_isplainfilefd(int fd); +/*!< + * \brief Check that the file is a plain file + * + * Returns: + *\li #ISC_R_SUCCESS + * Success. The file is a plain file. + *\li #ISC_R_INVALIDFILE + * The path specified was not usable by the operating system. + *\li #ISC_R_FILENOTFOUND + * The file does not exist. This return code comes from + * errno=ENOENT when stat returns -1. This code is mentioned + * here, because in logconf.c, it is the one rcode that is + * permitted in addition to ISC_R_SUCCESS. This is done since + * the next call in logconf.c is to isc_stdio_open(), which + * will create the file if it can. + *\li other ISC_R_* errors translated from errno + * These occur when stat returns -1 and an errno. + */ + +isc_result_t +isc_file_isdirectory(const char *name); +/*!< + * \brief Check that 'name' exists and is a directory. + * + * Returns: + *\li #ISC_R_SUCCESS + * Success, file is a directory. + *\li #ISC_R_INVALIDFILE + * File is not a directory. + *\li #ISC_R_FILENOTFOUND + * File does not exist. + *\li other ISC_R_* errors translated from errno + * These occur when stat returns -1 and an errno. + */ + +bool +isc_file_iscurrentdir(const char *filename); +/*!< + * \brief Return #true if the given file name is the current directory ("."). + */ + +bool +isc_file_ischdiridempotent(const char *filename); +/*%< + * Return #true if calling chdir(filename) multiple times will give + * the same result as calling it once. + */ + +const char * +isc_file_basename(const char *filename); +/*%< + * Return the final component of the path in the file name. + */ + +isc_result_t +isc_file_progname(const char *filename, char *buf, size_t buflen); +/*!< + * \brief Given an operating system specific file name "filename" + * referring to a program, return the canonical program name. + * + * Any directory prefix or executable file name extension (if + * used on the OS in case) is stripped. On systems where program + * names are case insensitive, the name is canonicalized to all + * lower case. The name is written to 'buf', an array of 'buflen' + * chars, and null terminated. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOSPACE The name did not fit in 'buf'. + */ + +isc_result_t +isc_file_template(const char *path, const char *templet, char *buf, + size_t buflen); +/*%< + * Create an OS specific template using 'path' to define the directory + * 'templet' to describe the filename and store the result in 'buf' + * such that path can be renamed to buf atomically. + */ + +isc_result_t +isc_file_renameunique(const char *file, char *templet); +/*%< + * Rename 'file' using 'templet' as a template for the new file name. + */ + +isc_result_t +isc_file_absolutepath(const char *filename, char *path, size_t pathlen); +/*%< + * Given a file name, return the fully qualified path to the file. + */ + +/* + * XXX We should also have a isc_file_writeeopen() function + * for safely open a file in a publicly writable directory + * (see write_open() in BIND 8's ns_config.c). + */ + +isc_result_t +isc_file_truncate(const char *filename, isc_offset_t size); +/*%< + * Truncate/extend the file specified to 'size' bytes. + */ + +isc_result_t +isc_file_safecreate(const char *filename, FILE **fp); +/*%< + * Open 'filename' for writing, truncating if necessary. Ensure that + * if it existed it was a normal file. If creating the file, ensure + * that only the owner can read/write it. + */ + +isc_result_t +isc_file_splitpath(isc_mem_t *mctx, const char *path, char **dirname, + char const **basename); +/*%< + * Split a path into dirname and basename. If 'path' contains no slash + * (or, on windows, backslash), then '*dirname' is set to ".". + * + * Allocates memory for '*dirname', which can be freed with isc_mem_free(). + * + * Returns: + * - ISC_R_SUCCESS on success + * - ISC_R_INVALIDFILE if 'path' is empty or ends with '/' + * - ISC_R_NOMEMORY if unable to allocate memory + */ + +isc_result_t +isc_file_getsize(const char *file, off_t *size); +/*%< + * Return the size of the file (stored in the parameter pointed + * to by 'size') in bytes. + * + * Returns: + * - ISC_R_SUCCESS on success + */ + +isc_result_t +isc_file_getsizefd(int fd, off_t *size); +/*%< + * Return the size of the file (stored in the parameter pointed + * to by 'size') in bytes. + * + * Returns: + * - ISC_R_SUCCESS on success + */ + +void * +isc_file_mmap(void *addr, size_t len, int prot, int flags, int fd, + off_t offset); +/*%< + * Portable front-end to mmap(). If mmap() is not defined on this + * platform, then we simulate it by calling malloc() and read(). + * (In this event, the addr, prot, and flags parameters are ignored). + */ + +int +isc_file_munmap(void *addr, size_t len); +/*%< + * Portable front-end to munmap(). If munmap() is not defined on + * this platform, then we simply free the memory. + */ + +isc_result_t +isc_file_sanitize(const char *dir, const char *base, const char *ext, + char *path, size_t length); +/*%< + * Generate a sanitized filename, such as for MKEYS or NZF files. + * + * Historically, MKEYS and NZF files used SHA256 hashes of the view + * name for the filename; this was to deal with the possibility of + * forbidden characters such as "/" being in a view name, and to + * avoid problems with case-insensitive file systems. + * + * Given a basename 'base' and an extension 'ext', this function checks + * for the existence of file using the old-style name format in directory + * 'dir'. If found, it returns the path to that file. If there is no + * file already in place, a new pathname is generated; if the basename + * contains any excluded characters, then a truncated SHA256 hash is + * used, otherwise the basename is used. The path name is copied + * into 'path', which must point to a buffer of at least 'length' + * bytes. + * + * Requires: + * - base != NULL + * - path != NULL + * + * Returns: + * - ISC_R_SUCCESS on success + * - ISC_R_NOSPACE if the resulting path would be longer than 'length' + */ + +bool +isc_file_isdirwritable(const char *path); +/*%< + * Return true if the path is a directory and is writable + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_FILE_H */ diff --git a/lib/isc/include/isc/formatcheck.h b/lib/isc/include/isc/formatcheck.h new file mode 100644 index 0000000..28bd573 --- /dev/null +++ b/lib/isc/include/isc/formatcheck.h @@ -0,0 +1,36 @@ +/* + * 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. + */ + +#ifndef ISC_FORMATCHECK_H +#define ISC_FORMATCHECK_H 1 + +/*! \file isc/formatcheck.h */ + +/*% + * ISC_FORMAT_PRINTF(). + * + * \li fmt is the location of the format string parameter. + * \li args is the location of the first argument (or 0 for no argument + * checking). + * + * Note: + * \li The first parameter is 1, not 0. + */ +#ifdef __GNUC__ +#define ISC_FORMAT_PRINTF(fmt, args) \ + __attribute__((__format__(__printf__, fmt, args))) +#else /* ifdef __GNUC__ */ +#define ISC_FORMAT_PRINTF(fmt, args) +#endif /* ifdef __GNUC__ */ + +#endif /* ISC_FORMATCHECK_H */ diff --git a/lib/isc/include/isc/fsaccess.h b/lib/isc/include/isc/fsaccess.h new file mode 100644 index 0000000..46c8774 --- /dev/null +++ b/lib/isc/include/isc/fsaccess.h @@ -0,0 +1,174 @@ +/* + * 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. + */ + +#ifndef ISC_FSACCESS_H +#define ISC_FSACCESS_H 1 + +/*! \file isc/fsaccess.h + * \brief The ISC filesystem access module encapsulates the setting of file + * and directory access permissions into one API that is meant to be + * portable to multiple operating systems. + * + * The two primary operating system flavors that are initially accommodated + * are POSIX and Windows NT 4.0 and later. The Windows NT access model is + * considerable more flexible than POSIX's model (as much as I am loathe to + * admit it), and so the ISC API has a higher degree of complexity than would + * be needed to simply address POSIX's needs. + * + * The full breadth of NT's flexibility is not available either, for the + * present time. Much of it is to provide compatibility with what Unix + * programmers are expecting. This is also due to not yet really needing all + * of the functionality of an NT system (or, for that matter, a POSIX system) + * in BIND9, and so resolving how to handle the various incompatibilities has + * been a purely theoretical exercise with no operational experience to + * indicate how flawed the thinking may be. + * + * Some of the more notable dumbing down of NT for this API includes: + * + *\li Each of FILE_READ_DATA and FILE_READ_EA are set with #ISC_FSACCESS_READ. + * + * \li All of FILE_WRITE_DATA, FILE_WRITE_EA and FILE_APPEND_DATA are + * set with #ISC_FSACCESS_WRITE. FILE_WRITE_ATTRIBUTES is not set + * so as to be consistent with Unix, where only the owner of the file + * or the superuser can change the attributes/mode of a file. + * + * \li Both of FILE_ADD_FILE and FILE_ADD_SUBDIRECTORY are set with + * #ISC_FSACCESS_CREATECHILD. This is similar to setting the WRITE + * permission on a Unix directory. + * + * \li SYNCHRONIZE is always set for files and directories, unless someone + * can give me a reason why this is a bad idea. + * + * \li READ_CONTROL and FILE_READ_ATTRIBUTES are always set; this is + * consistent with Unix, where any file or directory can be stat()'d + * unless the directory path disallows complete access somewhere along + * the way. + * + * \li WRITE_DAC is only set for the owner. This too is consistent with + * Unix, and is tighter security than allowing anyone else to be + * able to set permissions. + * + * \li DELETE is only set for the owner. On Unix the ability to delete + * a file is controlled by the directory permissions, but it isn't + * currently clear to me what happens on NT if the directory has + * FILE_DELETE_CHILD set but a file within it does not have DELETE + * set. Always setting DELETE on the file/directory for the owner + * gives maximum flexibility to the owner without exposing the + * file to deletion by others. + * + * \li WRITE_OWNER is never set. This too is consistent with Unix, + * and is also tighter security than allowing anyone to change the + * ownership of the file apart from the superu..ahem, Administrator. + * + * \li Inheritance is set to NO_INHERITANCE. + * + * Unix's dumbing down includes: + * + * \li The sticky bit cannot be set. + * + * \li setuid and setgid cannot be set. + * + * \li Only regular files and directories can be set. + * + * The rest of this comment discusses a few of the incompatibilities + * between the two systems that need more thought if this API is to + * be extended to accommodate them. + * + * The Windows standard access right "DELETE" doesn't have a direct + * equivalent in the Unix world, so it isn't clear what should be done + * with it. + * + * The Unix sticky bit is not supported. While NT does have a concept + * of allowing users to create files in a directory but not delete or + * rename them, it does not have a concept of allowing them to be deleted + * if they are owned by the user trying to delete/rename. While it is + * probable that something could be cobbled together in NT 5 with inheritance, + * it can't really be done in NT 4 as a single property that you could + * set on a directory. You'd need to coordinate something with file creation + * so that every file created had DELETE set for the owner but no one else. + * + * On Unix systems, setting #ISC_FSACCESS_LISTDIRECTORY sets READ. + * ... setting either #ISC_FSACCESS_CREATECHILD or #ISC_FSACCESS_DELETECHILD + * sets WRITE. + * ... setting #ISC_FSACCESS_ACCESSCHILD sets EXECUTE. + * + * On NT systems, setting #ISC_FSACCESS_LISTDIRECTORY sets FILE_LIST_DIRECTORY. + * ... setting #ISC_FSACCESS_CREATECHILD sets FILE_CREATE_CHILD independently. + * ... setting #ISC_FSACCESS_DELETECHILD sets FILE_DELETE_CHILD independently. + * ... setting #ISC_FSACCESS_ACCESSCHILD sets FILE_TRAVERSE. + * + * Unresolved: XXXDCL + * \li What NT access right controls the ability to rename a file? + * \li How does DELETE work? If a directory has FILE_DELETE_CHILD but a + * file or directory within it does not have DELETE, is that file + * or directory deletable? + * \li To implement isc_fsaccess_get(), mapping an existing Unix permission + * mode_t back to an isc_fsaccess_t is pretty trivial; however, mapping + * an NT DACL could be impossible to do in a responsible way. + * \li Similarly, trying to implement the functionality of being able to + * say "add group writability to whatever permissions already exist" + * could be tricky on NT because of the order-of-entry issue combined + * with possibly having one or more matching ACEs already explicitly + * granting or denying access. Because this functionality is + * not yet needed by the ISC, no code has been written to try to + * solve this problem. + */ + +#include <inttypes.h> + +#include <isc/lang.h> +#include <isc/types.h> + +/* + * Trustees. + */ +#define ISC_FSACCESS_OWNER 0x1 /*%< User account. */ +#define ISC_FSACCESS_GROUP 0x2 /*%< Primary group owner. */ +#define ISC_FSACCESS_OTHER 0x4 /*%< Not the owner or the group owner. */ +#define ISC_FSACCESS_WORLD 0x7 /*%< User, Group, Other. */ + +/* + * Types of permission. + */ +#define ISC_FSACCESS_READ 0x00000001 /*%< File only. */ +#define ISC_FSACCESS_WRITE 0x00000002 /*%< File only. */ +#define ISC_FSACCESS_EXECUTE 0x00000004 /*%< File only. */ +#define ISC_FSACCESS_CREATECHILD 0x00000008 /*%< Dir only. */ +#define ISC_FSACCESS_DELETECHILD 0x00000010 /*%< Dir only. */ +#define ISC_FSACCESS_LISTDIRECTORY 0x00000020 /*%< Dir only. */ +#define ISC_FSACCESS_ACCESSCHILD 0x00000040 /*%< Dir only. */ + +/*% + * Adding any permission bits beyond 0x200 would mean typedef'ing + * isc_fsaccess_t as uint64_t, and redefining this value to + * reflect the new range of permission types, Probably to 21 for + * maximum flexibility. The number of bits has to accommodate all of + * the permission types, and three full sets of them have to fit + * within an isc_fsaccess_t. + */ +#define ISC__FSACCESS_PERMISSIONBITS 10 + +ISC_LANG_BEGINDECLS + +void +isc_fsaccess_add(int trustee, int permission, isc_fsaccess_t *access); + +void +isc_fsaccess_remove(int trustee, int permission, isc_fsaccess_t *access); + +isc_result_t +isc_fsaccess_set(const char *path, isc_fsaccess_t access); + +ISC_LANG_ENDDECLS + +#endif /* ISC_FSACCESS_H */ diff --git a/lib/isc/include/isc/fuzz.h b/lib/isc/include/isc/fuzz.h new file mode 100644 index 0000000..207728e --- /dev/null +++ b/lib/isc/include/isc/fuzz.h @@ -0,0 +1,26 @@ +/* + * 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. + */ + +#ifndef ISC_FUZZ_H +#define ISC_FUZZ_H + +typedef enum { + isc_fuzz_none, + isc_fuzz_client, + isc_fuzz_tcpclient, + isc_fuzz_resolver, + isc_fuzz_http, + isc_fuzz_rndc +} isc_fuzztype_t; + +#endif /* ISC_FUZZ_H */ diff --git a/lib/isc/include/isc/hash.h b/lib/isc/include/isc/hash.h new file mode 100644 index 0000000..62ba186 --- /dev/null +++ b/lib/isc/include/isc/hash.h @@ -0,0 +1,66 @@ +/* + * 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. + */ + +#ifndef ISC_HASH_H +#define ISC_HASH_H 1 + +#include <inttypes.h> +#include <stdbool.h> + +#include "isc/lang.h" +#include "isc/types.h" + +/*** + *** Functions + ***/ +ISC_LANG_BEGINDECLS + +const void * +isc_hash_get_initializer(void); + +void +isc_hash_set_initializer(const void *initializer); + +#define isc_hash_function isc_hash64 + +uint32_t +isc_hash32(const void *data, const size_t length, const bool case_sensitive); +uint64_t +isc_hash64(const void *data, const size_t length, const bool case_sensitive); +/*!< + * \brief Calculate a hash over data. + * + * This hash function is useful for hashtables. The hash function is + * opaque and not important to the caller. The returned hash values are + * non-deterministic and will have different mapping every time a + * process using this library is run, but will have uniform + * distribution. + * + * isc_hash_32/64() calculates the hash from start to end over the + * input data. + * + * 'data' is the data to be hashed. + * + * 'length' is the size of the data to be hashed. + * + * 'case_sensitive' specifies whether the hash key should be treated as + * case_sensitive values. It should typically be false if the hash key + * is a DNS name. + * + * Returns: + * \li 32 or 64-bit hash value + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_HASH_H */ diff --git a/lib/isc/include/isc/heap.h b/lib/isc/include/isc/heap.h new file mode 100644 index 0000000..d492c60 --- /dev/null +++ b/lib/isc/include/isc/heap.h @@ -0,0 +1,164 @@ +/* + * 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. + */ + +#ifndef ISC_HEAP_H +#define ISC_HEAP_H 1 + +/*! \file isc/heap.h */ + +#include <stdbool.h> + +#include <isc/lang.h> +#include <isc/types.h> + +ISC_LANG_BEGINDECLS + +/*% + * The comparison function returns true if the first argument has + * higher priority than the second argument, and false otherwise. + */ +typedef bool (*isc_heapcompare_t)(void *, void *); + +/*% + * The index function allows the client of the heap to receive a callback + * when an item's index number changes. This allows it to maintain + * sync with its external state, but still delete itself, since deletions + * from the heap require the index be provided. + */ +typedef void (*isc_heapindex_t)(void *, unsigned int); + +/*% + * The heapaction function is used when iterating over the heap. + * + * NOTE: The heap structure CANNOT BE MODIFIED during the call to + * isc_heap_foreach(). + */ +typedef void (*isc_heapaction_t)(void *, void *); + +typedef struct isc_heap isc_heap_t; + +void +isc_heap_create(isc_mem_t *mctx, isc_heapcompare_t compare, + isc_heapindex_t index, unsigned int size_increment, + isc_heap_t **heapp); +/*!< + * \brief Create a new heap. The heap is implemented using a space-efficient + * storage method. When the heap elements are deleted space is not freed + * but will be reused when new elements are inserted. + * + * Heap elements are indexed from 1. + * + * Requires: + *\li "mctx" is valid. + *\li "compare" is a function which takes two void * arguments and + * returns true if the first argument has a higher priority than + * the second, and false otherwise. + *\li "index" is a function which takes a void *, and an unsigned int + * argument. This function will be called whenever an element's + * index value changes, so it may continue to delete itself from the + * heap. This option may be NULL if this functionality is unneeded. + *\li "size_increment" is a hint about how large the heap should grow + * when resizing is needed. If this is 0, a default size will be + * used, which is currently 1024, allowing space for an additional 1024 + * heap elements to be inserted before adding more space. + *\li "heapp" is not NULL, and "*heap" is NULL. + */ + +void +isc_heap_destroy(isc_heap_t **heapp); +/*!< + * \brief Destroys a heap. + * + * Requires: + *\li "heapp" is not NULL and "*heap" points to a valid isc_heap_t. + */ + +void +isc_heap_insert(isc_heap_t *heap, void *elt); +/*!< + * \brief Inserts a new element into a heap. + * + * Requires: + *\li "heapp" is not NULL and "*heap" points to a valid isc_heap_t. + */ + +void +isc_heap_delete(isc_heap_t *heap, unsigned int index); +/*!< + * \brief Deletes an element from a heap, by element index. + * + * Requires: + *\li "heapp" is not NULL and "*heap" points to a valid isc_heap_t. + *\li "index" is a valid element index, as provided by the "index" callback + * provided during heap creation. + */ + +void +isc_heap_increased(isc_heap_t *heap, unsigned int index); +/*!< + * \brief Indicates to the heap that an element's priority has increased. + * This function MUST be called whenever an element has increased in priority. + * + * Requires: + *\li "heapp" is not NULL and "*heap" points to a valid isc_heap_t. + *\li "index" is a valid element index, as provided by the "index" callback + * provided during heap creation. + */ + +void +isc_heap_decreased(isc_heap_t *heap, unsigned int index); +/*!< + * \brief Indicates to the heap that an element's priority has decreased. + * This function MUST be called whenever an element has decreased in priority. + * + * Requires: + *\li "heapp" is not NULL and "*heap" points to a valid isc_heap_t. + *\li "index" is a valid element index, as provided by the "index" callback + * provided during heap creation. + */ + +void * +isc_heap_element(isc_heap_t *heap, unsigned int index); +/*!< + * \brief Returns the element for a specific element index. + * + * Requires: + *\li "heapp" is not NULL and "*heap" points to a valid isc_heap_t. + *\li "index" is a valid element index, as provided by the "index" callback + * provided during heap creation. + * + * Returns: + *\li A pointer to the element for the element index. + */ + +void +isc_heap_foreach(isc_heap_t *heap, isc_heapaction_t action, void *uap); +/*!< + * \brief Iterate over the heap, calling an action for each element. The + * order of iteration is not sorted. + * + * Requires: + *\li "heapp" is not NULL and "*heap" points to a valid isc_heap_t. + *\li "action" is not NULL, and is a function which takes two arguments. + * The first is a void *, representing the element, and the second is + * "uap" as provided to isc_heap_foreach. + *\li "uap" is a caller-provided argument, and may be NULL. + * + * Note: + *\li The heap structure CANNOT be modified during this iteration. The only + * safe function to call while iterating the heap is isc_heap_element(). + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_HEAP_H */ diff --git a/lib/isc/include/isc/hex.h b/lib/isc/include/isc/hex.h new file mode 100644 index 0000000..a0e5e39 --- /dev/null +++ b/lib/isc/include/isc/hex.h @@ -0,0 +1,101 @@ +/* + * 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. + */ + +#ifndef ISC_HEX_H +#define ISC_HEX_H 1 + +/*! \file isc/hex.h */ + +#include <isc/lang.h> +#include <isc/types.h> + +ISC_LANG_BEGINDECLS + +/*** + *** Functions + ***/ + +isc_result_t +isc_hex_totext(isc_region_t *source, int wordlength, const char *wordbreak, + isc_buffer_t *target); +/*!< + * \brief Convert data into hex encoded text. + * + * Notes: + *\li The hex encoded text in 'target' will be divided into + * words of at most 'wordlength' characters, separated by + * the 'wordbreak' string. No parentheses will surround + * the text. + * + * Requires: + *\li 'source' is a region containing binary data + *\li 'target' is a text buffer containing available space + *\li 'wordbreak' points to a null-terminated string of + * zero or more whitespace characters + * + * Ensures: + *\li target will contain the hex encoded version of the data + * in source. The 'used' pointer in target will be advanced as + * necessary. + */ + +isc_result_t +isc_hex_decodestring(const char *cstr, isc_buffer_t *target); +/*!< + * \brief Decode a null-terminated hex string. + * + * Requires: + *\li 'cstr' is non-null. + *\li 'target' is a valid buffer. + * + * Returns: + *\li #ISC_R_SUCCESS -- the entire decoded representation of 'cstring' + * fit in 'target'. + *\li #ISC_R_BADHEX -- 'cstr' is not a valid hex encoding. + * + * Other error returns are any possible error code from: + * isc_lex_create(), + * isc_lex_openbuffer(), + * isc_hex_tobuffer(). + */ + +isc_result_t +isc_hex_tobuffer(isc_lex_t *lexer, isc_buffer_t *target, int length); +/*!< + * \brief Convert hex-encoded text from a lexer context into + * `target`. If 'length' is non-negative, it is the expected number of + * encoded octets to convert. + * + * If 'length' is -1 then 0 or more encoded octets are expected. + * If 'length' is -2 then 1 or more encoded octets are expected. + * + * Returns: + *\li #ISC_R_BADHEX -- invalid hex encoding + *\li #ISC_R_UNEXPECTEDEND: the text does not contain the expected + * number of encoded octets. + * + * Requires: + *\li 'lexer' is a valid lexer context + *\li 'target' is a buffer containing binary data + *\li 'length' is -2, -1, or non-negative + * + * Ensures: + *\li target will contain the data represented by the hex encoded + * string parsed by the lexer. No more than `length` octets will + * be read, if `length` is non-negative. The 'used' pointer in + * 'target' will be advanced as necessary. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_HEX_H */ diff --git a/lib/isc/include/isc/hmac.h b/lib/isc/include/isc/hmac.h new file mode 100644 index 0000000..3d0e741 --- /dev/null +++ b/lib/isc/include/isc/hmac.h @@ -0,0 +1,147 @@ +/* + * 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. + */ + +/*! + * \file isc/hmac.h + * \brief This is the header for for message authentication code. + */ + +#pragma once + +#include <isc/lang.h> +#include <isc/md.h> +#include <isc/platform.h> +#include <isc/result.h> +#include <isc/types.h> + +typedef void isc_hmac_t; + +/** + * isc_hmac: + * @type: the digest type + * @key: the key + * @keylen: the length of the key + * @buf: data to hash + * @len: length of the data to hash + * @digest: the output buffer + * @digestlen: the length of the data written to @digest + * + * This function computes the message authentication code using a digest type + * @type with key @key which is @keylen bytes long from data in @buf which is + * @len bytes long, and places the output into @digest, which must have space + * for the hash function output (use ISC_MAX_MD_SIZE if unsure). If the + * @digestlen parameter is not NULL then the number of bytes of data written + * (i.e. the length of the digest) will be written to the @digestlen. + */ +isc_result_t +isc_hmac(const isc_md_type_t *type, const void *key, const int keylen, + const unsigned char *buf, const size_t len, unsigned char *digest, + unsigned int *digestlen); + +/** + * isc_hmac_new: + * + * This function allocates, initializes and returns HMAC context. + */ +isc_hmac_t * +isc_hmac_new(void); + +/** + * isc_hmac_free: + * @md: HMAC context + * + * This function cleans up HMAC context and frees up the space allocated to it. + */ +void +isc_hmac_free(isc_hmac_t *hmac); + +/** + * isc_hmac_init: + * @md: HMAC context + * @key: HMAC key + * @keylen: HMAC key length + * @type: digest type + * + * This function sets up HMAC context to use a hash function of @type and key + * @key which is @keylen bytes long. + */ + +isc_result_t +isc_hmac_init(isc_hmac_t *hmac, const void *key, size_t keylen, + const isc_md_type_t *type); + +/** + * isc_hmac_reset: + * @hmac: HMAC context + * + * This function resets the HMAC context. This can be used to reuse an already + * existing context. + */ +isc_result_t +isc_hmac_reset(isc_hmac_t *hmac); + +/** + * isc_hmac_update: + * @hmac: HMAC context + * @buf: data to hash + * @len: length of the data to hash + * + * This function can be called repeatedly with chunks of the message @buf to be + * authenticated which is @len bytes long. + */ +isc_result_t +isc_hmac_update(isc_hmac_t *hmac, const unsigned char *buf, const size_t len); + +/** + * isc_hmac_final: + * @hmac: HMAC context + * @digest: the output buffer + * @digestlen: the length of the data written to @digest + * + * This function retrieves the message authentication code from @hmac and places + * it in @digest, which must have space for the hash function output. If the + * @digestlen parameter is not NULL then the number of bytes of data written + * (i.e. the length of the digest) will be written to the @digestlen. After + * calling this function no additional calls to isc_hmac_update() can be made. + */ +isc_result_t +isc_hmac_final(isc_hmac_t *hmac, unsigned char *digest, + unsigned int *digestlen); + +/** + * isc_hmac_md_type: + * @hmac: HMAC context + * + * This function return the isc_md_type_t previously set for the supplied + * HMAC context or NULL if no isc_md_type_t has been set. + */ +const isc_md_type_t * +isc_hmac_get_md_type(isc_hmac_t *hmac); + +/** + * isc_hmac_get_size: + * + * This function return the size of the message digest when passed an isc_hmac_t + * structure, i.e. the size of the hash. + */ +size_t +isc_hmac_get_size(isc_hmac_t *hmac); + +/** + * isc_hmac_get_block_size: + * + * This function return the block size of the message digest when passed an + * isc_hmac_t structure. + */ +int +isc_hmac_get_block_size(isc_hmac_t *hmac); diff --git a/lib/isc/include/isc/ht.h b/lib/isc/include/isc/ht.h new file mode 100644 index 0000000..f1386bb --- /dev/null +++ b/lib/isc/include/isc/ht.h @@ -0,0 +1,183 @@ +/* + * 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. + */ + +/* ! \file */ + +#ifndef ISC_HT_H +#define ISC_HT_H 1 + +#include <inttypes.h> +#include <string.h> + +#include <isc/result.h> +#include <isc/types.h> + +typedef struct isc_ht isc_ht_t; +typedef struct isc_ht_iter isc_ht_iter_t; + +/*% + * Initialize hashtable at *htp, using memory context and size of (1<<bits) + * + * Requires: + *\li 'htp' is not NULL and '*htp' is NULL. + *\li 'mctx' is a valid memory context. + *\li 'bits' >=1 and 'bits' <=32 + * + */ +void +isc_ht_init(isc_ht_t **htp, isc_mem_t *mctx, uint8_t bits); + +/*% + * Destroy hashtable, freeing everything + * + * Requires: + * \li '*htp' is valid hashtable + */ +void +isc_ht_destroy(isc_ht_t **htp); + +/*% + * Add a node to hashtable, pointed by binary key 'key' of size 'keysize'; + * set its value to 'value' + * + * Requires: + *\li 'ht' is a valid hashtable + * + * Returns: + *\li #ISC_R_NOMEMORY -- not enough memory to create pool + *\li #ISC_R_EXISTS -- node of the same key already exists + *\li #ISC_R_SUCCESS -- all is well. + */ +isc_result_t +isc_ht_add(isc_ht_t *ht, const unsigned char *key, uint32_t keysize, + void *value); + +/*% + * Find a node matching 'key'/'keysize' in hashtable 'ht'; + * if found, set '*valuep' to its value. (If 'valuep' is NULL, + * then simply return SUCCESS or NOTFOUND to indicate whether the + * key exists in the hashtable.) + * + * Requires: + * \li 'ht' is a valid hashtable + * + * Returns: + * \li #ISC_R_SUCCESS -- success + * \li #ISC_R_NOTFOUND -- key not found + */ +isc_result_t +isc_ht_find(const isc_ht_t *ht, const unsigned char *key, uint32_t keysize, + void **valuep); + +/*% + * Delete node from hashtable + * + * Requires: + *\li ht is a valid hashtable + * + * Returns: + *\li #ISC_R_NOTFOUND -- key not found + *\li #ISC_R_SUCCESS -- all is well + */ +isc_result_t +isc_ht_delete(isc_ht_t *ht, const unsigned char *key, uint32_t keysize); + +/*% + * Create an iterator for the hashtable; point '*itp' to it. + * + * Requires: + *\li 'ht' is a valid hashtable + *\li 'itp' is non NULL and '*itp' is NULL. + */ +void +isc_ht_iter_create(isc_ht_t *ht, isc_ht_iter_t **itp); + +/*% + * Destroy the iterator '*itp', set it to NULL + * + * Requires: + *\li 'itp' is non NULL and '*itp' is non NULL. + */ +void +isc_ht_iter_destroy(isc_ht_iter_t **itp); + +/*% + * Set an iterator to the first entry. + * + * Requires: + *\li 'it' is non NULL. + * + * Returns: + * \li #ISC_R_SUCCESS -- success + * \li #ISC_R_NOMORE -- no data in the hashtable + */ +isc_result_t +isc_ht_iter_first(isc_ht_iter_t *it); + +/*% + * Set an iterator to the next entry. + * + * Requires: + *\li 'it' is non NULL. + * + * Returns: + * \li #ISC_R_SUCCESS -- success + * \li #ISC_R_NOMORE -- end of hashtable reached + */ +isc_result_t +isc_ht_iter_next(isc_ht_iter_t *it); + +/*% + * Delete current entry and set an iterator to the next entry. + * + * Requires: + *\li 'it' is non NULL. + * + * Returns: + * \li #ISC_R_SUCCESS -- success + * \li #ISC_R_NOMORE -- end of hashtable reached + */ +isc_result_t +isc_ht_iter_delcurrent_next(isc_ht_iter_t *it); + +/*% + * Set 'value' to the current value under the iterator + * + * Requires: + *\li 'it' is non NULL. + *\li 'valuep' is non NULL and '*valuep' is NULL. + */ +void +isc_ht_iter_current(isc_ht_iter_t *it, void **valuep); + +/*% + * Set 'key' and 'keysize to the current key and keysize for the value + * under the iterator + * + * Requires: + *\li 'it' is non NULL. + *\li 'key' is non NULL and '*key' is NULL. + *\li 'keysize' is non NULL. + */ +void +isc_ht_iter_currentkey(isc_ht_iter_t *it, unsigned char **key, size_t *keysize); + +/*% + * Returns the number of items in the hashtable. + * + * Requires: + *\li 'ht' is a valid hashtable + */ +unsigned int +isc_ht_count(isc_ht_t *ht); +#endif /* ifndef ISC_HT_H */ diff --git a/lib/isc/include/isc/httpd.h b/lib/isc/include/isc/httpd.h new file mode 100644 index 0000000..7b66049 --- /dev/null +++ b/lib/isc/include/isc/httpd.h @@ -0,0 +1,85 @@ +/* + * 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. + */ + +#ifndef ISC_HTTPD_H +#define ISC_HTTPD_H 1 + +/*! \file */ + +#include <stdbool.h> + +#include <isc/event.h> +#include <isc/eventclass.h> +#include <isc/mutex.h> +#include <isc/task.h> +#include <isc/time.h> +#include <isc/types.h> + +/*% + * HTTP urls. These are the URLs we manage, and the function to call to + * provide the data for it. We pass in the base url (so the same function + * can handle multiple requests), and a structure to fill in to return a + * result to the client. We also pass in a pointer to be filled in for + * the data cleanup function. + */ +struct isc_httpdurl { + char *url; + isc_httpdaction_t *action; + void *action_arg; + bool isstatic; + isc_time_t loadtime; + ISC_LINK(isc_httpdurl_t) link; +}; + +#define HTTPD_EVENTCLASS ISC_EVENTCLASS(4300) +#define HTTPD_SHUTDOWN (HTTPD_EVENTCLASS + 0x0001) + +#define ISC_HTTPDMGR_FLAGSHUTTINGDOWN 0x00000001 + +/* + * Create a new http daemon which will send, once every time period, + * a http-like header followed by HTTP data. + */ +isc_result_t +isc_httpdmgr_create(isc_mem_t *mctx, isc_socket_t *sock, isc_task_t *task, + isc_httpdclientok_t *client_ok, + isc_httpdondestroy_t *ondestory, void *cb_arg, + isc_timermgr_t *tmgr, isc_httpdmgr_t **httpdp); + +void +isc_httpdmgr_shutdown(isc_httpdmgr_t **httpdp); + +isc_result_t +isc_httpdmgr_addurl(isc_httpdmgr_t *httpdmgr, const char *url, + isc_httpdaction_t *func, void *arg); + +isc_result_t +isc_httpdmgr_addurl2(isc_httpdmgr_t *httpdmgr, const char *url, bool isstatic, + isc_httpdaction_t *func, void *arg); + +isc_result_t +isc_httpd_response(isc_httpd_t *httpd); + +isc_result_t +isc_httpd_addheader(isc_httpd_t *httpd, const char *name, const char *val); + +isc_result_t +isc_httpd_addheaderuint(isc_httpd_t *httpd, const char *name, int val); + +isc_result_t +isc_httpd_endheaders(isc_httpd_t *httpd); + +void +isc_httpd_setfinishhook(void (*fn)(void)); + +#endif /* ISC_HTTPD_H */ diff --git a/lib/isc/include/isc/interfaceiter.h b/lib/isc/include/isc/interfaceiter.h new file mode 100644 index 0000000..5f6cbe2 --- /dev/null +++ b/lib/isc/include/isc/interfaceiter.h @@ -0,0 +1,130 @@ +/* + * 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. + */ + +#ifndef ISC_INTERFACEITER_H +#define ISC_INTERFACEITER_H 1 + +/***** +***** Module Info +*****/ + +/*! \file isc/interfaceiter.h + * \brief Iterates over the list of network interfaces. + * + * Interfaces whose address family is not supported are ignored and never + * returned by the iterator. Interfaces whose netmask, interface flags, + * or similar cannot be obtained are also ignored, and the failure is logged. + * + * Standards: + * The API for scanning varies greatly among operating systems. + * This module attempts to hide the differences. + */ + +/*** + *** Imports + ***/ + +#include <inttypes.h> + +#include <isc/lang.h> +#include <isc/netaddr.h> +#include <isc/types.h> + +/*! + * \brief Public structure describing a network interface. + */ + +struct isc_interface { + char name[32]; /*%< Interface name, null-terminated. */ + unsigned int af; /*%< Address family. */ + isc_netaddr_t address; /*%< Local address. */ + isc_netaddr_t netmask; /*%< Network mask. */ + isc_netaddr_t dstaddress; /*%< Destination address + * (point-to-point + * only). */ + uint32_t flags; /*%< Flags; see INTERFACE flags. */ +}; + +/*@{*/ +/*! Interface flags. */ + +#define INTERFACE_F_UP 0x00000001U +#define INTERFACE_F_POINTTOPOINT 0x00000002U +#define INTERFACE_F_LOOPBACK 0x00000004U +/*@}*/ + +/*** + *** Functions + ***/ + +ISC_LANG_BEGINDECLS + +isc_result_t +isc_interfaceiter_create(isc_mem_t *mctx, isc_interfaceiter_t **iterp); +/*!< + * \brief Create an iterator for traversing the operating system's list + * of network interfaces. + * + * Returns: + *\li #ISC_R_SUCCESS + * \li #ISC_R_NOMEMORY + *\li Various network-related errors + */ + +isc_result_t +isc_interfaceiter_first(isc_interfaceiter_t *iter); +/*!< + * \brief Position the iterator on the first interface. + * + * Returns: + *\li #ISC_R_SUCCESS Success. + *\li #ISC_R_NOMORE There are no interfaces. + */ + +isc_result_t +isc_interfaceiter_current(isc_interfaceiter_t *iter, isc_interface_t *ifdata); +/*!< + * \brief Get information about the interface the iterator is currently + * positioned at and store it at *ifdata. + * + * Requires: + *\li The iterator has been successfully positioned using + * isc_interface_iter_first() / isc_interface_iter_next(). + * + * Returns: + *\li #ISC_R_SUCCESS Success. + */ + +isc_result_t +isc_interfaceiter_next(isc_interfaceiter_t *iter); +/*!< + * \brief Position the iterator on the next interface. + * + * Requires: + * \li The iterator has been successfully positioned using + * isc_interface_iter_first() / isc_interface_iter_next(). + * + * Returns: + *\li #ISC_R_SUCCESS Success. + *\li #ISC_R_NOMORE There are no more interfaces. + */ + +void +isc_interfaceiter_destroy(isc_interfaceiter_t **iterp); +/*!< + * \brief Destroy the iterator. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_INTERFACEITER_H */ diff --git a/lib/isc/include/isc/iterated_hash.h b/lib/isc/include/isc/iterated_hash.h new file mode 100644 index 0000000..b5d6ab6 --- /dev/null +++ b/lib/isc/include/isc/iterated_hash.h @@ -0,0 +1,38 @@ +/* + * 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. + */ + +#pragma once + +#include <isc/lang.h> + +/* + * The maximal hash length that can be encoded in a name + * using base32hex. floor(255/8)*5 + */ +#define NSEC3_MAX_HASH_LENGTH 155 + +/* + * The maximum has that can be encoded in a single label using + * base32hex. floor(63/8)*5 + */ +#define NSEC3_MAX_LABEL_HASH 35 + +ISC_LANG_BEGINDECLS + +int +isc_iterated_hash(unsigned char *out, const unsigned int hashalg, + const int iterations, const unsigned char *salt, + const int saltlength, const unsigned char *in, + const int inlength); + +ISC_LANG_ENDDECLS diff --git a/lib/isc/include/isc/lang.h b/lib/isc/include/isc/lang.h new file mode 100644 index 0000000..35346db --- /dev/null +++ b/lib/isc/include/isc/lang.h @@ -0,0 +1,27 @@ +/* + * 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. + */ + +#ifndef ISC_LANG_H +#define ISC_LANG_H 1 + +/*! \file isc/lang.h */ + +#ifdef __cplusplus +#define ISC_LANG_BEGINDECLS extern "C" { +#define ISC_LANG_ENDDECLS } +#else /* ifdef __cplusplus */ +#define ISC_LANG_BEGINDECLS +#define ISC_LANG_ENDDECLS +#endif /* ifdef __cplusplus */ + +#endif /* ISC_LANG_H */ diff --git a/lib/isc/include/isc/lex.h b/lib/isc/include/isc/lex.h new file mode 100644 index 0000000..0209cbe --- /dev/null +++ b/lib/isc/include/isc/lex.h @@ -0,0 +1,447 @@ +/* + * 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. + */ + +#ifndef ISC_LEX_H +#define ISC_LEX_H 1 + +/***** +***** Module Info +*****/ + +/*! \file isc/lex.h + * \brief The "lex" module provides a lightweight tokenizer. It can operate + * on files or buffers, and can handle "include". It is designed for + * parsing of DNS master files and the BIND configuration file, but + * should be general enough to tokenize other things, e.g. HTTP. + * + * \li MP: + * No synchronization is provided. Clients must ensure exclusive + * access. + * + * \li Reliability: + * No anticipated impact. + * + * \li Resources: + * TBS + * + * \li Security: + * No anticipated impact. + * + * \li Standards: + * None. + */ + +/*** + *** Imports + ***/ + +#include <stdbool.h> +#include <stdio.h> + +#include <isc/lang.h> +#include <isc/region.h> +#include <isc/types.h> + +ISC_LANG_BEGINDECLS + +/*** + *** Options + ***/ + +/*@{*/ +/*! + * Various options for isc_lex_gettoken(). + */ + +#define ISC_LEXOPT_EOL 0x0001 /*%< Want end-of-line token. */ +#define ISC_LEXOPT_EOF 0x0002 /*%< Want end-of-file token. */ +#define ISC_LEXOPT_INITIALWS 0x0004 /*%< Want initial whitespace. */ +#define ISC_LEXOPT_NUMBER 0x0008 /*%< Recognize numbers. */ +#define ISC_LEXOPT_QSTRING 0x0010 /*%< Recognize qstrings. */ +/*@}*/ + +/*@{*/ +/*! + * The ISC_LEXOPT_DNSMULTILINE option handles the processing of '(' and ')' in + * the DNS master file format. If this option is set, then the + * ISC_LEXOPT_INITIALWS and ISC_LEXOPT_EOL options will be ignored when + * the paren count is > 0. To use this option, '(' and ')' must be special + * characters. + */ +#define ISC_LEXOPT_DNSMULTILINE 0x0020 /*%< Handle '(' and ')'. */ +#define ISC_LEXOPT_NOMORE 0x0040 /*%< Want "no more" token. */ + +#define ISC_LEXOPT_CNUMBER 0x0080 /*%< Recognize octal and hex. */ +#define ISC_LEXOPT_ESCAPE 0x0100 /*%< Recognize escapes. */ +#define ISC_LEXOPT_QSTRINGMULTILINE 0x0200 /*%< Allow multiline "" strings */ +#define ISC_LEXOPT_OCTAL 0x0400 /*%< Expect a octal number. */ +#define ISC_LEXOPT_BTEXT 0x0800 /*%< Bracketed text. */ +#define ISC_LEXOPT_VPAIR 0x1000 /*%< Recognize value pair. */ +#define ISC_LEXOPT_QVPAIR 0x2000 /*%< Recognize quoted value pair. */ +/*@}*/ +/*@{*/ +/*! + * Various commenting styles, which may be changed at any time with + * isc_lex_setcomments(). + */ + +#define ISC_LEXCOMMENT_C 0x01 +#define ISC_LEXCOMMENT_CPLUSPLUS 0x02 +#define ISC_LEXCOMMENT_SHELL 0x04 +#define ISC_LEXCOMMENT_DNSMASTERFILE 0x08 +/*@}*/ + +/*** + *** Types + ***/ + +/*! Lex */ + +typedef char isc_lexspecials_t[256]; + +/* Tokens */ + +typedef enum { + isc_tokentype_unknown = 0, + isc_tokentype_string = 1, + isc_tokentype_number = 2, + isc_tokentype_qstring = 3, + isc_tokentype_eol = 4, + isc_tokentype_eof = 5, + isc_tokentype_initialws = 6, + isc_tokentype_special = 7, + isc_tokentype_nomore = 8, + isc_tokentype_btext = 9, + isc_tokentype_vpair = 10, + isc_tokentype_qvpair = 11, +} isc_tokentype_t; + +typedef union { + char as_char; + unsigned long as_ulong; + isc_region_t as_region; + isc_textregion_t as_textregion; + void *as_pointer; +} isc_tokenvalue_t; + +typedef struct isc_token { + isc_tokentype_t type; + isc_tokenvalue_t value; +} isc_token_t; + +/*** + *** Functions + ***/ + +isc_result_t +isc_lex_create(isc_mem_t *mctx, size_t max_token, isc_lex_t **lexp); +/*%< + * Create a lexer. + * + * 'max_token' is a hint of the number of bytes in the largest token. + * + * Requires: + *\li '*lexp' is a valid lexer. + * + * Ensures: + *\li On success, *lexp is attached to the newly created lexer. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + */ + +void +isc_lex_destroy(isc_lex_t **lexp); +/*%< + * Destroy the lexer. + * + * Requires: + *\li '*lexp' is a valid lexer. + * + * Ensures: + *\li *lexp == NULL + */ + +unsigned int +isc_lex_getcomments(isc_lex_t *lex); +/*%< + * Return the current lexer commenting styles. + * + * Requires: + *\li 'lex' is a valid lexer. + * + * Returns: + *\li The commenting styles which are currently allowed. + */ + +void +isc_lex_setcomments(isc_lex_t *lex, unsigned int comments); +/*%< + * Set allowed lexer commenting styles. + * + * Requires: + *\li 'lex' is a valid lexer. + * + *\li 'comments' has meaningful values. + */ + +void +isc_lex_getspecials(isc_lex_t *lex, isc_lexspecials_t specials); +/*%< + * Put the current list of specials into 'specials'. + * + * Requires: + *\li 'lex' is a valid lexer. + */ + +void +isc_lex_setspecials(isc_lex_t *lex, isc_lexspecials_t specials); +/*!< + * The characters in 'specials' are returned as tokens. Along with + * whitespace, they delimit strings and numbers. + * + * Note: + *\li Comment processing takes precedence over special character + * recognition. + * + * Requires: + *\li 'lex' is a valid lexer. + */ + +isc_result_t +isc_lex_openfile(isc_lex_t *lex, const char *filename); +/*%< + * Open 'filename' and make it the current input source for 'lex'. + * + * Requires: + *\li 'lex' is a valid lexer. + * + *\li filename is a valid C string. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY Out of memory + *\li #ISC_R_NOTFOUND File not found + *\li #ISC_R_NOPERM No permission to open file + *\li #ISC_R_FAILURE Couldn't open file, not sure why + *\li #ISC_R_UNEXPECTED + */ + +isc_result_t +isc_lex_openstream(isc_lex_t *lex, FILE *stream); +/*%< + * Make 'stream' the current input source for 'lex'. + * + * Requires: + *\li 'lex' is a valid lexer. + * + *\li 'stream' is a valid C stream. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY Out of memory + */ + +isc_result_t +isc_lex_openbuffer(isc_lex_t *lex, isc_buffer_t *buffer); +/*%< + * Make 'buffer' the current input source for 'lex'. + * + * Requires: + *\li 'lex' is a valid lexer. + * + *\li 'buffer' is a valid buffer. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY Out of memory + */ + +isc_result_t +isc_lex_close(isc_lex_t *lex); +/*%< + * Close the most recently opened object (i.e. file or buffer). + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMORE No more input sources + */ + +isc_result_t +isc_lex_gettoken(isc_lex_t *lex, unsigned int options, isc_token_t *tokenp); +/*%< + * Get the next token. + * + * Requires: + *\li 'lex' is a valid lexer. + * + *\li 'lex' has an input source. + * + *\li 'options' contains valid options. + * + *\li '*tokenp' is a valid pointer. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_UNEXPECTEDEND + *\li #ISC_R_NOMEMORY + * + * These two results are returned only if their corresponding lexer + * options are not set. + * + *\li #ISC_R_EOF End of input source + *\li #ISC_R_NOMORE No more input sources + */ + +isc_result_t +isc_lex_getmastertoken(isc_lex_t *lex, isc_token_t *token, + isc_tokentype_t expect, bool eol); +/*%< + * Get the next token from a DNS master file type stream. This is a + * convenience function that sets appropriate options and handles quoted + * strings and end of line correctly for master files. It also ungets + * unexpected tokens. If `eol` is set then expect end-of-line otherwise + * eol is a error. + * + * Requires: + *\li 'lex' is a valid lexer. + * + *\li 'token' is a valid pointer + * + * Returns: + * + * \li any return code from isc_lex_gettoken(). + */ + +isc_result_t +isc_lex_getoctaltoken(isc_lex_t *lex, isc_token_t *token, bool eol); +/*%< + * Get the next token from a DNS master file type stream. This is a + * convenience function that sets appropriate options and handles end + * of line correctly for master files. It also ungets unexpected tokens. + * If `eol` is set then expect end-of-line otherwise eol is a error. + * + * Requires: + *\li 'lex' is a valid lexer. + * + *\li 'token' is a valid pointer + * + * Returns: + * + * \li any return code from isc_lex_gettoken(). + */ + +void +isc_lex_ungettoken(isc_lex_t *lex, isc_token_t *tokenp); +/*%< + * Unget the current token. + * + * Requires: + *\li 'lex' is a valid lexer. + * + *\li 'lex' has an input source. + * + *\li 'tokenp' points to a valid token. + * + *\li There is no ungotten token already. + */ + +void +isc_lex_getlasttokentext(isc_lex_t *lex, isc_token_t *tokenp, isc_region_t *r); +/*%< + * Returns a region containing the text of the last token returned. + * + * Requires: + *\li 'lex' is a valid lexer. + * + *\li 'lex' has an input source. + * + *\li 'tokenp' points to a valid token. + * + *\li A token has been gotten and not ungotten. + */ + +char * +isc_lex_getsourcename(isc_lex_t *lex); +/*%< + * Return the input source name. + * + * Requires: + *\li 'lex' is a valid lexer. + * + * Returns: + * \li source name or NULL if no current source. + *\li result valid while current input source exists. + */ + +unsigned long +isc_lex_getsourceline(isc_lex_t *lex); +/*%< + * Return the input source line number. + * + * Requires: + *\li 'lex' is a valid lexer. + * + * Returns: + *\li Current line number or 0 if no current source. + */ + +isc_result_t +isc_lex_setsourcename(isc_lex_t *lex, const char *name); +/*%< + * Assigns a new name to the input source. + * + * Requires: + * + * \li 'lex' is a valid lexer. + * + * Returns: + * \li #ISC_R_SUCCESS + * \li #ISC_R_NOMEMORY + * \li #ISC_R_NOTFOUND - there are no sources. + */ + +isc_result_t +isc_lex_setsourceline(isc_lex_t *lex, unsigned long line); +/*%< + * Assigns a new line number to the input source. This can be used + * when parsing a buffer that's been excerpted from the middle a file, + * allowing logged messages to display the correct line number, + * rather than the line number within the buffer. + * + * Requires: + * + * \li 'lex' is a valid lexer. + * + * Returns: + * \li #ISC_R_SUCCESS + * \li #ISC_R_NOTFOUND - there are no sources. + */ + +bool +isc_lex_isfile(isc_lex_t *lex); +/*%< + * Return whether the current input source is a file. + * + * Requires: + *\li 'lex' is a valid lexer. + * + * Returns: + * \li #true if the current input is a file, + *\li #false otherwise. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_LEX_H */ diff --git a/lib/isc/include/isc/lfsr.h b/lib/isc/include/isc/lfsr.h new file mode 100644 index 0000000..0e4f7f2 --- /dev/null +++ b/lib/isc/include/isc/lfsr.h @@ -0,0 +1,124 @@ +/* + * 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. + */ + +#ifndef ISC_LFSR_H +#define ISC_LFSR_H 1 + +/*! \file isc/lfsr.h */ + +#include <inttypes.h> + +#include <isc/lang.h> +#include <isc/types.h> + +typedef struct isc_lfsr isc_lfsr_t; + +/*% + * This function is called when reseeding is needed. It is allowed to + * modify any state in the LFSR in any way it sees fit OTHER THAN "bits". + * + * It MUST set "count" to a new value or the lfsr will never reseed again. + * + * Also, a reseed will never occur in the middle of an extraction. This + * is purely an optimization, and is probably what one would want. + */ +typedef void (*isc_lfsrreseed_t)(isc_lfsr_t *, void *); + +/*% + * The members of this structure can be used by the application, but care + * needs to be taken to not change state once the lfsr is in operation. + */ +struct isc_lfsr { + uint32_t state; /*%< previous state */ + unsigned int bits; /*%< length */ + uint32_t tap; /*%< bit taps */ + unsigned int count; /*%< reseed count (in BITS!) */ + isc_lfsrreseed_t reseed; /*%< reseed function */ + void *arg; /*%< reseed function argument */ +}; + +ISC_LANG_BEGINDECLS + +void +isc_lfsr_init(isc_lfsr_t *lfsr, uint32_t state, unsigned int bits, uint32_t tap, + unsigned int count, isc_lfsrreseed_t reseed, void *arg); +/*%< + * Initialize an LFSR. + * + * Note: + * + *\li Putting untrusted values into this function will cause the LFSR to + * generate (perhaps) non-maximal length sequences. + * + * Requires: + * + *\li lfsr != NULL + * + *\li 8 <= bits <= 32 + * + *\li tap != 0 + */ + +void +isc_lfsr_generate(isc_lfsr_t *lfsr, void *data, unsigned int count); +/*%< + * Returns "count" bytes of data from the LFSR. + * + * Requires: + * + *\li lfsr be valid. + * + *\li data != NULL. + * + *\li count > 0. + */ + +void +isc_lfsr_skip(isc_lfsr_t *lfsr, unsigned int skip); +/*%< + * Skip "skip" states. + * + * Requires: + * + *\li lfsr be valid. + */ + +uint32_t +isc_lfsr_generate32(isc_lfsr_t *lfsr1, isc_lfsr_t *lfsr2); +/*%< + * Given two LFSRs, use the current state from each to skip entries in the + * other. The next states are then xor'd together and returned. + * + * WARNING: + * + *\li This function is used only for very, very low security data, such + * as DNS message IDs where it is desired to have an unpredictable + * stream of bytes that are harder to predict than a simple flooding + * attack. + * + * Notes: + * + *\li Since the current state from each of the LFSRs is used to skip + * state in the other, it is important that no state be leaked + * from either LFSR. + * + * Requires: + * + *\li lfsr1 and lfsr2 be valid. + * + *\li 1 <= skipbits <= 31 + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_LFSR_H */ diff --git a/lib/isc/include/isc/lib.h b/lib/isc/include/isc/lib.h new file mode 100644 index 0000000..60d638d --- /dev/null +++ b/lib/isc/include/isc/lib.h @@ -0,0 +1,47 @@ +/* + * 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. + */ + +#pragma once + +/*! \file isc/lib.h */ + +#include <isc/lang.h> +#include <isc/types.h> + +ISC_LANG_BEGINDECLS + +void +isc_lib_register(void); +/*!< + * \brief Register the ISC library implementations for some base services + * such as memory or event management and handling socket or timer events. + * An external application that wants to use the ISC library must call this + * function very early in main(). + */ + +#ifdef WIN32 +int +isc_lib_ntservice(int(WINAPI *mainfunc)(int argc, char *argv[]), int argc, + char *argv[]); +/*!< + * \brief Execute a special routine needed when running as a Windows Service. + */ +#endif /* ifdef WIN32 */ + +extern void +isc_enable_constructors(void); +/*!< + * \brief Enable constructor linkage in non-libtool static builds. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/isc/include/isc/likely.h b/lib/isc/include/isc/likely.h new file mode 100644 index 0000000..360c383 --- /dev/null +++ b/lib/isc/include/isc/likely.h @@ -0,0 +1,33 @@ +/* + * 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. + */ + +#ifndef ISC_LIKELY_H +#define ISC_LIKELY_H 1 + +/*% + * Performance + */ +#ifdef CPPCHECK +#define ISC_LIKELY(x) (x) +#define ISC_UNLIKELY(x) (x) +#else /* ifdef CPPCHECK */ +#ifdef HAVE_BUILTIN_EXPECT +#define ISC_LIKELY(x) __builtin_expect(!!(x), 1) +#define ISC_UNLIKELY(x) __builtin_expect(!!(x), 0) +#else /* ifdef HAVE_BUILTIN_EXPECT */ +#define ISC_LIKELY(x) (x) +#define ISC_UNLIKELY(x) (x) +#endif /* ifdef HAVE_BUILTIN_EXPECT */ +#endif /* ifdef CPPCHECK */ + +#endif /* ISC_LIKELY_H */ diff --git a/lib/isc/include/isc/list.h b/lib/isc/include/isc/list.h new file mode 100644 index 0000000..2cf4437 --- /dev/null +++ b/lib/isc/include/isc/list.h @@ -0,0 +1,229 @@ +/* + * 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. + */ + +#pragma once + +#include <isc/assertions.h> + +#define ISC_LINK_TOMBSTONE(type) ((type *)-1) + +#define ISC_LIST_INITIALIZER \ + { \ + .head = NULL, .tail = NULL, \ + } +#define ISC_LINK_INITIALIZER_TYPE(type) \ + { \ + .prev = ISC_LINK_TOMBSTONE(type), \ + .next = ISC_LINK_TOMBSTONE(type), \ + } +#define ISC_LINK_INITIALIZER ISC_LINK_INITIALIZER_TYPE(void) + +#ifdef ISC_LIST_CHECKINIT +#define ISC_LINK_INSIST(x) ISC_INSIST(x) +#else /* ifdef ISC_LIST_CHECKINIT */ +#define ISC_LINK_INSIST(x) +#endif /* ifdef ISC_LIST_CHECKINIT */ + +#define ISC_LIST(type) \ + struct { \ + type *head, *tail; \ + } +#define ISC_LIST_INIT(list) \ + do { \ + (list).head = NULL; \ + (list).tail = NULL; \ + } while (0) + +#define ISC_LINK(type) \ + struct { \ + type *prev, *next; \ + } +#define ISC_LINK_INIT_TYPE(elt, link, type) \ + do { \ + (elt)->link.prev = ISC_LINK_TOMBSTONE(type); \ + (elt)->link.next = ISC_LINK_TOMBSTONE(type); \ + } while (0) +#define ISC_LINK_INIT(elt, link) ISC_LINK_INIT_TYPE(elt, link, void) +#define ISC_LINK_LINKED_TYPE(elt, link, type) \ + ((type *)((elt)->link.prev) != ISC_LINK_TOMBSTONE(type)) +#define ISC_LINK_LINKED(elt, link) ISC_LINK_LINKED_TYPE(elt, link, void) + +#define ISC_LIST_HEAD(list) ((list).head) +#define ISC_LIST_TAIL(list) ((list).tail) +#define ISC_LIST_EMPTY(list) ((list).head == NULL) + +#define __ISC_LIST_PREPENDUNSAFE(list, elt, link) \ + do { \ + if ((list).head != NULL) { \ + (list).head->link.prev = (elt); \ + } else { \ + (list).tail = (elt); \ + } \ + (elt)->link.prev = NULL; \ + (elt)->link.next = (list).head; \ + (list).head = (elt); \ + } while (0) + +#define ISC_LIST_PREPEND(list, elt, link) \ + do { \ + ISC_LINK_INSIST(!ISC_LINK_LINKED(elt, link)); \ + __ISC_LIST_PREPENDUNSAFE(list, elt, link); \ + } while (0) + +#define ISC_LIST_INITANDPREPEND(list, elt, link) \ + __ISC_LIST_PREPENDUNSAFE(list, elt, link) + +#define __ISC_LIST_APPENDUNSAFE(list, elt, link) \ + do { \ + if ((list).tail != NULL) { \ + (list).tail->link.next = (elt); \ + } else { \ + (list).head = (elt); \ + } \ + (elt)->link.prev = (list).tail; \ + (elt)->link.next = NULL; \ + (list).tail = (elt); \ + } while (0) + +#define ISC_LIST_APPEND(list, elt, link) \ + do { \ + ISC_LINK_INSIST(!ISC_LINK_LINKED(elt, link)); \ + __ISC_LIST_APPENDUNSAFE(list, elt, link); \ + } while (0) + +#define ISC_LIST_INITANDAPPEND(list, elt, link) \ + __ISC_LIST_APPENDUNSAFE(list, elt, link) + +#define __ISC_LIST_UNLINKUNSAFE_TYPE(list, elt, link, type) \ + do { \ + if ((elt)->link.next != NULL) { \ + (elt)->link.next->link.prev = (elt)->link.prev; \ + } else { \ + ISC_INSIST((list).tail == (elt)); \ + (list).tail = (elt)->link.prev; \ + } \ + if ((elt)->link.prev != NULL) { \ + (elt)->link.prev->link.next = (elt)->link.next; \ + } else { \ + ISC_INSIST((list).head == (elt)); \ + (list).head = (elt)->link.next; \ + } \ + (elt)->link.prev = ISC_LINK_TOMBSTONE(type); \ + (elt)->link.next = ISC_LINK_TOMBSTONE(type); \ + ISC_INSIST((list).head != (elt)); \ + ISC_INSIST((list).tail != (elt)); \ + } while (0) + +#define __ISC_LIST_UNLINKUNSAFE(list, elt, link) \ + __ISC_LIST_UNLINKUNSAFE_TYPE(list, elt, link, void) + +#define ISC_LIST_UNLINK_TYPE(list, elt, link, type) \ + do { \ + ISC_LINK_INSIST(ISC_LINK_LINKED(elt, link)); \ + __ISC_LIST_UNLINKUNSAFE_TYPE(list, elt, link, type); \ + } while (0) +#define ISC_LIST_UNLINK(list, elt, link) \ + ISC_LIST_UNLINK_TYPE(list, elt, link, void) + +#define ISC_LIST_PREV(elt, link) ((elt)->link.prev) +#define ISC_LIST_NEXT(elt, link) ((elt)->link.next) + +#define __ISC_LIST_INSERTBEFOREUNSAFE(list, before, elt, link) \ + do { \ + if ((before)->link.prev == NULL) { \ + ISC_LIST_PREPEND(list, elt, link); \ + } else { \ + (elt)->link.prev = (before)->link.prev; \ + (before)->link.prev = (elt); \ + (elt)->link.prev->link.next = (elt); \ + (elt)->link.next = (before); \ + } \ + } while (0) + +#define ISC_LIST_INSERTBEFORE(list, before, elt, link) \ + do { \ + ISC_LINK_INSIST(ISC_LINK_LINKED(before, link)); \ + ISC_LINK_INSIST(!ISC_LINK_LINKED(elt, link)); \ + __ISC_LIST_INSERTBEFOREUNSAFE(list, before, elt, link); \ + } while (0) + +#define __ISC_LIST_INSERTAFTERUNSAFE(list, after, elt, link) \ + do { \ + if ((after)->link.next == NULL) { \ + ISC_LIST_APPEND(list, elt, link); \ + } else { \ + (elt)->link.next = (after)->link.next; \ + (after)->link.next = (elt); \ + (elt)->link.next->link.prev = (elt); \ + (elt)->link.prev = (after); \ + } \ + } while (0) + +#define ISC_LIST_INSERTAFTER(list, after, elt, link) \ + do { \ + ISC_LINK_INSIST(ISC_LINK_LINKED(after, link)); \ + ISC_LINK_INSIST(!ISC_LINK_LINKED(elt, link)); \ + __ISC_LIST_INSERTAFTERUNSAFE(list, after, elt, link); \ + } while (0) + +#define ISC_LIST_APPENDLIST(list1, list2, link) \ + do { \ + if (ISC_LIST_EMPTY(list1)) { \ + (list1) = (list2); \ + } else if (!ISC_LIST_EMPTY(list2)) { \ + (list1).tail->link.next = (list2).head; \ + (list2).head->link.prev = (list1).tail; \ + (list1).tail = (list2).tail; \ + } \ + (list2).head = NULL; \ + (list2).tail = NULL; \ + } while (0) + +#define ISC_LIST_PREPENDLIST(list1, list2, link) \ + do { \ + if (ISC_LIST_EMPTY(list1)) { \ + (list1) = (list2); \ + } else if (!ISC_LIST_EMPTY(list2)) { \ + (list2).tail->link.next = (list1).head; \ + (list1).head->link.prev = (list2).tail; \ + (list1).head = (list2).head; \ + } \ + (list2).head = NULL; \ + (list2).tail = NULL; \ + } while (0) + +#define ISC_LIST_ENQUEUE(list, elt, link) ISC_LIST_APPEND(list, elt, link) +#define __ISC_LIST_ENQUEUEUNSAFE(list, elt, link) \ + __ISC_LIST_APPENDUNSAFE(list, elt, link) +#define ISC_LIST_DEQUEUE(list, elt, link) \ + ISC_LIST_UNLINK_TYPE(list, elt, link, void) +#define ISC_LIST_DEQUEUE_TYPE(list, elt, link, type) \ + ISC_LIST_UNLINK_TYPE(list, elt, link, type) +#define __ISC_LIST_DEQUEUEUNSAFE(list, elt, link) \ + __ISC_LIST_UNLINKUNSAFE_TYPE(list, elt, link, void) +#define __ISC_LIST_DEQUEUEUNSAFE_TYPE(list, elt, link, type) \ + __ISC_LIST_UNLINKUNSAFE_TYPE(list, elt, link, type) + +#define ISC_LIST_MOVEUNSAFE(dest, src) \ + { \ + (dest).head = (src).head; \ + (dest).tail = (src).tail; \ + (src).head = NULL; \ + (src).tail = NULL; \ + } + +#define ISC_LIST_MOVE(dest, src) \ + { \ + INSIST(ISC_LIST_EMPTY(dest)); \ + ISC_LIST_MOVEUNSAFE(dest, src); \ + } diff --git a/lib/isc/include/isc/log.h b/lib/isc/include/isc/log.h new file mode 100644 index 0000000..289cbd7 --- /dev/null +++ b/lib/isc/include/isc/log.h @@ -0,0 +1,848 @@ +/* + * 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. + */ + +#ifndef ISC_LOG_H +#define ISC_LOG_H 1 + +/*! \file isc/log.h */ + +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <syslog.h> /* XXXDCL NT */ + +#include <isc/formatcheck.h> +#include <isc/lang.h> +#include <isc/platform.h> +#include <isc/types.h> + +/*@{*/ +/*! + * \brief Severity levels, patterned after Unix's syslog levels. + * + */ +#define ISC_LOG_DEBUG(level) (level) +/*! + * #ISC_LOG_DYNAMIC can only be used for defining channels with + * isc_log_createchannel(), not to specify a level in isc_log_write(). + */ +#define ISC_LOG_DYNAMIC 0 +#define ISC_LOG_INFO (-1) +#define ISC_LOG_NOTICE (-2) +#define ISC_LOG_WARNING (-3) +#define ISC_LOG_ERROR (-4) +#define ISC_LOG_CRITICAL (-5) +/*@}*/ + +/*@{*/ +/*! + * \brief Destinations. + */ +#define ISC_LOG_TONULL 1 +#define ISC_LOG_TOSYSLOG 2 +#define ISC_LOG_TOFILE 3 +#define ISC_LOG_TOFILEDESC 4 +/*@}*/ + +/*@{*/ +/*% + * Channel flags. + */ +#define ISC_LOG_PRINTTIME 0x00001 +#define ISC_LOG_PRINTLEVEL 0x00002 +#define ISC_LOG_PRINTCATEGORY 0x00004 +#define ISC_LOG_PRINTMODULE 0x00008 +#define ISC_LOG_PRINTTAG 0x00010 /* tag and ":" */ +#define ISC_LOG_PRINTPREFIX 0x00020 /* tag only, no colon */ +#define ISC_LOG_PRINTALL 0x0003F +#define ISC_LOG_BUFFERED 0x00040 +#define ISC_LOG_DEBUGONLY 0x01000 +#define ISC_LOG_OPENERR 0x08000 /* internal */ +#define ISC_LOG_ISO8601 0x10000 /* if PRINTTIME, use ISO8601 */ +#define ISC_LOG_UTC 0x20000 /* if PRINTTIME, use UTC */ +/*@}*/ + +/*@{*/ +/*! + * \brief Other options. + * + * XXXDCL INFINITE doesn't yet work. Arguably it isn't needed, but + * since I am intend to make large number of versions work efficiently, + * INFINITE is going to be trivial to add to that. + */ +#define ISC_LOG_ROLLINFINITE (-1) +#define ISC_LOG_ROLLNEVER (-2) +#define ISC_LOG_MAX_VERSIONS 256 +/*@}*/ + +/*@{*/ +/*! + * \brief Type of suffix used on rolled log files. + */ +typedef enum { + isc_log_rollsuffix_increment, + isc_log_rollsuffix_timestamp +} isc_log_rollsuffix_t; +/*@}*/ + +/*! + * \brief Used to name the categories used by a library. + * + * An array of isc_logcategory + * structures names each category, and the id value is initialized by calling + * isc_log_registercategories. + */ +struct isc_logcategory { + const char *name; + unsigned int id; +}; + +/*% + * Similar to isc_logcategory, but for all the modules a library defines. + */ +struct isc_logmodule { + const char *name; + unsigned int id; +}; + +/*% + * The isc_logfile structure is initialized as part of an isc_logdestination + * before calling isc_log_createchannel(). + * + * When defining an #ISC_LOG_TOFILE + * channel the name, versions and maximum_size should be set before calling + * isc_log_createchannel(). To define an #ISC_LOG_TOFILEDESC channel set only + * the stream before the call. + * + * Setting maximum_size to zero implies no maximum. + */ +typedef struct isc_logfile { + FILE *stream; /*%< Initialized to NULL for + * #ISC_LOG_TOFILE. */ + const char *name; /*%< NULL for #ISC_LOG_TOFILEDESC. */ + int versions; /* >= 0, #ISC_LOG_ROLLNEVER, + * #ISC_LOG_ROLLINFINITE. */ + isc_log_rollsuffix_t suffix; + /*% + * stdio's ftell is standardized to return a long, which may well not + * be big enough for the largest file supportable by the operating + * system (though it is _probably_ big enough for the largest log + * anyone would want). st_size returned by fstat should be typedef'd + * to a size large enough for the largest possible file on a system. + */ + isc_offset_t maximum_size; + bool maximum_reached; /*%< Private. */ +} isc_logfile_t; + +/*% + * Passed to isc_log_createchannel to define the attributes of either + * a stdio or a syslog log. + */ +typedef union isc_logdestination { + isc_logfile_t file; + int facility; /* XXXDCL NT */ +} isc_logdestination_t; + +/*@{*/ +/*% + * The built-in categories of libisc. + * + * Each library registering categories should provide library_LOGCATEGORY_name + * definitions with indexes into its isc_logcategory structure corresponding to + * the order of the names. + */ +LIBISC_EXTERNAL_DATA extern isc_logcategory_t isc_categories[]; +LIBISC_EXTERNAL_DATA extern isc_log_t *isc_lctx; +LIBISC_EXTERNAL_DATA extern isc_logmodule_t isc_modules[]; +/*@}*/ + +/*@{*/ +/*% + * Do not log directly to DEFAULT. Use another category. When in doubt, + * use GENERAL. + */ +#define ISC_LOGCATEGORY_DEFAULT (&isc_categories[0]) +#define ISC_LOGCATEGORY_GENERAL (&isc_categories[1]) +/*@}*/ + +#define ISC_LOGMODULE_SOCKET (&isc_modules[0]) +#define ISC_LOGMODULE_TIME (&isc_modules[1]) +#define ISC_LOGMODULE_INTERFACE (&isc_modules[2]) +#define ISC_LOGMODULE_TIMER (&isc_modules[3]) +#define ISC_LOGMODULE_FILE (&isc_modules[4]) +#define ISC_LOGMODULE_NETMGR (&isc_modules[5]) +#define ISC_LOGMODULE_OTHER (&isc_modules[6]) + +ISC_LANG_BEGINDECLS + +void +isc_log_create(isc_mem_t *mctx, isc_log_t **lctxp, isc_logconfig_t **lcfgp); +/*%< + * Establish a new logging context, with default channels. + * + * Notes: + *\li isc_log_create() calls isc_logconfig_create(), so see its comment + * below for more information. + * + * Requires: + *\li mctx is a valid memory context. + *\li lctxp is not null and *lctxp is null. + *\li lcfgp is null or lcfgp is not null and *lcfgp is null. + * + * Ensures: + *\li *lctxp will point to a valid logging context if all of the necessary + * memory was allocated, or NULL otherwise. + *\li *lcfgp will point to a valid logging configuration if all of the + * necessary memory was allocated, or NULL otherwise. + *\li On failure, no additional memory is allocated. + */ + +void +isc_logconfig_create(isc_log_t *lctx, isc_logconfig_t **lcfgp); +/*%< + * Create the data structure that holds all of the configurable information + * about where messages are actually supposed to be sent -- the information + * that could changed based on some configuration file, as opposed to the + * the category/module specification of isc_log_[v]write[1] that is compiled + * into a program, or the debug_level which is dynamic state information. + * + * Notes: + *\li It is necessary to specify the logging context the configuration + * will be used with because the number of categories and modules + * needs to be known in order to set the configuration. However, + * the configuration is not used by the logging context until the + * isc_logconfig_use function is called. + * + *\li The memory context used for operations that allocate memory for + * the configuration is that of the logging context, as specified + * in the isc_log_create call. + * + *\li Four default channels are established: + *\verbatim + * default_syslog + * - log to syslog's daemon facility #ISC_LOG_INFO or higher + * default_stderr + * - log to stderr #ISC_LOG_INFO or higher + * default_debug + * - log to stderr #ISC_LOG_DEBUG dynamically + * null + * - log nothing + *\endverbatim + * + * Requires: + *\li lctx is a valid logging context. + *\li lcftp is not null and *lcfgp is null. + * + * Ensures: + *\li *lcfgp will point to a valid logging context if all of the necessary + * memory was allocated, or NULL otherwise. + *\li On failure, no additional memory is allocated. + */ + +void +isc_logconfig_use(isc_log_t *lctx, isc_logconfig_t *lcfg); +/*%< + * Associate a new configuration with a logging context. + * + * Notes: + *\li This is thread safe. The logging context will lock a mutex + * before attempting to swap in the new configuration, and isc_log_doit + * (the internal function used by all of isc_log_[v]write[1]) locks + * the same lock for the duration of its use of the configuration. + * + * Requires: + *\li lctx is a valid logging context. + *\li lcfg is a valid logging configuration. + *\li lctx is the same configuration given to isc_logconfig_create + * when the configuration was created. + * + * Ensures: + *\li Future calls to isc_log_write will use the new configuration. + */ + +void +isc_log_destroy(isc_log_t **lctxp); +/*%< + * Deallocate the memory associated with a logging context. + * + * Requires: + *\li *lctx is a valid logging context. + * + * Ensures: + *\li All of the memory associated with the logging context is returned + * to the free memory pool. + * + *\li Any open files are closed. + * + *\li The logging context is marked as invalid. + */ + +void +isc_logconfig_destroy(isc_logconfig_t **lcfgp); +/*%< + * Destroy a logging configuration. + * + * Requires: + *\li lcfgp is not null and *lcfgp is a valid logging configuration. + *\li The logging configuration is not in use by an existing logging context. + * + * Ensures: + *\li All memory allocated for the configuration is freed. + * + *\li The configuration is marked as invalid. + */ + +void +isc_log_registercategories(isc_log_t *lctx, isc_logcategory_t categories[]); +/*%< + * Identify logging categories a library will use. + * + * Notes: + *\li A category should only be registered once, but no mechanism enforces + * this rule. + * + *\li The end of the categories array is identified by a NULL name. + * + *\li Because the name is used by #ISC_LOG_PRINTCATEGORY, it should not + * be altered or destroyed after isc_log_registercategories(). + * + *\li Because each element of the categories array is used by + * isc_log_categorybyname, it should not be altered or destroyed + * after registration. + * + *\li The value of the id integer in each structure is overwritten + * by this function, and so id need not be initialized to any particular + * value prior to the function call. + * + *\li A subsequent call to isc_log_registercategories with the same + * logging context (but new categories) will cause the last + * element of the categories array from the prior call to have + * its "name" member changed from NULL to point to the new + * categories array, and its "id" member set to UINT_MAX. + * + * Requires: + *\li lctx is a valid logging context. + *\li categories != NULL. + *\li categories[0].name != NULL. + * + * Ensures: + * \li There are references to each category in the logging context, + * so they can be used with isc_log_usechannel() and isc_log_write(). + */ + +void +isc_log_registermodules(isc_log_t *lctx, isc_logmodule_t modules[]); +/*%< + * Identify logging categories a library will use. + * + * Notes: + *\li A module should only be registered once, but no mechanism enforces + * this rule. + * + *\li The end of the modules array is identified by a NULL name. + * + *\li Because the name is used by #ISC_LOG_PRINTMODULE, it should not + * be altered or destroyed after isc_log_registermodules(). + * + *\li Because each element of the modules array is used by + * isc_log_modulebyname, it should not be altered or destroyed + * after registration. + * + *\li The value of the id integer in each structure is overwritten + * by this function, and so id need not be initialized to any particular + * value prior to the function call. + * + *\li A subsequent call to isc_log_registermodules with the same + * logging context (but new modules) will cause the last + * element of the modules array from the prior call to have + * its "name" member changed from NULL to point to the new + * modules array, and its "id" member set to UINT_MAX. + * + * Requires: + *\li lctx is a valid logging context. + *\li modules != NULL. + *\li modules[0].name != NULL; + * + * Ensures: + *\li Each module has a reference in the logging context, so they can be + * used with isc_log_usechannel() and isc_log_write(). + */ + +void +isc_log_createchannel(isc_logconfig_t *lcfg, const char *name, + unsigned int type, int level, + const isc_logdestination_t *destination, + unsigned int flags); +/*%< + * Specify the parameters of a logging channel. + * + * Notes: + *\li The name argument is copied to memory in the logging context, so + * it can be altered or destroyed after isc_log_createchannel(). + * + *\li Defining a very large number of channels will have a performance + * impact on isc_log_usechannel(), since the names are searched + * linearly until a match is made. This same issue does not affect + * isc_log_write, however. + * + *\li Channel names can be redefined; this is primarily useful for programs + * that want their own definition of default_syslog, default_debug + * and default_stderr. + * + *\li Any channel that is redefined will not affect logging that was + * already directed to its original definition, _except_ for the + * default_stderr channel. This case is handled specially so that + * the default logging category can be changed by redefining + * default_stderr. (XXXDCL Though now that I think of it, the default + * logging category can be changed with only one additional function + * call by defining a new channel and then calling isc_log_usechannel() + * for #ISC_LOGCATEGORY_DEFAULT.) + * + *\li Specifying #ISC_LOG_PRINTTIME or #ISC_LOG_PRINTTAG for syslog is + * allowed, but probably not what you wanted to do. + * + * #ISC_LOG_DEBUGONLY will mark the channel as usable only when the + * debug level of the logging context (see isc_log_setdebuglevel) + * is non-zero. + * + * Requires: + *\li lcfg is a valid logging configuration. + * + *\li name is not NULL. + * + *\li type is #ISC_LOG_TOSYSLOG, #ISC_LOG_TOFILE, #ISC_LOG_TOFILEDESC or + * #ISC_LOG_TONULL. + * + *\li destination is not NULL unless type is #ISC_LOG_TONULL. + * + *\li level is >= #ISC_LOG_CRITICAL (the most negative logging level). + * + *\li flags does not include any bits aside from the ISC_LOG_PRINT* bits, + * #ISC_LOG_DEBUGONLY or #ISC_LOG_BUFFERED. + * + * Ensures: + *\li #ISC_R_SUCCESS + * A channel with the given name is usable with + * isc_log_usechannel(). + * + *\li #ISC_R_NOMEMORY or #ISC_R_UNEXPECTED + * No additional memory is being used by the logging context. + * Any channel that previously existed with the given name + * is not redefined. + */ + +isc_result_t +isc_log_usechannel(isc_logconfig_t *lcfg, const char *name, + const isc_logcategory_t *category, + const isc_logmodule_t *module); +/*%< + * Associate a named logging channel with a category and module that + * will use it. + * + * Notes: + *\li The name is searched for linearly in the set of known channel names + * until a match is found. (Note the performance impact of a very large + * number of named channels.) When multiple channels of the same + * name are defined, the most recent definition is found. + * + *\li Specifying a very large number of channels for a category will have + * a moderate impact on performance in isc_log_write(), as each + * call looks up the category for the start of a linked list, which + * it follows all the way to the end to find matching modules. The + * test for matching modules is integral, though. + * + *\li If category is NULL, then the channel is associated with the indicated + * module for all known categories (including the "default" category). + * + *\li If module is NULL, then the channel is associated with every module + * that uses that category. + * + *\li Passing both category and module as NULL would make every log message + * use the indicated channel. + * + * \li Specifying a channel that is #ISC_LOG_TONULL for a category/module pair + * has no effect on any other channels associated with that pair, + * regardless of ordering. Thus you cannot use it to "mask out" one + * category/module pair when you have specified some other channel that + * is also used by that category/module pair. + * + * Requires: + *\li lcfg is a valid logging configuration. + * + *\li category is NULL or has an id that is in the range of known ids. + * + * module is NULL or has an id that is in the range of known ids. + * + * Ensures: + *\li #ISC_R_SUCCESS + * The channel will be used by the indicated category/module + * arguments. + * + *\li #ISC_R_NOMEMORY + * If assignment for a specific category has been requested, + * the channel has not been associated with the indicated + * category/module arguments and no additional memory is + * used by the logging context. + * If assignment for all categories has been requested + * then _some_ may have succeeded (starting with category + * "default" and progressing through the order of categories + * passed to isc_log_registercategories()) and additional memory + * is being used by whatever assignments succeeded. + * + * Returns: + *\li #ISC_R_SUCCESS Success + *\li #ISC_R_NOMEMORY Resource limit: Out of memory + */ + +/* Attention: next four comments PRECEDE code */ +/*! + * \brief + * Write a message to the log channels. + * + * Notes: + *\li lctx can be NULL; this is allowed so that programs which use + * libraries that use the ISC logging system are not required to + * also use it. + * + *\li The format argument is a printf(3) string, with additional arguments + * as necessary. + * + * Requires: + *\li lctx is a valid logging context. + * + *\li The category and module arguments must have ids that are in the + * range of known ids, as established by isc_log_registercategories() + * and isc_log_registermodules(). + * + *\li level != #ISC_LOG_DYNAMIC. ISC_LOG_DYNAMIC is used only to define + * channels, and explicit debugging level must be identified for + * isc_log_write() via ISC_LOG_DEBUG(level). + * + *\li format != NULL. + * + * Ensures: + *\li The log message is written to every channel associated with the + * indicated category/module pair. + * + * Returns: + *\li Nothing. Failure to log a message is not construed as a + * meaningful error. + */ +void +isc_log_write(isc_log_t *lctx, isc_logcategory_t *category, + isc_logmodule_t *module, int level, const char *format, ...) + + ISC_FORMAT_PRINTF(5, 6); + +/*% + * Write a message to the log channels. + * + * Notes: + *\li lctx can be NULL; this is allowed so that programs which use + * libraries that use the ISC logging system are not required to + * also use it. + * + *\li The format argument is a printf(3) string, with additional arguments + * as necessary. + * + * Requires: + *\li lctx is a valid logging context. + * + *\li The category and module arguments must have ids that are in the + * range of known ids, as established by isc_log_registercategories() + * and isc_log_registermodules(). + * + *\li level != #ISC_LOG_DYNAMIC. ISC_LOG_DYNAMIC is used only to define + * channels, and explicit debugging level must be identified for + * isc_log_write() via ISC_LOG_DEBUG(level). + * + *\li format != NULL. + * + * Ensures: + *\li The log message is written to every channel associated with the + * indicated category/module pair. + * + * Returns: + *\li Nothing. Failure to log a message is not construed as a + * meaningful error. + */ +void +isc_log_vwrite(isc_log_t *lctx, isc_logcategory_t *category, + isc_logmodule_t *module, int level, const char *format, + va_list args) + + ISC_FORMAT_PRINTF(5, 0); + +/*% + * Write a message to the log channels, pruning duplicates that occur within + * a configurable amount of seconds (see isc_log_[sg]etduplicateinterval). + * This function is otherwise identical to isc_log_write(). + */ +void +isc_log_write1(isc_log_t *lctx, isc_logcategory_t *category, + isc_logmodule_t *module, int level, const char *format, ...) + + ISC_FORMAT_PRINTF(5, 6); + +/*% + * Write a message to the log channels, pruning duplicates that occur within + * a configurable amount of seconds (see isc_log_[sg]etduplicateinterval). + * This function is otherwise identical to isc_log_vwrite(). + */ +void +isc_log_vwrite1(isc_log_t *lctx, isc_logcategory_t *category, + isc_logmodule_t *module, int level, const char *format, + va_list args) + + ISC_FORMAT_PRINTF(5, 0); + +void +isc_log_setdebuglevel(isc_log_t *lctx, unsigned int level); +/*%< + * Set the debugging level used for logging. + * + * Notes: + *\li Setting the debugging level to 0 disables debugging log messages. + * + * Requires: + *\li lctx is a valid logging context. + * + * Ensures: + *\li The debugging level is set to the requested value. + */ + +unsigned int +isc_log_getdebuglevel(isc_log_t *lctx); +/*%< + * Get the current debugging level. + * + * Notes: + *\li This is provided so that a program can have a notion of + * "increment debugging level" or "decrement debugging level" + * without needing to keep track of what the current level is. + * + *\li A return value of 0 indicates that debugging messages are disabled. + * + * Requires: + *\li lctx is a valid logging context. + * + * Ensures: + *\li The current logging debugging level is returned. + */ + +bool +isc_log_wouldlog(isc_log_t *lctx, int level); +/*%< + * Determine whether logging something to 'lctx' at 'level' would + * actually cause something to be logged somewhere. + * + * If #false is returned, it is guaranteed that nothing would + * be logged, allowing the caller to omit unnecessary + * isc_log_write() calls and possible message preformatting. + */ + +void +isc_log_setduplicateinterval(isc_logconfig_t *lcfg, unsigned int interval); +/*%< + * Set the interval over which duplicate log messages will be ignored + * by isc_log_[v]write1(), in seconds. + * + * Notes: + *\li Increasing the duplicate interval from X to Y will not necessarily + * filter out duplicates of messages logged in Y - X seconds since the + * increase. (Example: Message1 is logged at midnight. Message2 + * is logged at 00:01:00, when the interval is only 30 seconds, causing + * Message1 to be expired from the log message history. Then the interval + * is increased to 3000 (five minutes) and at 00:04:00 Message1 is logged + * again. It will appear the second time even though less than five + * passed since the first occurrence. + * + * Requires: + *\li lctx is a valid logging context. + */ + +unsigned int +isc_log_getduplicateinterval(isc_logconfig_t *lcfg); +/*%< + * Get the current duplicate filtering interval. + * + * Requires: + *\li lctx is a valid logging context. + * + * Returns: + *\li The current duplicate filtering interval. + */ + +void +isc_log_settag(isc_logconfig_t *lcfg, const char *tag); +/*%< + * Set the program name or other identifier for #ISC_LOG_PRINTTAG. + * + * Requires: + *\li lcfg is a valid logging configuration. + * + * Notes: + *\li If this function has not set the tag to a non-NULL, non-empty value, + * then the #ISC_LOG_PRINTTAG channel flag will not print anything. + * Unlike some implementations of syslog on Unix systems, you *must* set + * the tag in order to get it logged. It is not implicitly derived from + * the program name (which is pretty impossible to infer portably). + * + *\li Setting the tag to NULL or the empty string will also cause the + * #ISC_LOG_PRINTTAG channel flag to not print anything. If tag equals the + * empty string, calls to isc_log_gettag will return NULL. + * + * XXXDCL when creating a new isc_logconfig_t, it might be nice if the tag + * of the currently active isc_logconfig_t was inherited. this does not + * currently happen. + */ + +char * +isc_log_gettag(isc_logconfig_t *lcfg); +/*%< + * Get the current identifier printed with #ISC_LOG_PRINTTAG. + * + * Requires: + *\li lcfg is a valid logging configuration. + * + * Notes: + *\li Since isc_log_settag() will not associate a zero-length string + * with the logging configuration, attempts to do so will cause + * this function to return NULL. However, a determined programmer + * will observe that (currently) a tag of length greater than zero + * could be set, and then modified to be zero length. + * + * Returns: + *\li A pointer to the current identifier, or NULL if none has been set. + */ + +void +isc_log_opensyslog(const char *tag, int options, int facility); +/*%< + * Initialize syslog logging. + * + * Notes: + *\li XXXDCL NT + * This is currently equivalent to openlog(), but is not going to remain + * that way. In the meantime, the arguments are all identical to + * those used by openlog(3), as follows: + * + * \code + * tag: The string to use in the position of the program + * name in syslog messages. Most (all?) syslogs + * will use basename(argv[0]) if tag is NULL. + * + * options: LOG_CONS, LOG_PID, LOG_NDELAY ... whatever your + * syslog supports. + * + * facility: The default syslog facility. This is irrelevant + * since isc_log_write will ALWAYS use the channel's + * declared facility. + * \endcode + * + *\li Zero effort has been made (yet) to accommodate systems with openlog() + * that only takes two arguments, or to identify valid syslog + * facilities or options for any given architecture. + * + *\li It is necessary to call isc_log_opensyslog() to initialize + * syslogging on machines which do not support network connections to + * syslogd because they require a Unix domain socket to be used. Since + * this is a chore to determine at run-time, it is suggested that it + * always be called by programs using the ISC logging system. + * + * Requires: + *\li Nothing. + * + * Ensures: + *\li openlog() is called to initialize the syslog system. + */ + +void +isc_log_closefilelogs(isc_log_t *lctx); +/*%< + * Close all open files used by #ISC_LOG_TOFILE channels. + * + * Notes: + *\li This function is provided for programs that want to use their own + * log rolling mechanism rather than the one provided internally. + * For example, a program that wanted to keep daily logs would define + * a channel which used #ISC_LOG_ROLLNEVER, then once a day would + * rename the log file and call isc_log_closefilelogs(). + * + *\li #ISC_LOG_TOFILEDESC channels are unaffected. + * + * Requires: + *\li lctx is a valid context. + * + * Ensures: + *\li The open files are closed and will be reopened when they are + * next needed. + */ + +isc_logcategory_t * +isc_log_categorybyname(isc_log_t *lctx, const char *name); +/*%< + * Find a category by its name. + * + * Notes: + *\li The string name of a category is not required to be unique. + * + * Requires: + *\li lctx is a valid context. + *\li name is not NULL. + * + * Returns: + *\li A pointer to the _first_ isc_logcategory_t structure used by "name". + * + *\li NULL if no category exists by that name. + */ + +isc_logmodule_t * +isc_log_modulebyname(isc_log_t *lctx, const char *name); +/*%< + * Find a module by its name. + * + * Notes: + *\li The string name of a module is not required to be unique. + * + * Requires: + *\li lctx is a valid context. + *\li name is not NULL. + * + * Returns: + *\li A pointer to the _first_ isc_logmodule_t structure used by "name". + * + *\li NULL if no module exists by that name. + */ + +void +isc_log_setcontext(isc_log_t *lctx); +/*%< + * Sets the context used by the libisc for logging. + * + * Requires: + *\li lctx be a valid context. + */ + +isc_result_t +isc_logfile_roll(isc_logfile_t *file); +/*%< + * Roll a logfile. + * + * Requires: + *\li file is not NULL. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_LOG_H */ diff --git a/lib/isc/include/isc/magic.h b/lib/isc/include/isc/magic.h new file mode 100644 index 0000000..1dc9fc2 --- /dev/null +++ b/lib/isc/include/isc/magic.h @@ -0,0 +1,37 @@ +/* + * 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. + */ + +#ifndef ISC_MAGIC_H +#define ISC_MAGIC_H 1 + +#include <isc/likely.h> + +/*! \file isc/magic.h */ + +typedef struct { + unsigned int magic; +} isc__magic_t; + +/*% + * To use this macro the magic number MUST be the first thing in the + * structure, and MUST be of type "unsigned int". + * The intent of this is to allow magic numbers to be checked even though + * the object is otherwise opaque. + */ +#define ISC_MAGIC_VALID(a, b) \ + (ISC_LIKELY((a) != NULL) && \ + ISC_LIKELY(((const isc__magic_t *)(a))->magic == (b))) + +#define ISC_MAGIC(a, b, c, d) ((a) << 24 | (b) << 16 | (c) << 8 | (d)) + +#endif /* ISC_MAGIC_H */ diff --git a/lib/isc/include/isc/managers.h b/lib/isc/include/isc/managers.h new file mode 100644 index 0000000..0ed17ff --- /dev/null +++ b/lib/isc/include/isc/managers.h @@ -0,0 +1,29 @@ +/* + * 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. + */ + +#pragma once + +#include <isc/netmgr.h> +#include <isc/result.h> +#include <isc/socket.h> +#include <isc/task.h> +#include <isc/timer.h> + +typedef struct isc_managers isc_managers_t; + +isc_result_t +isc_managers_create(isc_mem_t *mctx, size_t workers, size_t quantum, + isc_nm_t **netmgrp, isc_taskmgr_t **taskmgrp); + +void +isc_managers_destroy(isc_nm_t **netmgrp, isc_taskmgr_t **taskmgrp); diff --git a/lib/isc/include/isc/md.h b/lib/isc/include/isc/md.h new file mode 100644 index 0000000..eb9c863 --- /dev/null +++ b/lib/isc/include/isc/md.h @@ -0,0 +1,205 @@ +/* + * 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. + */ + +/*! + * \file isc/md.h + * \brief This is the header file for message digest algorithms. + */ + +#pragma once + +#include <isc/lang.h> +#include <isc/platform.h> +#include <isc/result.h> +#include <isc/types.h> + +typedef void isc_md_t; + +/** + * isc_md_type_t: + * @ISC_MD_MD5: MD5 + * @ISC_MD_SHA1: SHA-1 + * @ISC_MD_SHA224: SHA-224 + * @ISC_MD_SHA256: SHA-256 + * @ISC_MD_SHA384: SHA-384 + * @ISC_MD_SHA512: SHA-512 + * + * Enumeration of supported message digest algorithms. + */ +typedef void isc_md_type_t; + +#define ISC_MD_MD5 isc__md_md5() +#define ISC_MD_SHA1 isc__md_sha1() +#define ISC_MD_SHA224 isc__md_sha224() +#define ISC_MD_SHA256 isc__md_sha256() +#define ISC_MD_SHA384 isc__md_sha384() +#define ISC_MD_SHA512 isc__md_sha512() + +const isc_md_type_t * +isc__md_md5(void); +const isc_md_type_t * +isc__md_sha1(void); +const isc_md_type_t * +isc__md_sha224(void); +const isc_md_type_t * +isc__md_sha256(void); +const isc_md_type_t * +isc__md_sha384(void); +const isc_md_type_t * +isc__md_sha512(void); + +#define ISC_MD5_DIGESTLENGTH isc_md_type_get_size(ISC_MD_MD5) +#define ISC_MD5_BLOCK_LENGTH isc_md_type_get_block_size(ISC_MD_MD5) +#define ISC_SHA1_DIGESTLENGTH isc_md_type_get_size(ISC_MD_SHA1) +#define ISC_SHA1_BLOCK_LENGTH isc_md_type_get_block_size(ISC_MD_SHA1) +#define ISC_SHA224_DIGESTLENGTH isc_md_type_get_size(ISC_MD_SHA224) +#define ISC_SHA224_BLOCK_LENGTH isc_md_type_get_block_size(ISC_MD_SHA224) +#define ISC_SHA256_DIGESTLENGTH isc_md_type_get_size(ISC_MD_SHA256) +#define ISC_SHA256_BLOCK_LENGTH isc_md_type_get_block_size(ISC_MD_SHA256) +#define ISC_SHA384_DIGESTLENGTH isc_md_type_get_size(ISC_MD_SHA384) +#define ISC_SHA384_BLOCK_LENGTH isc_md_type_get_block_size(ISC_MD_SHA384) +#define ISC_SHA512_DIGESTLENGTH isc_md_type_get_size(ISC_MD_SHA512) +#define ISC_SHA512_BLOCK_LENGTH isc_md_type_get_block_size(ISC_MD_SHA512) + +#define ISC_MAX_MD_SIZE 64U /* EVP_MAX_MD_SIZE */ +#define ISC_MAX_BLOCK_SIZE 128U /* ISC_SHA512_BLOCK_LENGTH */ + +/** + * isc_md: + * @type: the digest type + * @buf: the data to hash + * @len: the length of the data to hash + * @digest: the output buffer + * @digestlen: the length of the data written to @digest + * + * This function hashes @len bytes of data at @buf and places the result in + * @digest. If the @digestlen parameter is not NULL then the number of bytes of + * data written (i.e. the length of the digest) will be written to the integer + * at @digestlen, at most ISC_MAX_MD_SIZE bytes will be written. + */ +isc_result_t +isc_md(const isc_md_type_t *type, const unsigned char *buf, const size_t len, + unsigned char *digest, unsigned int *digestlen); + +/** + * isc_md_new: + * + * This function allocates, initializes and returns a digest context. + */ +isc_md_t * +isc_md_new(void); + +/** + * isc_md_free: + * @md: message digest context + * + * This function cleans up digest context ctx and frees up the space allocated + * to it. + */ +void +isc_md_free(isc_md_t *); + +/** + * isc_md_init: + * @md: message digest context + * @type: digest type + * + * This function sets up digest context @md to use a digest @type. @md must be + * initialized before calling this function. + */ +isc_result_t +isc_md_init(isc_md_t *, const isc_md_type_t *md_type); + +/** + * isc_md_reset: + * @md: message digest context + * + * This function resets the digest context ctx. This can be used to reuse an + * already existing context. + */ +isc_result_t +isc_md_reset(isc_md_t *md); + +/** + * isc_md_update: + * @md: message digest context + * @buf: data to hash + * @len: length of the data to hash + * + * This function hashes @len bytes of data at @buf into the digest context @md. + * This function can be called several times on the same @md to hash additional + * data. + */ +isc_result_t +isc_md_update(isc_md_t *md, const unsigned char *buf, const size_t len); + +/** + * isc_md_final: + * @md: message digest context + * @digest: the output buffer + * @digestlen: the length of the data written to @digest + * + * This function retrieves the digest value from @md and places it in @digest. + * If the @digestlen parameter is not NULL then the number of bytes of data + * written (i.e. the length of the digest) will be written to the integer at + * @digestlen, at most ISC_MAX_MD_SIZE bytes will be written. After calling + * this function no additional calls to isc_md_update() can be made. + */ +isc_result_t +isc_md_final(isc_md_t *md, unsigned char *digest, unsigned int *digestlen); + +/** + * isc_md_get_type: + * @md: message digest contezt + * + * This function return the isc_md_type_t previously set for the supplied + * message digest context or NULL if no isc_md_type_t has been set. + */ +const isc_md_type_t * +isc_md_get_md_type(isc_md_t *md); + +/** + * isc_md_size: + * + * This function return the size of the message digest when passed an isc_md_t + * structure, i.e. the size of the hash. + */ +size_t +isc_md_get_size(isc_md_t *md); + +/** + * isc_md_block_size: + * + * This function return the block size of the message digest when passed an + * isc_md_t structure. + */ +size_t +isc_md_get_block_size(isc_md_t *md); + +/** + * isc_md_size: + * + * This function return the size of the message digest when passed an + * isc_md_type_t , i.e. the size of the hash. + */ +size_t +isc_md_type_get_size(const isc_md_type_t *md_type); + +/** + * isc_md_block_size: + * + * This function return the block size of the message digest when passed an + * isc_md_type_t. + */ +size_t +isc_md_type_get_block_size(const isc_md_type_t *md_type); diff --git a/lib/isc/include/isc/mem.h b/lib/isc/include/isc/mem.h new file mode 100644 index 0000000..1542edd --- /dev/null +++ b/lib/isc/include/isc/mem.h @@ -0,0 +1,633 @@ +/* + * 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. + */ + +#ifndef ISC_MEM_H +#define ISC_MEM_H 1 + +/*! \file isc/mem.h */ + +#include <stdbool.h> +#include <stdio.h> + +#include <isc/lang.h> +#include <isc/mutex.h> +#include <isc/platform.h> +#include <isc/types.h> +#include <isc/util.h> + +ISC_LANG_BEGINDECLS + +#define ISC_MEM_LOWATER 0 +#define ISC_MEM_HIWATER 1 +typedef void (*isc_mem_water_t)(void *, int); + +/*% + * Define ISC_MEM_TRACKLINES=1 to turn on detailed tracing of memory + * allocation and freeing by file and line number. + */ +#ifndef ISC_MEM_TRACKLINES +#define ISC_MEM_TRACKLINES 1 +#endif /* ifndef ISC_MEM_TRACKLINES */ + +/*% + * Define ISC_MEM_CHECKOVERRUN=1 to turn on checks for using memory outside + * the requested space. This will increase the size of each allocation. + * + * If we are performing a Coverity static analysis then ISC_MEM_CHECKOVERRUN + * can hide bugs that would otherwise discovered so force to zero. + */ +#ifdef __COVERITY__ +#undef ISC_MEM_CHECKOVERRUN +#define ISC_MEM_CHECKOVERRUN 0 +#endif /* ifdef __COVERITY__ */ +#ifndef ISC_MEM_CHECKOVERRUN +#define ISC_MEM_CHECKOVERRUN 1 +#endif /* ifndef ISC_MEM_CHECKOVERRUN */ + +/*% + * Define ISC_MEMPOOL_NAMES=1 to make memory pools store a symbolic + * name so that the leaking pool can be more readily identified in + * case of a memory leak. + */ +#ifndef ISC_MEMPOOL_NAMES +#define ISC_MEMPOOL_NAMES 1 +#endif /* ifndef ISC_MEMPOOL_NAMES */ + +LIBISC_EXTERNAL_DATA extern unsigned int isc_mem_debugging; +LIBISC_EXTERNAL_DATA extern unsigned int isc_mem_defaultflags; + +/*@{*/ +#define ISC_MEM_DEBUGTRACE 0x00000001U +#define ISC_MEM_DEBUGRECORD 0x00000002U +#define ISC_MEM_DEBUGUSAGE 0x00000004U +#define ISC_MEM_DEBUGSIZE 0x00000008U +#define ISC_MEM_DEBUGCTX 0x00000010U +#define ISC_MEM_DEBUGALL 0x0000001FU +/*!< + * The variable isc_mem_debugging holds a set of flags for + * turning certain memory debugging options on or off at + * runtime. It is initialized to the value ISC_MEM_DEGBUGGING, + * which is 0 by default but may be overridden at compile time. + * The following flags can be specified: + * + * \li #ISC_MEM_DEBUGTRACE + * Log each allocation and free to isc_lctx. + * + * \li #ISC_MEM_DEBUGRECORD + * Remember each allocation, and match them up on free. + * Crash if a free doesn't match an allocation. + * + * \li #ISC_MEM_DEBUGUSAGE + * If a hi_water mark is set, print the maximum inuse memory + * every time it is raised once it exceeds the hi_water mark. + * + * \li #ISC_MEM_DEBUGSIZE + * Check the size argument being passed to isc_mem_put() matches + * that passed to isc_mem_get(). + * + * \li #ISC_MEM_DEBUGCTX + * Check the mctx argument being passed to isc_mem_put() matches + * that passed to isc_mem_get(). + */ +/*@}*/ + +#if ISC_MEM_TRACKLINES +#define _ISC_MEM_FILELINE , __FILE__, __LINE__ +#define _ISC_MEM_FLARG , const char *, unsigned int +#else /* if ISC_MEM_TRACKLINES */ +#define _ISC_MEM_FILELINE +#define _ISC_MEM_FLARG +#endif /* if ISC_MEM_TRACKLINES */ + +/*! + * Define ISC_MEM_USE_INTERNAL_MALLOC=1 to use the internal malloc() + * implementation in preference to the system one. The internal malloc() + * is very space-efficient, and quite fast on uniprocessor systems. It + * performs poorly on multiprocessor machines. + * JT: we can overcome the performance issue on multiprocessor machines + * by carefully separating memory contexts. + */ + +#if !defined(ISC_MEM_USE_INTERNAL_MALLOC) && !__SANITIZE_ADDRESS__ +#define ISC_MEM_USE_INTERNAL_MALLOC 0 +#endif /* ifndef ISC_MEM_USE_INTERNAL_MALLOC */ + +/* + * Flags for isc_mem_create() calls. + */ +#define ISC_MEMFLAG_RESERVED 0x00000001 /* reserved, obsoleted, don't use */ +#define ISC_MEMFLAG_INTERNAL 0x00000002 /* use internal malloc */ +#define ISC_MEMFLAG_FILL \ + 0x00000004 /* fill with pattern after alloc and frees */ + +/*% + * Define ISC_MEM_DEFAULTFILL=1 to turn filling the memory with pattern + * after alloc and free. + */ +#if !ISC_MEM_USE_INTERNAL_MALLOC + +#if ISC_MEM_DEFAULTFILL +#define ISC_MEMFLAG_DEFAULT ISC_MEMFLAG_FILL +#else /* if ISC_MEM_DEFAULTFILL */ +#define ISC_MEMFLAG_DEFAULT 0 +#endif /* if ISC_MEM_DEFAULTFILL */ + +#else /* if !ISC_MEM_USE_INTERNAL_MALLOC */ + +#if ISC_MEM_DEFAULTFILL +#define ISC_MEMFLAG_DEFAULT ISC_MEMFLAG_INTERNAL | ISC_MEMFLAG_FILL +#else /* if ISC_MEM_DEFAULTFILL */ +#define ISC_MEMFLAG_DEFAULT ISC_MEMFLAG_INTERNAL +#endif /* if ISC_MEM_DEFAULTFILL */ + +#endif /* if !ISC_MEM_USE_INTERNAL_MALLOC */ + +/*% + * isc_mem_putanddetach() is a convenience function for use where you + * have a structure with an attached memory context. + * + * Given: + * + * \code + * struct { + * ... + * isc_mem_t *mctx; + * ... + * } *ptr; + * + * isc_mem_t *mctx; + * + * isc_mem_putanddetach(&ptr->mctx, ptr, sizeof(*ptr)); + * \endcode + * + * is the equivalent of: + * + * \code + * mctx = NULL; + * isc_mem_attach(ptr->mctx, &mctx); + * isc_mem_detach(&ptr->mctx); + * isc_mem_put(mctx, ptr, sizeof(*ptr)); + * isc_mem_detach(&mctx); + * \endcode + */ + +/*% memory and memory pool methods */ +typedef struct isc_memmethods { + void *(*memget)(isc_mem_t *mctx, size_t size _ISC_MEM_FLARG); + void (*memput)(isc_mem_t *mctx, void *ptr, size_t size _ISC_MEM_FLARG); + void (*memputanddetach)(isc_mem_t **mctxp, void *ptr, + size_t size _ISC_MEM_FLARG); + void *(*memallocate)(isc_mem_t *mctx, size_t size _ISC_MEM_FLARG); + void *(*memreallocate)(isc_mem_t *mctx, void *ptr, + size_t size _ISC_MEM_FLARG); + char *(*memstrdup)(isc_mem_t *mctx, const char *s _ISC_MEM_FLARG); + char *(*memstrndup)(isc_mem_t *mctx, const char *s, + size_t size _ISC_MEM_FLARG); + void (*memfree)(isc_mem_t *mctx, void *ptr _ISC_MEM_FLARG); +} isc_memmethods_t; + +/*% + * This structure is actually just the common prefix of a memory context + * implementation's version of an isc_mem_t. + * \brief + * Direct use of this structure by clients is forbidden. mctx implementations + * may change the structure. 'magic' must be ISCAPI_MCTX_MAGIC for any of the + * isc_mem_ routines to work. mctx implementations must maintain all mctx + * invariants. + */ +struct isc_mem { + unsigned int impmagic; + unsigned int magic; + isc_memmethods_t *methods; +}; + +#define ISCAPI_MCTX_MAGIC ISC_MAGIC('A', 'm', 'c', 'x') +#define ISCAPI_MCTX_VALID(m) ((m) != NULL && (m)->magic == ISCAPI_MCTX_MAGIC) + +/*% + * This is the common prefix of a memory pool context. The same note as + * that for the mem structure applies. + */ +struct isc_mempool { + unsigned int impmagic; + unsigned int magic; +}; + +#define ISCAPI_MPOOL_MAGIC ISC_MAGIC('A', 'm', 'p', 'l') +#define ISCAPI_MPOOL_VALID(mp) \ + ((mp) != NULL && (mp)->magic == ISCAPI_MPOOL_MAGIC) + +/*% + * These functions are actually implemented in isc__mem_<function> + * (two underscores). The single-underscore macros are used to pass + * __FILE__ and __LINE__, and in the case of the put functions, to + * set the pointer being freed to NULL in the calling function. + * + * Many of these functions have a further isc___mem_<function> + * (three underscores) implementation, which is called indirectly + * via the isc_memmethods structure in the mctx so that dynamically + * loaded modules can use them even if named is statically linked. + */ + +#define ISCMEMFUNC(sfx) isc__mem_##sfx +#define ISCMEMPOOLFUNC(sfx) isc__mempool_##sfx + +#define isc_mem_get(c, s) ISCMEMFUNC(get)((c), (s)_ISC_MEM_FILELINE) +#define isc_mem_allocate(c, s) ISCMEMFUNC(allocate)((c), (s)_ISC_MEM_FILELINE) +#define isc_mem_reallocate(c, p, s) \ + ISCMEMFUNC(reallocate)((c), (p), (s)_ISC_MEM_FILELINE) +#define isc_mem_strdup(c, p) ISCMEMFUNC(strdup)((c), (p)_ISC_MEM_FILELINE) +#define isc_mem_strndup(c, p, s) \ + ISCMEMFUNC(strndup)((c), (p), (s)_ISC_MEM_FILELINE) +#define isc_mempool_get(c) ISCMEMPOOLFUNC(get)((c)_ISC_MEM_FILELINE) + +#define isc_mem_put(c, p, s) \ + do { \ + ISCMEMFUNC(put)((c), (p), (s)_ISC_MEM_FILELINE); \ + (p) = NULL; \ + } while (0) +#define isc_mem_putanddetach(c, p, s) \ + do { \ + ISCMEMFUNC(putanddetach)((c), (p), (s)_ISC_MEM_FILELINE); \ + (p) = NULL; \ + } while (0) +#define isc_mem_free(c, p) \ + do { \ + ISCMEMFUNC(free)((c), (p)_ISC_MEM_FILELINE); \ + (p) = NULL; \ + } while (0) +#define isc_mempool_put(c, p) \ + do { \ + ISCMEMPOOLFUNC(put)((c), (p)_ISC_MEM_FILELINE); \ + (p) = NULL; \ + } while (0) + +/*@{*/ +void +isc_mem_create(isc_mem_t **mctxp); + +/*!< + * \brief Create a memory context. + * + * Requires: + * mctxp != NULL && *mctxp == NULL */ +/*@}*/ + +/*@{*/ +void +isc_mem_attach(isc_mem_t *, isc_mem_t **); +void +isc_mem_detach(isc_mem_t **); +/*!< + * \brief Attach to / detach from a memory context. + * + * This is intended for applications that use multiple memory contexts + * in such a way that it is not obvious when the last allocations from + * a given context has been freed and destroying the context is safe. + * + * Most applications do not need to call these functions as they can + * simply create a single memory context at the beginning of main() + * and destroy it at the end of main(), thereby guaranteeing that it + * is not destroyed while there are outstanding allocations. + */ +/*@}*/ + +void +isc_mem_destroy(isc_mem_t **); +/*%< + * Destroy a memory context. + */ + +void +isc_mem_stats(isc_mem_t *mctx, FILE *out); +/*%< + * Print memory usage statistics for 'mctx' on the stream 'out'. + */ + +void +isc_mem_setdestroycheck(isc_mem_t *mctx, bool on); +/*%< + * If 'on' is true, 'mctx' will check for memory leaks when + * destroyed and abort the program if any are present. + */ + +size_t +isc_mem_inuse(isc_mem_t *mctx); +/*%< + * Get an estimate of the amount of memory in use in 'mctx', in bytes. + * This includes quantization overhead, but does not include memory + * allocated from the system but not yet used. + */ + +size_t +isc_mem_maxinuse(isc_mem_t *mctx); +/*%< + * Get an estimate of the largest amount of memory that has been in + * use in 'mctx' at any time. + */ + +size_t +isc_mem_total(isc_mem_t *mctx); +/*%< + * Get the total amount of memory in 'mctx', in bytes, including memory + * not yet used. + */ + +bool +isc_mem_isovermem(isc_mem_t *mctx); +/*%< + * Return true iff the memory context is in "over memory" state, i.e., + * a hiwater mark has been set and the used amount of memory has exceeds + * the mark. + */ + +void +isc_mem_setwater(isc_mem_t *mctx, isc_mem_water_t water, void *water_arg, + size_t hiwater, size_t lowater); +/*%< + * Set high and low water marks for this memory context. + * + * When the memory usage of 'mctx' exceeds 'hiwater', + * '(water)(water_arg, #ISC_MEM_HIWATER)' will be called. 'water' needs to + * call isc_mem_waterack() with #ISC_MEM_HIWATER to acknowledge the state + * change. 'water' may be called multiple times. + * + * When the usage drops below 'lowater', 'water' will again be called, this + * time with #ISC_MEM_LOWATER. 'water' need to calls isc_mem_waterack() with + * #ISC_MEM_LOWATER to acknowledge the change. + * + * static void + * water(void *arg, int mark) { + * struct foo *foo = arg; + * + * LOCK(&foo->marklock); + * if (foo->mark != mark) { + * foo->mark = mark; + * .... + * isc_mem_waterack(foo->mctx, mark); + * } + * UNLOCK(&foo->marklock); + * } + * + * If 'water' is NULL then 'water_arg', 'hi_water' and 'lo_water' are + * ignored and the state is reset. + * + * Requires: + * + * 'water' is not NULL. + * hi_water >= lo_water + */ + +void +isc_mem_waterack(isc_mem_t *ctx, int mark); +/*%< + * Called to acknowledge changes in signaled by calls to 'water'. + */ + +void +isc_mem_checkdestroyed(FILE *file); +/*%< + * Check that all memory contexts have been destroyed. + * Prints out those that have not been. + * Fatally fails if there are still active contexts. + */ + +unsigned int +isc_mem_references(isc_mem_t *ctx); +/*%< + * Return the current reference count. + */ + +void +isc_mem_setname(isc_mem_t *ctx, const char *name, void *tag); +/*%< + * Name 'ctx'. + * + * Notes: + * + *\li Only the first 15 characters of 'name' will be copied. + * + *\li 'tag' is for debugging purposes only. + * + * Requires: + * + *\li 'ctx' is a valid ctx. + */ + +const char * +isc_mem_getname(isc_mem_t *ctx); +/*%< + * Get the name of 'ctx', as previously set using isc_mem_setname(). + * + * Requires: + *\li 'ctx' is a valid ctx. + * + * Returns: + *\li A non-NULL pointer to a null-terminated string. + * If the ctx has not been named, the string is + * empty. + */ + +void * +isc_mem_gettag(isc_mem_t *ctx); +/*%< + * Get the tag value for 'task', as previously set using isc_mem_setname(). + * + * Requires: + *\li 'ctx' is a valid ctx. + * + * Notes: + *\li This function is for debugging purposes only. + * + * Requires: + *\li 'ctx' is a valid task. + */ + +#ifdef HAVE_LIBXML2 +int +isc_mem_renderxml(void *writer0); +/*%< + * Render all contexts' statistics and status in XML for writer. + */ +#endif /* HAVE_LIBXML2 */ + +#ifdef HAVE_JSON_C +isc_result_t +isc_mem_renderjson(void *memobj0); +/*%< + * Render all contexts' statistics and status in JSON. + */ +#endif /* HAVE_JSON_C */ + +/* + * Memory pools + */ + +void +isc_mempool_create(isc_mem_t *mctx, size_t size, isc_mempool_t **mpctxp); +/*%< + * Create a memory pool. + * + * Requires: + *\li mctx is a valid memory context. + *\li size > 0 + *\li mpctxp != NULL and *mpctxp == NULL + * + * Defaults: + *\li maxalloc = UINT_MAX + *\li freemax = 1 + *\li fillcount = 1 + * + * Returns: + *\li #ISC_R_NOMEMORY -- not enough memory to create pool + *\li #ISC_R_SUCCESS -- all is well. + */ + +void +isc_mempool_destroy(isc_mempool_t **mpctxp); +/*%< + * Destroy a memory pool. + * + * Requires: + *\li mpctxp != NULL && *mpctxp is a valid pool. + *\li The pool has no un"put" allocations outstanding + */ + +void +isc_mempool_setname(isc_mempool_t *mpctx, const char *name); +/*%< + * Associate a name with a memory pool. At most 15 characters may be used. + * + * Requires: + *\li mpctx is a valid pool. + *\li name != NULL; + */ + +/* + * The following functions get/set various parameters. Note that due to + * the unlocked nature of pools these are potentially random values unless + * the imposed externally provided locking protocols are followed. + * + * Also note that the quota limits will not always take immediate effect. + * For instance, setting "maxalloc" to a number smaller than the currently + * allocated count is permitted. New allocations will be refused until + * the count drops below this threshold. + * + * All functions require (in addition to other requirements): + * mpctx is a valid memory pool + */ + +unsigned int +isc_mempool_getfreemax(isc_mempool_t *mpctx); +/*%< + * Returns the maximum allowed size of the free list. + */ + +void +isc_mempool_setfreemax(isc_mempool_t *mpctx, unsigned int limit); +/*%< + * Sets the maximum allowed size of the free list. + */ + +unsigned int +isc_mempool_getfreecount(isc_mempool_t *mpctx); +/*%< + * Returns current size of the free list. + */ + +unsigned int +isc_mempool_getmaxalloc(isc_mempool_t *mpctx); +/*!< + * Returns the maximum allowed number of allocations. + */ + +void +isc_mempool_setmaxalloc(isc_mempool_t *mpctx, unsigned int limit); +/*%< + * Sets the maximum allowed number of allocations. + * + * Additional requirements: + *\li limit > 0 + */ + +unsigned int +isc_mempool_getallocated(isc_mempool_t *mpctx); +/*%< + * Returns the number of items allocated from this pool. + */ + +unsigned int +isc_mempool_getfillcount(isc_mempool_t *mpctx); +/*%< + * Returns the number of items allocated as a block from the parent memory + * context when the free list is empty. + */ + +void +isc_mempool_setfillcount(isc_mempool_t *mpctx, unsigned int limit); +/*%< + * Sets the fillcount. + * + * Additional requirements: + *\li limit > 0 + */ + +#if defined(UNIT_TESTING) && defined(malloc) +/* + * cmocka.h redefined malloc as a macro, we #undef it + * to avoid replacing ISC_ATTR_MALLOC with garbage. + */ +#pragma push_macro("malloc") +#undef malloc +#define POP_MALLOC_MACRO 1 +#endif + +/* + * Pseudo-private functions for use via macros. Do not call directly. + */ +void ISCMEMFUNC(putanddetach)(isc_mem_t **, void *, size_t _ISC_MEM_FLARG); +void ISCMEMFUNC(put)(isc_mem_t *, void *, size_t _ISC_MEM_FLARG); +void ISCMEMFUNC(free)(isc_mem_t *, void *_ISC_MEM_FLARG); + +ISC_ATTR_RETURNS_NONNULL +ISC_ATTR_MALLOC_DEALLOCATOR_IDX(ISCMEMFUNC(put), 2) +void *ISCMEMFUNC(get)(isc_mem_t *, size_t _ISC_MEM_FLARG); + +ISC_ATTR_RETURNS_NONNULL +ISC_ATTR_MALLOC_DEALLOCATOR_IDX(ISCMEMFUNC(free), 2) +void *ISCMEMFUNC(allocate)(isc_mem_t *, size_t _ISC_MEM_FLARG); + +ISC_ATTR_RETURNS_NONNULL +ISC_ATTR_DEALLOCATOR_IDX(ISCMEMFUNC(free), 2) +void *ISCMEMFUNC(reallocate)(isc_mem_t *, void *, size_t _ISC_MEM_FLARG); + +ISC_ATTR_RETURNS_NONNULL +ISC_ATTR_MALLOC_DEALLOCATOR_IDX(ISCMEMFUNC(free), 2) +char *ISCMEMFUNC(strdup)(isc_mem_t *, const char *_ISC_MEM_FLARG); +char *ISCMEMFUNC(strndup)(isc_mem_t *, const char *, size_t _ISC_MEM_FLARG); + +ISC_ATTR_MALLOC_DEALLOCATOR_IDX(ISCMEMPOOLFUNC(put), 2) +void *ISCMEMPOOLFUNC(get)(isc_mempool_t *_ISC_MEM_FLARG); + +void ISCMEMPOOLFUNC(put)(isc_mempool_t *, void *_ISC_MEM_FLARG); + +#ifdef POP_MALLOC_MACRO +/* + * Restore cmocka.h macro for malloc. + */ +#pragma pop_macro("malloc") +#endif + +ISC_LANG_ENDDECLS + +#endif /* ISC_MEM_H */ diff --git a/lib/isc/include/isc/meminfo.h b/lib/isc/include/isc/meminfo.h new file mode 100644 index 0000000..3cda5b2 --- /dev/null +++ b/lib/isc/include/isc/meminfo.h @@ -0,0 +1,33 @@ +/* + * 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. + */ + +#ifndef ISC_MEMINFO_H +#define ISC_MEMINFO_H 1 + +#include <inttypes.h> + +#include <isc/lang.h> +#include <isc/types.h> + +ISC_LANG_BEGINDECLS + +uint64_t +isc_meminfo_totalphys(void); +/*%< + * Return total available physical memory in bytes, or 0 if this cannot + * be determined + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_MEMINFO_H */ diff --git a/lib/isc/include/isc/mutexblock.h b/lib/isc/include/isc/mutexblock.h new file mode 100644 index 0000000..d2144e1 --- /dev/null +++ b/lib/isc/include/isc/mutexblock.h @@ -0,0 +1,57 @@ +/* + * 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. + */ + +#ifndef ISC_MUTEXBLOCK_H +#define ISC_MUTEXBLOCK_H 1 + +/*! \file isc/mutexblock.h */ + +#include <isc/lang.h> +#include <isc/mutex.h> +#include <isc/types.h> + +ISC_LANG_BEGINDECLS + +void +isc_mutexblock_init(isc_mutex_t *block, unsigned int count); +/*%< + * Initialize a block of locks. If an error occurs all initialized locks + * will be destroyed, if possible. + * + * Requires: + * + *\li block != NULL + * + *\li count > 0 + * + */ + +void +isc_mutexblock_destroy(isc_mutex_t *block, unsigned int count); +/*%< + * Destroy a block of locks. + * + * Requires: + * + *\li block != NULL + * + *\li count > 0 + * + *\li Each lock in the block be initialized via isc_mutex_init() or + * the whole block was initialized via isc_mutex_initblock(). + * + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_MUTEXBLOCK_H */ diff --git a/lib/isc/include/isc/netaddr.h b/lib/isc/include/isc/netaddr.h new file mode 100644 index 0000000..519aec7 --- /dev/null +++ b/lib/isc/include/isc/netaddr.h @@ -0,0 +1,199 @@ +/* + * 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. + */ + +#ifndef ISC_NETADDR_H +#define ISC_NETADDR_H 1 + +/*! \file isc/netaddr.h */ + +#include <inttypes.h> +#include <stdbool.h> + +#include <isc/lang.h> +#include <isc/net.h> +#include <isc/types.h> + +#ifdef ISC_PLATFORM_HAVESYSUNH +#include <sys/types.h> +#include <sys/un.h> +#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */ + +ISC_LANG_BEGINDECLS + +/* + * Any updates to this structure should also be applied in + * contrib/modules/dlz/dlz_minmal.h. + */ +struct isc_netaddr { + unsigned int family; + union { + struct in_addr in; + struct in6_addr in6; +#ifdef ISC_PLATFORM_HAVESYSUNH + char un[sizeof(((struct sockaddr_un *)0)->sun_path)]; +#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */ + } type; + uint32_t zone; +}; + +bool +isc_netaddr_equal(const isc_netaddr_t *a, const isc_netaddr_t *b); + +/*%< + * Compare network addresses 'a' and 'b'. Return #true if + * they are equal, #false if not. + */ + +bool +isc_netaddr_eqprefix(const isc_netaddr_t *a, const isc_netaddr_t *b, + unsigned int prefixlen); +/*%< + * Compare the 'prefixlen' most significant bits of the network + * addresses 'a' and 'b'. If 'b''s scope is zero then 'a''s scope is + * ignored. Return #true if they are equal, #false if not. + */ + +isc_result_t +isc_netaddr_masktoprefixlen(const isc_netaddr_t *s, unsigned int *lenp); +/*%< + * Convert a netmask in 's' into a prefix length in '*lenp'. + * The mask should consist of zero or more '1' bits in the + * most significant part of the address, followed by '0' bits. + * If this is not the case, #ISC_R_MASKNONCONTIG is returned. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_MASKNONCONTIG + */ + +isc_result_t +isc_netaddr_totext(const isc_netaddr_t *netaddr, isc_buffer_t *target); +/*%< + * Append a text representation of 'sockaddr' to the buffer 'target'. + * The text is NOT null terminated. Handles IPv4 and IPv6 addresses. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOSPACE The text or the null termination did not fit. + *\li #ISC_R_FAILURE Unspecified failure + */ + +void +isc_netaddr_format(const isc_netaddr_t *na, char *array, unsigned int size); +/*%< + * Format a human-readable representation of the network address '*na' + * into the character array 'array', which is of size 'size'. + * The resulting string is guaranteed to be null-terminated. + */ + +#define ISC_NETADDR_FORMATSIZE \ + sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:XXX.XXX.XXX.XXX%SSSSSSSSSS") +/*%< + * Minimum size of array to pass to isc_netaddr_format(). + */ + +void +isc_netaddr_fromsockaddr(isc_netaddr_t *netaddr, const isc_sockaddr_t *source); + +void +isc_netaddr_fromin(isc_netaddr_t *netaddr, const struct in_addr *ina); + +void +isc_netaddr_fromin6(isc_netaddr_t *netaddr, const struct in6_addr *ina6); + +isc_result_t +isc_netaddr_frompath(isc_netaddr_t *netaddr, const char *path); + +void +isc_netaddr_setzone(isc_netaddr_t *netaddr, uint32_t zone); + +uint32_t +isc_netaddr_getzone(const isc_netaddr_t *netaddr); + +void +isc_netaddr_any(isc_netaddr_t *netaddr); +/*%< + * Return the IPv4 wildcard address. + */ + +void +isc_netaddr_any6(isc_netaddr_t *netaddr); +/*%< + * Return the IPv6 wildcard address. + */ + +void +isc_netaddr_unspec(isc_netaddr_t *netaddr); +/*%< + * Initialize as AF_UNSPEC address. + */ + +bool +isc_netaddr_ismulticast(const isc_netaddr_t *na); +/*%< + * Returns true if the address is a multicast address. + */ + +bool +isc_netaddr_isexperimental(const isc_netaddr_t *na); +/*%< + * Returns true if the address is a experimental (CLASS E) address. + */ + +bool +isc_netaddr_islinklocal(const isc_netaddr_t *na); +/*%< + * Returns #true if the address is a link local address. + */ + +bool +isc_netaddr_issitelocal(const isc_netaddr_t *na); +/*%< + * Returns #true if the address is a site local address. + */ + +bool +isc_netaddr_isnetzero(const isc_netaddr_t *na); +/*%< + * Returns #true if the address is in net zero. + */ + +void +isc_netaddr_fromv4mapped(isc_netaddr_t *t, const isc_netaddr_t *s); +/*%< + * Convert an IPv6 v4mapped address into an IPv4 address. + */ + +isc_result_t +isc_netaddr_prefixok(const isc_netaddr_t *na, unsigned int prefixlen); +/* + * Test whether the netaddr 'na' and 'prefixlen' are consistent. + * e.g. prefixlen within range. + * na does not have bits set which are not covered by the prefixlen. + * + * Returns: + * ISC_R_SUCCESS + * ISC_R_RANGE prefixlen out of range + * ISC_R_NOTIMPLEMENTED unsupported family + * ISC_R_FAILURE extra bits. + */ + +bool +isc_netaddr_isloopback(const isc_netaddr_t *na); +/* + * Test whether the netaddr 'na' is a loopback IPv4 or IPv6 address (in + * 127.0.0.0/8 or ::1). + */ +ISC_LANG_ENDDECLS + +#endif /* ISC_NETADDR_H */ diff --git a/lib/isc/include/isc/netmgr.h b/lib/isc/include/isc/netmgr.h new file mode 100644 index 0000000..f1747be --- /dev/null +++ b/lib/isc/include/isc/netmgr.h @@ -0,0 +1,540 @@ +/* + * 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. + */ + +#pragma once + +#include <unistd.h> + +#include <isc/mem.h> +#include <isc/region.h> +#include <isc/result.h> +#include <isc/types.h> + +#ifndef _WIN32 +#include <sys/socket.h> +#include <sys/types.h> +#endif + +#if defined(SO_REUSEPORT_LB) || (defined(SO_REUSEPORT) && defined(__linux__)) +#define HAVE_SO_REUSEPORT_LB 1 +#endif + +/* + * Replacement for isc_sockettype_t provided by socket.h. + */ +typedef enum { + isc_socktype_tcp = 1, + isc_socktype_udp = 2, + isc_socktype_unix = 3, + isc_socktype_raw = 4 +} isc_socktype_t; + +typedef void (*isc_nm_recv_cb_t)(isc_nmhandle_t *handle, isc_result_t eresult, + isc_region_t *region, void *cbarg); +/*%< + * Callback function to be used when receiving a packet. + * + * 'handle' the handle that can be used to send back the answer. + * 'eresult' the result of the event. + * 'region' contains the received data, if any. It will be freed + * after return by caller. + * 'cbarg' the callback argument passed to isc_nm_listenudp(), + * isc_nm_listentcpdns(), or isc_nm_read(). + */ +typedef isc_result_t (*isc_nm_accept_cb_t)(isc_nmhandle_t *handle, + isc_result_t result, void *cbarg); +/*%< + * Callback function to be used when accepting a connection. (This differs + * from isc_nm_cb_t below in that it returns a result code.) + * + * 'handle' the handle that can be used to send back the answer. + * 'eresult' the result of the event. + * 'cbarg' the callback argument passed to isc_nm_listentcp() or + * isc_nm_listentcpdns(). + */ + +typedef void (*isc_nm_cb_t)(isc_nmhandle_t *handle, isc_result_t result, + void *cbarg); +/*%< + * Callback function for other network completion events (send, connect). + * + * 'handle' the handle on which the event took place. + * 'eresult' the result of the event. + * 'cbarg' the callback argument passed to isc_nm_send(), + * isc_nm_tcp_connect(), or isc_nm_listentcp() + */ + +typedef void (*isc_nm_opaquecb_t)(void *arg); +/*%< + * Opaque callback function, used for isc_nmhandle 'reset' and 'free' + * callbacks. + */ + +typedef void (*isc_nm_workcb_t)(void *arg); +typedef void (*isc_nm_after_workcb_t)(void *arg, isc_result_t result); +/*%< + * Callback functions for libuv threadpool work (see uv_work_t) + */ + +void +isc_nm_attach(isc_nm_t *mgr, isc_nm_t **dst); +void +isc_nm_detach(isc_nm_t **mgr0); +/*%< + * Attach/detach a network manager. When all references have been + * released, the network manager is shut down, freeing all resources. + * Destroy is working the same way as detach, but it actively waits + * for all other references to be gone. + */ + +/* Return thread ID of current thread, or ISC_NETMGR_TID_UNKNOWN */ +int +isc_nm_tid(void); + +void +isc_nmsocket_close(isc_nmsocket_t **sockp); +/*%< + * isc_nmsocket_close() detaches a listening socket that was + * created by isc_nm_listenudp(), isc_nm_listentcp(), or + * isc_nm_listentcpdns(). Once there are no remaining child + * sockets with active handles, the socket will be closed. + */ + +#ifdef NETMGR_TRACE +#define isc_nmhandle_attach(handle, dest) \ + isc__nmhandle_attach(handle, dest, __FILE__, __LINE__, __func__) +#define isc_nmhandle_detach(handlep) \ + isc__nmhandle_detach(handlep, __FILE__, __LINE__, __func__) +#define FLARG , const char *file, unsigned int line, const char *func +#else +#define isc_nmhandle_attach(handle, dest) isc__nmhandle_attach(handle, dest) +#define isc_nmhandle_detach(handlep) isc__nmhandle_detach(handlep) +#define FLARG +#endif + +void +isc__nmhandle_attach(isc_nmhandle_t *handle, isc_nmhandle_t **dest FLARG); +void +isc__nmhandle_detach(isc_nmhandle_t **handlep FLARG); +/*%< + * Increment/decrement the reference counter in a netmgr handle, + * but (unlike the attach/detach functions) do not change the pointer + * value. If reference counters drop to zero, the handle can be + * marked inactive, possibly triggering deletion of its associated + * socket. + * + * (This will be used to prevent a client from being cleaned up when + * it's passed to an isc_task event handler. The libuv code would not + * otherwise know that the handle was in use and might free it, along + * with the client.) + */ +#undef FLARG + +void * +isc_nmhandle_getdata(isc_nmhandle_t *handle); + +void * +isc_nmhandle_getextra(isc_nmhandle_t *handle); + +bool +isc_nmhandle_is_stream(isc_nmhandle_t *handle); + +void +isc_nmhandle_setdata(isc_nmhandle_t *handle, void *arg, + isc_nm_opaquecb_t doreset, isc_nm_opaquecb_t dofree); +/*%< + * isc_nmhandle_t has a void* opaque field (for example, ns_client_t). + * We reuse handle and `opaque` can also be reused between calls. + * This function sets this field and two callbacks: + * - doreset resets the `opaque` to initial state + * - dofree frees everything associated with `opaque` + */ + +void +isc_nmhandle_settimeout(isc_nmhandle_t *handle, uint32_t timeout); +void +isc_nmhandle_cleartimeout(isc_nmhandle_t *handle); +/*%< + * Set/clear the read/recv timeout for the socket connected to 'handle' + * to 'timeout' (in milliseconds), and reset the timer. + * + * When this is called on a 'wrapper' socket handle (for example, + * a TCPDNS socket wrapping a TCP connection), the timer is set for + * both socket layers. + */ + +void +isc_nmhandle_keepalive(isc_nmhandle_t *handle, bool value); +/*%< + * Enable/disable keepalive on this connection by setting it to 'value'. + * + * When keepalive is active, we switch to using the keepalive timeout + * to determine when to close a connection, rather than the idle timeout. + * + * This applies only to TCP-based DNS connections (i.e., TCPDNS). + * On other types of connection it has no effect. + */ + +isc_sockaddr_t +isc_nmhandle_peeraddr(isc_nmhandle_t *handle); +/*%< + * Return the peer address for the given handle. + */ +isc_sockaddr_t +isc_nmhandle_localaddr(isc_nmhandle_t *handle); +/*%< + * Return the local address for the given handle. + */ + +isc_nm_t * +isc_nmhandle_netmgr(isc_nmhandle_t *handle); +/*%< + * Return a pointer to the netmgr object for the given handle. + */ + +isc_result_t +isc_nm_listenudp(isc_nm_t *mgr, isc_sockaddr_t *iface, isc_nm_recv_cb_t cb, + void *cbarg, size_t extrasize, isc_nmsocket_t **sockp); +/*%< + * Start listening for UDP packets on interface 'iface' using net manager + * 'mgr'. + * + * On success, 'sockp' will be updated to contain a new listening UDP socket. + * + * When a packet is received on the socket, 'cb' will be called with 'cbarg' + * as its argument. + * + * When handles are allocated for the socket, 'extrasize' additional bytes + * can be allocated along with the handle for an associated object, which + * can then be freed automatically when the handle is destroyed. + */ + +void +isc_nm_udpconnect(isc_nm_t *mgr, isc_sockaddr_t *local, isc_sockaddr_t *peer, + isc_nm_cb_t cb, void *cbarg, unsigned int timeout, + size_t extrahandlesize); +/*%< + * Open a UDP socket, bind to 'local' and connect to 'peer', and + * immediately call 'cb' with a handle so that the caller can begin + * sending packets over UDP. + * + * When handles are allocated for the socket, 'extrasize' additional bytes + * can be allocated along with the handle for an associated object, which + * can then be freed automatically when the handle is destroyed. + * + * 'timeout' specifies the timeout interval in milliseconds. + * + * The connected socket can only be accessed via the handle passed to + * 'cb'. + */ + +void +isc_nm_stoplistening(isc_nmsocket_t *sock); +/*%< + * Stop listening on socket 'sock'. + */ + +void +isc_nm_pause(isc_nm_t *mgr); +/*%< + * Pause all processing, equivalent to taskmgr exclusive tasks. + * It won't return until all workers have been paused. + */ + +void +isc_nm_resume(isc_nm_t *mgr); +/*%< + * Resume paused processing. It will return immediately after signalling + * workers to resume. + */ + +void +isc_nm_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg); +/* + * Begin (or continue) reading on the socket associated with 'handle', and + * update its recv callback to 'cb', which will be called as soon as there + * is data to process. + */ + +void +isc_nm_pauseread(isc_nmhandle_t *handle); +/*%< + * Pause reading on this handle's socket, but remember the callback. + * + * Requires: + * \li 'handle' is a valid netmgr handle. + */ + +void +isc_nm_cancelread(isc_nmhandle_t *handle); +/*%< + * Cancel reading on a connected socket. Calls the read/recv callback on + * active handles with a result code of ISC_R_CANCELED. + * + * Requires: + * \li 'sock' is a valid netmgr socket + * \li ...for which a read/recv callback has been defined. + */ + +void +isc_nm_resumeread(isc_nmhandle_t *handle); +/*%< + * Resume reading on the handle's socket. + * + * Requires: + * \li 'handle' is a valid netmgr handle. + * \li ...for a socket with a defined read/recv callback. + */ + +void +isc_nm_send(isc_nmhandle_t *handle, isc_region_t *region, isc_nm_cb_t cb, + void *cbarg); +/*%< + * Send the data in 'region' via 'handle'. Afterward, the callback 'cb' is + * called with the argument 'cbarg'. + * + * 'region' is not copied; it has to be allocated beforehand and freed + * in 'cb'. + */ + +isc_result_t +isc_nm_listentcp(isc_nm_t *mgr, isc_sockaddr_t *iface, + isc_nm_accept_cb_t accept_cb, void *accept_cbarg, + size_t extrahandlesize, int backlog, isc_quota_t *quota, + isc_nmsocket_t **sockp); +/*%< + * Start listening for raw messages over the TCP interface 'iface', using + * net manager 'mgr'. + * + * On success, 'sockp' will be updated to contain a new listening TCP + * socket. + * + * When connection is accepted on the socket, 'accept_cb' will be called with + * 'accept_cbarg' as its argument. The callback is expected to start a read. + * + * When handles are allocated for the socket, 'extrasize' additional bytes + * will be allocated along with the handle for an associated object. + * + * If 'quota' is not NULL, then the socket is attached to the specified + * quota. This allows us to enforce TCP client quota limits. + * + */ + +void +isc_nm_tcpconnect(isc_nm_t *mgr, isc_sockaddr_t *local, isc_sockaddr_t *peer, + isc_nm_cb_t cb, void *cbarg, unsigned int timeout, + size_t extrahandlesize); +/*%< + * Create a socket using netmgr 'mgr', bind it to the address 'local', + * and connect it to the address 'peer'. + * + * When the connection is complete or has timed out, call 'cb' with + * argument 'cbarg'. Allocate 'extrahandlesize' additional bytes along + * with the handle to use for an associated object. + * + * 'timeout' specifies the timeout interval in milliseconds. + * + * The connected socket can only be accessed via the handle passed to + * 'cb'. + */ + +isc_result_t +isc_nm_listentcpdns(isc_nm_t *mgr, isc_sockaddr_t *iface, + isc_nm_recv_cb_t recv_cb, void *recv_cbarg, + isc_nm_accept_cb_t accept_cb, void *accept_cbarg, + size_t extrahandlesize, int backlog, isc_quota_t *quota, + isc_nmsocket_t **sockp); +/*%< + * Start listening for DNS messages over the TCP interface 'iface', using + * net manager 'mgr'. + * + * On success, 'sockp' will be updated to contain a new listening TCPDNS + * socket. This is a wrapper around a raw TCP socket, which sends and + * receives DNS messages via that socket. It handles message buffering + * and pipelining, and automatically prepends messages with a two-byte + * length field. + * + * When a complete DNS message is received on the socket, 'cb' will be + * called with 'cbarg' as its argument. + * + * When a new TCPDNS connection is accepted, 'accept_cb' will be called + * with 'accept_cbarg' as its argument. + * + * When handles are allocated for the socket, 'extrasize' additional bytes + * will be allocated along with the handle for an associated object + * (typically ns_client). + * + * 'quota' is passed to isc_nm_listentcp() when opening the raw TCP socket. + */ + +void +isc_nm_tcpdns_sequential(isc_nmhandle_t *handle); +/*%< + * Disable pipelining on this connection. Each DNS packet will be only + * processed after the previous completes. + * + * The socket must be unpaused after the query is processed. This is done + * the response is sent, or if we're dropping the query, it will be done + * when a handle is fully dereferenced by calling the socket's + * closehandle_cb callback. + * + * Note: This can only be run while a message is being processed; if it is + * run before any messages are read, no messages will be read. + * + * Also note: once this has been set, it cannot be reversed for a given + * connection. + */ + +void +isc_nm_tcpdns_keepalive(isc_nmhandle_t *handle, bool value); +/*%< + * Enable/disable keepalive on this connection by setting it to 'value'. + * + * When keepalive is active, we switch to using the keepalive timeout + * to determine when to close a connection, rather than the idle timeout. + */ + +void +isc_nm_settimeouts(isc_nm_t *mgr, uint32_t init, uint32_t idle, + uint32_t keepalive, uint32_t advertised); +/*%< + * Sets the initial, idle, and keepalive timeout values (in milliseconds) to use + * for TCP connections, and the timeout value to advertise in responses using + * the EDNS TCP Keepalive option (which should ordinarily be the same + * as 'keepalive'), in milliseconds. + * + * Requires: + * \li 'mgr' is a valid netmgr. + */ + +bool +isc_nm_getloadbalancesockets(isc_nm_t *mgr); +void +isc_nm_setloadbalancesockets(isc_nm_t *mgr, bool enabled); +/*%< + * Get and set value of load balancing of the sockets. + * + * Requires: + * \li 'mgr' is a valid netmgr. + */ + +void +isc_nm_gettimeouts(isc_nm_t *mgr, uint32_t *initial, uint32_t *idle, + uint32_t *keepalive, uint32_t *advertised); +/*%< + * Gets the initial, idle, keepalive, or advertised timeout values, + * in milliseconds. + * + * Any integer pointer parameter not set to NULL will be updated to + * contain the corresponding timeout value. + * + * Requires: + * \li 'mgr' is a valid netmgr. + */ + +void +isc_nm_maxudp(isc_nm_t *mgr, uint32_t maxudp); +/*%< + * Simulate a broken firewall that blocks UDP messages larger than a given + * size. + */ + +void +isc_nm_setstats(isc_nm_t *mgr, isc_stats_t *stats); +/*%< + * Set a socket statistics counter set 'stats' for 'mgr'. + * + * Requires: + *\li 'mgr' is valid and doesn't have stats already set. + * + *\li stats is a valid set of statistics counters supporting the + * full range of socket-related stats counter numbers. + */ + +void +isc_nm_tcpdnsconnect(isc_nm_t *mgr, isc_sockaddr_t *local, isc_sockaddr_t *peer, + isc_nm_cb_t cb, void *cbarg, unsigned int timeout, + size_t extrahandlesize); +/*%< + * Establish a DNS client connection via a TCP connection, bound to + * the address 'local' and connected to the address 'peer'. + * + * When the connection is complete or has timed out, call 'cb' with + * argument 'cbarg'. Allocate 'extrahandlesize' additional bytes along + * with the handle to use for an associated object. + * + * 'timeout' specifies the timeout interval in milliseconds. + * + * The connected socket can only be accessed via the handle passed to + * 'cb'. + */ + +void +isc_nm_task_enqueue(isc_nm_t *mgr, isc_task_t *task, int threadid); +/*%< + * Enqueue the 'task' onto the netmgr ievents queue. + * + * Requires: + * \li 'mgr' is a valid netmgr object + * \li 'task' is a valid task + * \li 'threadid' is either the preferred netmgr tid or -1, in which case + * tid will be picked randomly. The threadid is capped (by modulo) to + * maximum number of 'workers' as specifed in isc_nm_start() + */ + +void +isc_nm_work_offload(isc_nm_t *mgr, isc_nm_workcb_t work_cb, + isc_nm_after_workcb_t after_work_cb, void *data); +/*%< + * Schedules a job to be handled by the libuv thread pool (see uv_work_t). + * The function specified in `work_cb` will be run by a thread in the + * thread pool; when complete, the `after_work_cb` function will run. + * + * Requires: + * \li 'mgr' is a valid netmgr object. + * \li We are currently running in a network manager thread. + */ + +void +isc__nm_force_tid(int tid); +/*%< + * Force the thread ID to 'tid'. This is STRICTLY for use in unit + * tests and should not be used in any production code. + */ + +void +isc_nmhandle_setwritetimeout(isc_nmhandle_t *handle, uint64_t write_timeout); + +/* + * Timer related functions + */ + +typedef struct isc_nm_timer isc_nm_timer_t; + +typedef void (*isc_nm_timer_cb)(void *, isc_result_t); + +void +isc_nm_timer_create(isc_nmhandle_t *, isc_nm_timer_cb, void *, + isc_nm_timer_t **); + +void +isc_nm_timer_attach(isc_nm_timer_t *, isc_nm_timer_t **); + +void +isc_nm_timer_detach(isc_nm_timer_t **); + +void +isc_nm_timer_start(isc_nm_timer_t *, uint64_t); + +void +isc_nm_timer_stop(isc_nm_timer_t *); diff --git a/lib/isc/include/isc/netscope.h b/lib/isc/include/isc/netscope.h new file mode 100644 index 0000000..0622548 --- /dev/null +++ b/lib/isc/include/isc/netscope.h @@ -0,0 +1,39 @@ +/* + * 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. + */ + +#ifndef ISC_NETSCOPE_H +#define ISC_NETSCOPE_H 1 + +/*! \file isc/netscope.h */ + +#include <inttypes.h> + +ISC_LANG_BEGINDECLS + +/*% + * Convert a string of an IPv6 scope zone to zone index. If the conversion + * succeeds, 'zoneid' will store the index value. + * + * XXXJT: when a standard interface for this purpose is defined, + * we should use it. + * + * Returns: + * \li ISC_R_SUCCESS: conversion succeeds + * \li ISC_R_FAILURE: conversion fails + */ +isc_result_t +isc_netscope_pton(int af, char *scopename, void *addr, uint32_t *zoneid); + +ISC_LANG_ENDDECLS + +#endif /* ISC_NETSCOPE_H */ diff --git a/lib/isc/include/isc/nonce.h b/lib/isc/include/isc/nonce.h new file mode 100644 index 0000000..b593e41 --- /dev/null +++ b/lib/isc/include/isc/nonce.h @@ -0,0 +1,33 @@ +/* + * 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. + */ + +#pragma once + +#include <stdlib.h> + +#include <isc/lang.h> + +/*! \file isc/nonce.h + * \brief Provides a function for generating an arbitrarily long nonce. + */ + +ISC_LANG_BEGINDECLS + +void +isc_nonce_buf(void *buf, size_t buflen); +/*!< + * Fill 'buf', up to 'buflen' bytes, with random data from the + * crypto provider's random function. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/isc/include/isc/os.h b/lib/isc/include/isc/os.h new file mode 100644 index 0000000..585abc0 --- /dev/null +++ b/lib/isc/include/isc/os.h @@ -0,0 +1,32 @@ +/* + * 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. + */ + +#ifndef ISC_OS_H +#define ISC_OS_H 1 + +/*! \file isc/os.h */ + +#include <isc/lang.h> + +ISC_LANG_BEGINDECLS + +unsigned int +isc_os_ncpus(void); +/*%< + * Return the number of CPUs available on the system, or 1 if this cannot + * be determined. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_OS_H */ diff --git a/lib/isc/include/isc/parseint.h b/lib/isc/include/isc/parseint.h new file mode 100644 index 0000000..d41c57e --- /dev/null +++ b/lib/isc/include/isc/parseint.h @@ -0,0 +1,60 @@ +/* + * 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. + */ + +#ifndef ISC_PARSEINT_H +#define ISC_PARSEINT_H 1 + +#include <inttypes.h> + +#include <isc/lang.h> +#include <isc/types.h> + +/*! \file isc/parseint.h + * \brief Parse integers, in a saner way than atoi() or strtoul() do. + */ + +/*** + *** Functions + ***/ + +ISC_LANG_BEGINDECLS + +isc_result_t +isc_parse_uint32(uint32_t *uip, const char *string, int base); + +isc_result_t +isc_parse_uint16(uint16_t *uip, const char *string, int base); + +isc_result_t +isc_parse_uint8(uint8_t *uip, const char *string, int base); +/*%< + * Parse the null-terminated string 'string' containing a base 'base' + * integer, storing the result in '*uip'. + * The base is interpreted + * as in strtoul(). Unlike strtoul(), leading whitespace, minus or + * plus signs are not accepted, and all errors (including overflow) + * are reported uniformly through the return value. + * + * Requires: + *\li 'string' points to a null-terminated string + *\li 0 <= 'base' <= 36 + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_BADNUMBER The string is not numeric (in the given base) + *\li #ISC_R_RANGE The number is not representable as the requested type. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_PARSEINT_H */ diff --git a/lib/isc/include/isc/platform.h.in b/lib/isc/include/isc/platform.h.in new file mode 100644 index 0000000..85b468c --- /dev/null +++ b/lib/isc/include/isc/platform.h.in @@ -0,0 +1,100 @@ +/* + * 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. + */ + +#ifndef ISC_PLATFORM_H +#define ISC_PLATFORM_H 1 + +/*! \file */ + +/***** + ***** Platform-dependent defines. + *****/ + +/*** + *** Default strerror_r buffer size + ***/ + +#define ISC_STRERRORSIZE 128 + +/*** + *** System limitations + ***/ + +#include <limits.h> + +#ifndef NAME_MAX +#define NAME_MAX 256 +#endif + +#ifndef PATH_MAX +#define PATH_MAX 1024 +#endif + +#ifndef IOV_MAX +#define IOV_MAX 1024 +#endif + +/*** + *** Miscellaneous. + ***/ + +/* + * Defined to <gssapi.h> or <gssapi/gssapi.h> for how to include + * the GSSAPI header. + */ +@ISC_PLATFORM_GSSAPIHEADER@ + +/* + * Defined to <gssapi_krb5.h> or <gssapi/gssapi_krb5.h> for how to + * include the GSSAPI KRB5 header. + */ +@ISC_PLATFORM_GSSAPI_KRB5_HEADER@ + +/* + * Defined to <krb5.h> or <krb5/krb5.h> for how to include + * the KRB5 header. + */ +@ISC_PLATFORM_KRB5HEADER@ + +/* + * Define if the platform has <sys/un.h>. + */ +@ISC_PLATFORM_HAVESYSUNH@ + +/* + * Defines for the noreturn attribute. + */ +@ISC_PLATFORM_NORETURN_PRE@ +@ISC_PLATFORM_NORETURN_POST@ + +/*** + *** Windows dll support. + ***/ + +#define LIBISC_EXTERNAL_DATA +#define LIBDNS_EXTERNAL_DATA +#define LIBISCCC_EXTERNAL_DATA +#define LIBISCCFG_EXTERNAL_DATA +#define LIBNS_EXTERNAL_DATA +#define LIBBIND9_EXTERNAL_DATA +#define LIBTESTS_EXTERNAL_DATA + +/* + * Tell emacs to use C mode for this file. + * + * Local Variables: + * mode: c + * End: + */ + +#endif /* ISC_PLATFORM_H */ diff --git a/lib/isc/include/isc/pool.h b/lib/isc/include/isc/pool.h new file mode 100644 index 0000000..b354c18 --- /dev/null +++ b/lib/isc/include/isc/pool.h @@ -0,0 +1,141 @@ +/* + * 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. + */ + +#ifndef ISC_OBJPOOL_H +#define ISC_OBJPOOL_H 1 + +/***** +***** Module Info +*****/ + +/*! \file isc/pool.h + * \brief An object pool is a mechanism for sharing a small pool of + * fungible objects among a large number of objects that depend on them. + * + * This is useful, for example, when it causes performance problems for + * large number of zones to share a single memory context or task object, + * but it would create a different set of problems for them each to have an + * independent task or memory context. + */ + +/*** + *** Imports. + ***/ + +#include <isc/lang.h> +#include <isc/mem.h> +#include <isc/types.h> + +ISC_LANG_BEGINDECLS + +/***** +***** Types. +*****/ + +typedef void (*isc_pooldeallocator_t)(void **object); + +typedef isc_result_t (*isc_poolinitializer_t)(void **target, void *arg); + +typedef struct isc_pool isc_pool_t; + +/***** +***** Functions. +*****/ + +isc_result_t +isc_pool_create(isc_mem_t *mctx, unsigned int count, isc_pooldeallocator_t free, + isc_poolinitializer_t init, void *initarg, isc_pool_t **poolp); +/*%< + * Create a pool of "count" object pointers. If 'free' is not NULL, + * it points to a function that will detach the objects. 'init' + * points to a function that will initialize the arguments, and + * 'arg' to an argument to be passed into that function (for example, + * a relevant manager or context object). + * + * Requires: + * + *\li 'mctx' is a valid memory context. + * + *\li init != NULL + * + *\li poolp != NULL && *poolp == NULL + * + * Ensures: + * + *\li On success, '*poolp' points to the new object pool. + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + *\li #ISC_R_UNEXPECTED + */ + +void * +isc_pool_get(isc_pool_t *pool); +/*%< + * Returns a pointer to an object from the pool. Currently the object + * is chosen from the pool at random. (This may be changed in the future + * to something that guaratees balance.) + */ + +int +isc_pool_count(isc_pool_t *pool); +/*%< + * Returns the number of objcts in the pool 'pool'. + */ + +isc_result_t +isc_pool_expand(isc_pool_t **sourcep, unsigned int count, isc_pool_t **targetp); + +/*%< + * If 'size' is larger than the number of objects in the pool pointed to by + * 'sourcep', then a new pool of size 'count' is allocated, the existing + * objects are copied into it, additional ones created to bring the + * total number up to 'count', and the resulting pool is attached to + * 'targetp'. + * + * If 'count' is less than or equal to the number of objects in 'source', then + * 'sourcep' is attached to 'targetp' without any other action being taken. + * + * In either case, 'sourcep' is detached. + * + * Requires: + * + * \li 'sourcep' is not NULL and '*source' is not NULL + * \li 'targetp' is not NULL and '*source' is NULL + * + * Ensures: + * + * \li On success, '*targetp' points to a valid task pool. + * \li On success, '*sourcep' points to NULL. + * + * Returns: + * + * \li #ISC_R_SUCCESS + * \li #ISC_R_NOMEMORY + */ + +void +isc_pool_destroy(isc_pool_t **poolp); +/*%< + * Destroy a task pool. The tasks in the pool are detached but not + * shut down. + * + * Requires: + * \li '*poolp' is a valid task pool. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_OBJPOOL_H */ diff --git a/lib/isc/include/isc/portset.h b/lib/isc/include/isc/portset.h new file mode 100644 index 0000000..fbdde82 --- /dev/null +++ b/lib/isc/include/isc/portset.h @@ -0,0 +1,138 @@ +/* + * 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. + */ + +/*! \file isc/portset.h + * \brief Transport Protocol Port Manipulation Module + * + * This module provides simple utilities to handle a set of transport protocol + * (UDP or TCP) port numbers, e.g., for creating an ACL list. An isc_portset_t + * object is an opaque instance of a port set, for which the user can add or + * remove a specific port or a range of consecutive ports. This object is + * expected to be used as a temporary work space only, and does not protect + * simultaneous access from multiple threads. Therefore it must not be stored + * in a place that can be accessed from multiple threads. + */ + +#ifndef ISC_PORTSET_H +#define ISC_PORTSET_H 1 + +/*** + *** Imports + ***/ + +#include <stdbool.h> + +#include <isc/net.h> + +/*** + *** Functions + ***/ + +ISC_LANG_BEGINDECLS + +isc_result_t +isc_portset_create(isc_mem_t *mctx, isc_portset_t **portsetp); +/*%< + * Create a port set and initialize it as an empty set. + * + * Requires: + *\li 'mctx' to be valid. + *\li 'portsetp' to be non NULL and '*portsetp' to be NULL; + * + * Returns: + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + */ + +void +isc_portset_destroy(isc_mem_t *mctx, isc_portset_t **portsetp); +/*%< + * Destroy a port set. + * + * Requires: + *\li 'mctx' to be valid and must be the same context given when the port set + * was created. + *\li '*portsetp' to be a valid set. + */ + +bool +isc_portset_isset(isc_portset_t *portset, in_port_t port); +/*%< + * Test whether the given port is stored in the portset. + * + * Requires: + *\li 'portset' to be a valid set. + * + * Returns + * \li #true if the port is found, false otherwise. + */ + +unsigned int +isc_portset_nports(isc_portset_t *portset); +/*%< + * Provides the number of ports stored in the given portset. + * + * Requires: + *\li 'portset' to be a valid set. + * + * Returns + * \li the number of ports stored in portset. + */ + +void +isc_portset_add(isc_portset_t *portset, in_port_t port); +/*%< + * Add the given port to the portset. The port may or may not be stored in + * the portset. + * + * Requires: + *\li 'portlist' to be valid. + */ + +void +isc_portset_remove(isc_portset_t *portset, in_port_t port); +/*%< + * Remove the given port to the portset. The port may or may not be stored in + * the portset. + * + * Requires: + *\li 'portlist' to be valid. + */ + +void +isc_portset_addrange(isc_portset_t *portset, in_port_t port_lo, + in_port_t port_hi); +/*%< + * Add a subset of [port_lo, port_hi] (inclusive) to the portset. Ports in the + * subset may or may not be stored in portset. + * + * Requires: + *\li 'portlist' to be valid. + *\li port_lo <= port_hi + */ + +void +isc_portset_removerange(isc_portset_t *portset, in_port_t port_lo, + in_port_t port_hi); +/*%< + * Subtract a subset of [port_lo, port_hi] (inclusive) from the portset. Ports + * in the subset may or may not be stored in portset. + * + * Requires: + *\li 'portlist' to be valid. + *\li port_lo <= port_hi + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_PORTSET_H */ diff --git a/lib/isc/include/isc/print.h b/lib/isc/include/isc/print.h new file mode 100644 index 0000000..b3ae7fd --- /dev/null +++ b/lib/isc/include/isc/print.h @@ -0,0 +1,33 @@ +/* + * 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. + */ + +#ifndef ISC_PRINT_H +#define ISC_PRINT_H 1 + +/*! \file isc/print.h */ + +/*** + *** Imports + ***/ + +#include <isc/formatcheck.h> /* Required for ISC_FORMAT_PRINTF() macro. */ +#include <isc/lang.h> +#include <isc/platform.h> + +/*** + *** Functions + ***/ + +#include <stdio.h> + +#endif /* ISC_PRINT_H */ diff --git a/lib/isc/include/isc/quota.h b/lib/isc/include/isc/quota.h new file mode 100644 index 0000000..b9f9a24 --- /dev/null +++ b/lib/isc/include/isc/quota.h @@ -0,0 +1,156 @@ +/* + * 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. + */ + +#ifndef ISC_QUOTA_H +#define ISC_QUOTA_H 1 + +/***** +***** Module Info +*****/ + +/*! \file isc/quota.h + * + * \brief The isc_quota_t object is a simple helper object for implementing + * quotas on things like the number of simultaneous connections to + * a server. It keeps track of the amount of quota in use, and + * encapsulates the locking necessary to allow multiple tasks to + * share a quota. + */ + +/*** + *** Imports. + ***/ + +#include <isc/atomic.h> +#include <isc/lang.h> +#include <isc/magic.h> +#include <isc/mutex.h> +#include <isc/types.h> + +/***** +***** Types. +*****/ + +ISC_LANG_BEGINDECLS + +/*% isc_quota_cb - quota callback structure */ +typedef struct isc_quota_cb isc_quota_cb_t; +typedef void (*isc_quota_cb_func_t)(isc_quota_t *quota, void *data); +struct isc_quota_cb { + int magic; + isc_quota_cb_func_t cb_func; + void *data; + ISC_LINK(isc_quota_cb_t) link; +}; + +/*% isc_quota structure */ +struct isc_quota { + int magic; + atomic_uint_fast32_t max; + atomic_uint_fast32_t used; + atomic_uint_fast32_t soft; + atomic_uint_fast32_t waiting; + isc_mutex_t cblock; + ISC_LIST(isc_quota_cb_t) cbs; +}; + +void +isc_quota_init(isc_quota_t *quota, unsigned int max); +/*%< + * Initialize a quota object. + */ + +void +isc_quota_destroy(isc_quota_t *quota); +/*%< + * Destroy a quota object. + */ + +void +isc_quota_soft(isc_quota_t *quota, unsigned int soft); +/*%< + * Set a soft quota. + */ + +void +isc_quota_max(isc_quota_t *quota, unsigned int max); +/*%< + * Re-set a maximum quota. + */ + +unsigned int +isc_quota_getmax(isc_quota_t *quota); +/*%< + * Get the maximum quota. + */ + +unsigned int +isc_quota_getsoft(isc_quota_t *quota); +/*%< + * Get the soft quota. + */ + +unsigned int +isc_quota_getused(isc_quota_t *quota); +/*%< + * Get the current usage of quota. + */ + +isc_result_t +isc_quota_attach(isc_quota_t *quota, isc_quota_t **p); +/*%< + * + * Attempt to reserve one unit of 'quota', and also attaches '*p' to the quota + * if successful (ISC_R_SUCCESS or ISC_R_SOFTQUOTA). + * + * Returns: + * \li #ISC_R_SUCCESS Success + * \li #ISC_R_SOFTQUOTA Success soft quota reached + * \li #ISC_R_QUOTA Quota is full + */ + +isc_result_t +isc_quota_attach_cb(isc_quota_t *quota, isc_quota_t **p, isc_quota_cb_t *cb); +/*%< + * + * Like isc_quota_attach(), but if there's no quota left then cb->cb_func will + * be called when we are attached to quota. + * + * Note: It's the caller's responsibility to make sure that we don't end up + * with a huge number of callbacks waiting, making it easy to create a + * resource exhaustion attack. For example, in the case of TCP listening, + * we simply don't accept new connections when the quota is exceeded, so + * the number of callbacks waiting in the queue will be limited by the + * listen() backlog. + * + * Returns: + * \li #ISC_R_SUCCESS Success + * \li #ISC_R_SOFTQUOTA Success soft quota reached + * \li #ISC_R_QUOTA Quota is full + */ + +void +isc_quota_cb_init(isc_quota_cb_t *cb, isc_quota_cb_func_t cb_func, void *data); +/*%< + * Initialize isc_quota_cb_t - setup the list, set the callback and data. + */ + +void +isc_quota_detach(isc_quota_t **p); +/*%< + * Release one unit of quota, and also detaches '*p' from the quota. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_QUOTA_H */ diff --git a/lib/isc/include/isc/radix.h b/lib/isc/include/isc/radix.h new file mode 100644 index 0000000..7c004e9 --- /dev/null +++ b/lib/isc/include/isc/radix.h @@ -0,0 +1,224 @@ +/* + * 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. + */ + +#ifndef _RADIX_H +#define _RADIX_H + +#include <inttypes.h> +#include <string.h> + +#include <isc/magic.h> +#include <isc/mutex.h> +#include <isc/net.h> +#include <isc/refcount.h> +#include <isc/types.h> + +#define NETADDR_TO_PREFIX_T(na, pt, bits) \ + do { \ + const void *p = na; \ + memset(&(pt), 0, sizeof(pt)); \ + if (p != NULL) { \ + (pt).family = (na)->family; \ + (pt).bitlen = (bits); \ + if ((pt).family == AF_INET6) { \ + memmove(&(pt).add.sin6, &(na)->type.in6, \ + ((bits) + 7) / 8); \ + } else \ + memmove(&(pt).add.sin, &(na)->type.in, \ + ((bits) + 7) / 8); \ + } else { \ + (pt).family = AF_UNSPEC; \ + (pt).bitlen = 0; \ + } \ + isc_refcount_init(&(pt).refcount, 0); \ + } while (0) + +typedef struct isc_prefix { + isc_mem_t *mctx; + unsigned int family; /* AF_INET | AF_INET6, or AF_UNSPEC for + * "any" */ + unsigned int bitlen; /* 0 for "any" */ + isc_refcount_t refcount; + union { + struct in_addr sin; + struct in6_addr sin6; + } add; +} isc_prefix_t; + +typedef void (*isc_radix_destroyfunc_t)(void *); +typedef void (*isc_radix_processfunc_t)(isc_prefix_t *, void **); + +#define isc_prefix_tochar(prefix) ((char *)&(prefix)->add.sin) +#define isc_prefix_touchar(prefix) ((u_char *)&(prefix)->add.sin) + +/* + * We need "first match" when we search the radix tree to preserve + * compatibility with the existing ACL implementation. Radix trees + * naturally lend themselves to "best match". In order to get "first match" + * behavior, we keep track of the order in which entries are added to the + * tree--and when a search is made, we find all matching entries, and + * return the one that was added first. + * + * An IPv4 prefix and an IPv6 prefix may share a radix tree node if they + * have the same length and bit pattern (e.g., 127/8 and 7f::/8). To + * disambiguate between them, node_num and data are two-element arrays: + * + * - node_num[0] and data[0] are used for IPv4 client addresses + * - node_num[1] and data[1] are used for IPv6 client addresses + * + * A prefix of 0/0 (aka "any" or "none"), is always stored as IPv4, + * but matches all IPv6 addresses too. + */ + +#define RADIX_V4 0 +#define RADIX_V6 1 +#define RADIX_FAMILIES 2 + +#define ISC_RADIX_FAMILY(p) (((p)->family == AF_INET6) ? RADIX_V6 : RADIX_V4) + +typedef struct isc_radix_node { + isc_mem_t *mctx; + uint32_t bit; /* bit length of the prefix */ + isc_prefix_t *prefix; /* who we are in radix tree */ + struct isc_radix_node *l, *r; /* left and right children */ + struct isc_radix_node *parent; /* may be used */ + void *data[RADIX_FAMILIES]; /* pointers to IPv4 + * and IPV6 data */ + int node_num[RADIX_FAMILIES]; /* which node + * this was in + * the tree, + * or -1 for glue + * nodes */ +} isc_radix_node_t; + +#define RADIX_TREE_MAGIC ISC_MAGIC('R', 'd', 'x', 'T'); +#define RADIX_TREE_VALID(a) ISC_MAGIC_VALID(a, RADIX_TREE_MAGIC); + +typedef struct isc_radix_tree { + unsigned int magic; + isc_mem_t *mctx; + isc_radix_node_t *head; + uint32_t maxbits; /* for IP, 32 bit addresses */ + int num_active_node; /* for debugging purposes */ + int num_added_node; /* total number of nodes */ +} isc_radix_tree_t; + +isc_result_t +isc_radix_search(isc_radix_tree_t *radix, isc_radix_node_t **target, + isc_prefix_t *prefix); +/*%< + * Search 'radix' for the best match to 'prefix'. + * Return the node found in '*target'. + * + * Requires: + * \li 'radix' to be valid. + * \li 'target' is not NULL and "*target" is NULL. + * \li 'prefix' to be valid. + * + * Returns: + * \li ISC_R_NOTFOUND + * \li ISC_R_SUCCESS + */ + +isc_result_t +isc_radix_insert(isc_radix_tree_t *radix, isc_radix_node_t **target, + isc_radix_node_t *source, isc_prefix_t *prefix); +/*%< + * Insert 'source' or 'prefix' into the radix tree 'radix'. + * Return the node added in 'target'. + * + * Requires: + * \li 'radix' to be valid. + * \li 'target' is not NULL and "*target" is NULL. + * \li 'prefix' to be valid or 'source' to be non NULL and contain + * a valid prefix. + * + * Returns: + * \li ISC_R_NOMEMORY + * \li ISC_R_SUCCESS + */ + +void +isc_radix_remove(isc_radix_tree_t *radix, isc_radix_node_t *node); +/*%< + * Remove the node 'node' from the radix tree 'radix'. + * + * Requires: + * \li 'radix' to be valid. + * \li 'node' to be valid. + */ + +isc_result_t +isc_radix_create(isc_mem_t *mctx, isc_radix_tree_t **target, int maxbits); +/*%< + * Create a radix tree with a maximum depth of 'maxbits'; + * + * Requires: + * \li 'mctx' to be valid. + * \li 'target' to be non NULL and '*target' to be NULL. + * \li 'maxbits' to be less than or equal to RADIX_MAXBITS. + * + * Returns: + * \li ISC_R_NOMEMORY + * \li ISC_R_SUCCESS + */ + +void +isc_radix_destroy(isc_radix_tree_t *radix, isc_radix_destroyfunc_t func); +/*%< + * Destroy a radix tree optionally calling 'func' to clean up node data. + * + * Requires: + * \li 'radix' to be valid. + */ + +void +isc_radix_process(isc_radix_tree_t *radix, isc_radix_processfunc_t func); +/*%< + * Walk a radix tree calling 'func' to process node data. + * + * Requires: + * \li 'radix' to be valid. + * \li 'func' to point to a function. + */ + +#define RADIX_MAXBITS 128 +#define RADIX_NBIT(x) (0x80 >> ((x)&0x7f)) +#define RADIX_NBYTE(x) ((x) >> 3) + +#define RADIX_WALK(Xhead, Xnode) \ + do { \ + isc_radix_node_t *Xstack[RADIX_MAXBITS + 1]; \ + isc_radix_node_t **Xsp = Xstack; \ + isc_radix_node_t *Xrn = (Xhead); \ + while ((Xnode = Xrn)) { \ + if (Xnode->prefix) + +#define RADIX_WALK_END \ + if (Xrn->l) { \ + if (Xrn->r) { \ + *Xsp++ = Xrn->r; \ + } \ + Xrn = Xrn->l; \ + } else if (Xrn->r) { \ + Xrn = Xrn->r; \ + } else if (Xsp != Xstack) { \ + Xrn = *(--Xsp); \ + } else { \ + Xrn = (isc_radix_node_t *)0; \ + } \ + } \ + } \ + while (0) + +#endif /* _RADIX_H */ diff --git a/lib/isc/include/isc/random.h b/lib/isc/include/isc/random.h new file mode 100644 index 0000000..1e30d0c --- /dev/null +++ b/lib/isc/include/isc/random.h @@ -0,0 +1,65 @@ +/* + * 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. + */ + +#pragma once + +#include <inttypes.h> +#include <stdlib.h> + +#include <isc/lang.h> +#include <isc/types.h> + +/*! \file isc/random.h + * \brief Implements wrapper around a non-cryptographically secure + * pseudo-random number generator. + * + */ + +ISC_LANG_BEGINDECLS + +uint8_t +isc_random8(void); +/*!< + * \brief Returns a single 8-bit random value. + */ + +uint16_t +isc_random16(void); +/*!< + * \brief Returns a single 16-bit random value. + */ + +uint32_t +isc_random32(void); +/*!< + * \brief Returns a single 32-bit random value. + */ + +void +isc_random_buf(void *buf, size_t buflen); +/*!< + * \brief Fills the region buf of length buflen with random data. + */ + +uint32_t +isc_random_uniform(uint32_t upper_bound); +/*!< + * \brief Will return a single 32-bit value, uniformly distributed but + * less than upper_bound. This is recommended over + * constructions like ``isc_random() % upper_bound'' as it + * avoids "modulo bias" when the upper bound is not a power of + * two. In the worst case, this function may require multiple + * iterations to ensure uniformity. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/isc/include/isc/ratelimiter.h b/lib/isc/include/isc/ratelimiter.h new file mode 100644 index 0000000..08862dc --- /dev/null +++ b/lib/isc/include/isc/ratelimiter.h @@ -0,0 +1,148 @@ +/* + * 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. + */ + +#ifndef ISC_RATELIMITER_H +#define ISC_RATELIMITER_H 1 + +/***** +***** Module Info +*****/ + +/*! \file isc/ratelimiter.h + * \brief A rate limiter is a mechanism for dispatching events at a limited + * rate. This is intended to be used when sending zone maintenance + * SOA queries, NOTIFY messages, etc. + */ + +/*** + *** Imports. + ***/ + +#include <inttypes.h> +#include <stdbool.h> + +#include <isc/lang.h> +#include <isc/types.h> + +ISC_LANG_BEGINDECLS + +/***** +***** Functions. +*****/ + +isc_result_t +isc_ratelimiter_create(isc_mem_t *mctx, isc_timermgr_t *timermgr, + isc_task_t *task, isc_ratelimiter_t **ratelimiterp); +/*%< + * Create a rate limiter. The execution interval is initially undefined. + */ + +isc_result_t +isc_ratelimiter_setinterval(isc_ratelimiter_t *rl, isc_interval_t *interval); +/*!< + * Set the minimum interval between event executions. + * The interval value is copied, so the caller need not preserve it. + * + * Requires: + * '*interval' is a nonzero interval. + */ + +void +isc_ratelimiter_setpertic(isc_ratelimiter_t *rl, uint32_t perint); +/*%< + * Set the number of events processed per interval timer tick. + * If 'perint' is zero it is treated as 1. + */ + +void +isc_ratelimiter_setpushpop(isc_ratelimiter_t *rl, bool pushpop); +/*%< + * Set / clear the ratelimiter to from push pop mode rather + * first in - first out mode (default). + */ + +isc_result_t +isc_ratelimiter_enqueue(isc_ratelimiter_t *rl, isc_task_t *task, + isc_event_t **eventp); +/*%< + * Queue an event for rate-limited execution. + * + * This is similar + * to doing an isc_task_send() to the 'task', except that the + * execution may be delayed to achieve the desired rate of + * execution. + * + * '(*eventp)->ev_sender' is used to hold the task. The caller + * must ensure that the task exists until the event is delivered. + * + * Requires: + *\li An interval has been set by calling + * isc_ratelimiter_setinterval(). + * + *\li 'task' to be non NULL. + *\li '(*eventp)->ev_sender' to be NULL. + */ + +isc_result_t +isc_ratelimiter_dequeue(isc_ratelimiter_t *rl, isc_event_t *event); +/* + * Dequeue a event off the ratelimiter queue. + * + * Returns: + * \li ISC_R_NOTFOUND if the event is no longer linked to the rate limiter. + * \li ISC_R_SUCCESS + */ + +void +isc_ratelimiter_shutdown(isc_ratelimiter_t *ratelimiter); +/*%< + * Shut down a rate limiter. + * + * Ensures: + *\li All events that have not yet been + * dispatched to the task are dispatched immediately with + * the #ISC_EVENTATTR_CANCELED bit set in ev_attributes. + * + *\li Further attempts to enqueue events will fail with + * #ISC_R_SHUTTINGDOWN. + * + *\li The rate limiter is no longer attached to its task. + */ + +void +isc_ratelimiter_attach(isc_ratelimiter_t *source, isc_ratelimiter_t **target); +/*%< + * Attach to a rate limiter. + */ + +void +isc_ratelimiter_detach(isc_ratelimiter_t **ratelimiterp); +/*%< + * Detach from a rate limiter. + */ + +isc_result_t +isc_ratelimiter_stall(isc_ratelimiter_t *rl); +/*%< + * Stall event processing. + */ + +isc_result_t +isc_ratelimiter_release(isc_ratelimiter_t *rl); +/*%< + * Release a stalled rate limiter. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_RATELIMITER_H */ diff --git a/lib/isc/include/isc/refcount.h b/lib/isc/include/isc/refcount.h new file mode 100644 index 0000000..68e512c --- /dev/null +++ b/lib/isc/include/isc/refcount.h @@ -0,0 +1,156 @@ +/* + * 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. + */ + +#pragma once + +#include <inttypes.h> + +#include <isc/assertions.h> +#include <isc/atomic.h> +#include <isc/error.h> +#include <isc/lang.h> +#include <isc/mutex.h> +#include <isc/platform.h> +#include <isc/types.h> + +/*! \file isc/refcount.h + * \brief Implements a locked reference counter. + * + * These macros uses C11(-like) atomic functions to implement reference + * counting. The isc_refcount_t type must not be accessed directly. + */ + +ISC_LANG_BEGINDECLS + +typedef atomic_uint_fast32_t isc_refcount_t; + +/** \def isc_refcount_init(ref, n) + * \brief Initialize the reference counter. + * \param[in] ref pointer to reference counter. + * \param[in] n an initial number of references. + * \return nothing. + * + * \warning No memory barrier are being imposed here. + */ +#define isc_refcount_init(target, value) atomic_init(target, value) + +/** \def isc_refcount_current(ref) + * \brief Returns current number of references. + * \param[in] ref pointer to reference counter. + * \returns current value of reference counter. + * + * Undo implicit promotion to 64 bits in our Windows implementation of + * atomic_load_explicit() by casting to uint_fast32_t. + */ + +#define isc_refcount_current(target) (uint_fast32_t) atomic_load_acquire(target) + +/** \def isc_refcount_destroy(ref) + * \brief a destructor that makes sure that all references were cleared. + * \param[in] ref pointer to reference counter. + * \returns nothing. + */ +#define isc_refcount_destroy(target) \ + ISC_REQUIRE(isc_refcount_current(target) == 0) + +/** \def isc_refcount_increment0(ref) + * \brief increases reference counter by 1. + * \param[in] ref pointer to reference counter. + * \returns previous value of reference counter. + */ +#if _MSC_VER +static inline uint_fast32_t +isc_refcount_increment0(isc_refcount_t *target) { + uint_fast32_t __v; + __v = (uint_fast32_t)atomic_fetch_add_relaxed(target, 1); + INSIST(__v < UINT32_MAX); + return (__v); +} +#else /* _MSC_VER */ +#define isc_refcount_increment0(target) \ + ({ \ + /* cppcheck-suppress shadowVariable */ \ + uint_fast32_t __v; \ + __v = atomic_fetch_add_relaxed(target, 1); \ + INSIST(__v < UINT32_MAX); \ + __v; \ + }) +#endif /* _MSC_VER */ + +/** \def isc_refcount_increment(ref) + * \brief increases reference counter by 1. + * \param[in] ref pointer to reference counter. + * \returns previous value of reference counter. + */ +#if _MSC_VER +static inline uint_fast32_t +isc_refcount_increment(isc_refcount_t *target) { + uint_fast32_t __v; + __v = (uint_fast32_t)atomic_fetch_add_relaxed(target, 1); + INSIST(__v > 0 && __v < UINT32_MAX); + return (__v); +} +#else /* _MSC_VER */ +#define isc_refcount_increment(target) \ + ({ \ + /* cppcheck-suppress shadowVariable */ \ + uint_fast32_t __v; \ + __v = atomic_fetch_add_relaxed(target, 1); \ + INSIST(__v > 0 && __v < UINT32_MAX); \ + __v; \ + }) +#endif /* _MSC_VER */ + +/** \def isc_refcount_decrement(ref) + * \brief decreases reference counter by 1. + * \param[in] ref pointer to reference counter. + * \returns previous value of reference counter. + */ +#if _MSC_VER +static inline uint_fast32_t +isc_refcount_decrement(isc_refcount_t *target) { + uint_fast32_t __v; + __v = (uint_fast32_t)atomic_fetch_sub_acq_rel(target, 1); + INSIST(__v > 0); + return (__v); +} +#else /* _MSC_VER */ +#define isc_refcount_decrement(target) \ + ({ \ + /* cppcheck-suppress shadowVariable */ \ + uint_fast32_t __v; \ + __v = atomic_fetch_sub_acq_rel(target, 1); \ + INSIST(__v > 0); \ + __v; \ + }) +#endif /* _MSC_VER */ + +#define isc_refcount_decrementz(target) \ + do { \ + uint_fast32_t _refs = isc_refcount_decrement(target); \ + ISC_INSIST(_refs == 1); \ + } while (0) + +#define isc_refcount_decrement1(target) \ + do { \ + uint_fast32_t _refs = isc_refcount_decrement(target); \ + ISC_INSIST(_refs > 1); \ + } while (0) + +#define isc_refcount_decrement0(target) \ + do { \ + uint_fast32_t _refs = isc_refcount_decrement(target); \ + ISC_INSIST(_refs > 0); \ + } while (0) + +ISC_LANG_ENDDECLS diff --git a/lib/isc/include/isc/regex.h b/lib/isc/include/isc/regex.h new file mode 100644 index 0000000..f288d86 --- /dev/null +++ b/lib/isc/include/isc/regex.h @@ -0,0 +1,36 @@ +/* + * 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. + */ + +#ifndef ISC_REGEX_H +#define ISC_REGEX_H 1 + +/*! \file isc/regex.h */ + +#include <isc/lang.h> +#include <isc/types.h> + +ISC_LANG_BEGINDECLS + +int +isc_regex_validate(const char *expression); +/*%< + * Check a regular expression for syntactic correctness. + * + * Returns: + *\li -1 on error. + *\li the number of groups in the expression. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_REGEX_H */ diff --git a/lib/isc/include/isc/region.h b/lib/isc/include/isc/region.h new file mode 100644 index 0000000..c386617 --- /dev/null +++ b/lib/isc/include/isc/region.h @@ -0,0 +1,98 @@ +/* + * 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. + */ + +#ifndef ISC_REGION_H +#define ISC_REGION_H 1 + +/*! \file isc/region.h */ + +#include <isc/lang.h> +#include <isc/types.h> + +struct isc_region { + unsigned char *base; + unsigned int length; +}; + +struct isc_textregion { + char *base; + unsigned int length; +}; + +/* XXXDCL questionable ... bears discussion. we have been putting off + * discussing the region api. + */ +struct isc_constregion { + const void *base; + unsigned int length; +}; + +struct isc_consttextregion { + const char *base; + unsigned int length; +}; + +/*@{*/ +/*! + * The region structure is not opaque, and is usually directly manipulated. + * Some macros are defined below for convenience. + */ + +#define isc_region_consume(r, l) \ + do { \ + isc_region_t *_r = (r); \ + unsigned int _l = (l); \ + INSIST(_r->length >= _l); \ + _r->base += _l; \ + _r->length -= _l; \ + } while (0) + +#define isc_textregion_consume(r, l) \ + do { \ + isc_textregion_t *_r = (r); \ + unsigned int _l = (l); \ + INSIST(_r->length >= _l); \ + _r->base += _l; \ + _r->length -= _l; \ + } while (0) + +#define isc_constregion_consume(r, l) \ + do { \ + isc_constregion_t *_r = (r); \ + unsigned int _l = (l); \ + INSIST(_r->length >= _l); \ + _r->base += _l; \ + _r->length -= _l; \ + } while (0) +/*@}*/ + +ISC_LANG_BEGINDECLS + +int +isc_region_compare(isc_region_t *r1, isc_region_t *r2); +/*%< + * Compares the contents of two regions + * + * Requires: + *\li 'r1' is a valid region + *\li 'r2' is a valid region + * + * Returns: + *\li < 0 if r1 is lexicographically less than r2 + *\li = 0 if r1 is lexicographically identical to r2 + *\li > 0 if r1 is lexicographically greater than r2 + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_REGION_H */ diff --git a/lib/isc/include/isc/resource.h b/lib/isc/include/isc/resource.h new file mode 100644 index 0000000..f9e6e20 --- /dev/null +++ b/lib/isc/include/isc/resource.h @@ -0,0 +1,90 @@ +/* + * 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. + */ + +#ifndef ISC_RESOURCE_H +#define ISC_RESOURCE_H 1 + +/*! \file isc/resource.h */ + +#include <isc/lang.h> +#include <isc/types.h> + +#define ISC_RESOURCE_UNLIMITED ((isc_resourcevalue_t)UINT64_MAX) + +ISC_LANG_BEGINDECLS + +isc_result_t +isc_resource_setlimit(isc_resource_t resource, isc_resourcevalue_t value); +/*%< + * Set the maximum limit for a system resource. + * + * Notes: + *\li If 'value' exceeds the maximum possible on the operating system, + * it is silently limited to that maximum -- or to "infinity", if + * the operating system has that concept. #ISC_RESOURCE_UNLIMITED + * can be used to explicitly ask for the maximum. + * + * Requires: + *\li 'resource' is a valid member of the isc_resource_t enumeration. + * + * Returns: + *\li #ISC_R_SUCCESS Success. + *\li #ISC_R_NOTIMPLEMENTED 'resource' is not a type known by the OS. + *\li #ISC_R_NOPERM The calling process did not have adequate permission + * to change the resource limit. + */ + +isc_result_t +isc_resource_getlimit(isc_resource_t resource, isc_resourcevalue_t *value); +/*%< + * Get the maximum limit for a system resource. + * + * Notes: + *\li 'value' is set to the maximum limit. + * + *\li #ISC_RESOURCE_UNLIMITED is the maximum value of isc_resourcevalue_t. + * + *\li On many (all?) Unix systems, RLIM_INFINITY is a valid value that is + * significantly less than #ISC_RESOURCE_UNLIMITED, but which in practice + * behaves the same. + * + *\li The current ISC libdns configuration file parser assigns a value + * of UINT32_MAX for a size_spec of "unlimited" and ISC_UNIT32_MAX - 1 + * for "default", the latter of which is supposed to represent "the + * limit that was in force when the server started". Since these are + * valid values in the middle of the range of isc_resourcevalue_t, + * there is the possibility for confusion over what exactly those + * particular values are supposed to represent in a particular context -- + * discrete integral values or generalized concepts. + * + * Requires: + *\li 'resource' is a valid member of the isc_resource_t enumeration. + * + * Returns: + *\li #ISC_R_SUCCESS Success. + *\li #ISC_R_NOTIMPLEMENTED 'resource' is not a type known by the OS. + */ + +isc_result_t +isc_resource_getcurlimit(isc_resource_t resource, isc_resourcevalue_t *value); +/*%< + * Same as isc_resource_getlimit(), but returns the current (soft) limit. + * + * Returns: + *\li #ISC_R_SUCCESS Success. + *\li #ISC_R_NOTIMPLEMENTED 'resource' is not a type known by the OS. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_RESOURCE_H */ diff --git a/lib/isc/include/isc/result.h b/lib/isc/include/isc/result.h new file mode 100644 index 0000000..11ff7f6 --- /dev/null +++ b/lib/isc/include/isc/result.h @@ -0,0 +1,122 @@ +/* + * 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. + */ + +#ifndef ISC_RESULT_H +#define ISC_RESULT_H 1 + +/*! \file isc/result.h */ + +#include <isc/lang.h> +#include <isc/types.h> + +#define ISC_R_SUCCESS 0 /*%< success */ +#define ISC_R_NOMEMORY 1 /*%< out of memory */ +#define ISC_R_TIMEDOUT 2 /*%< timed out */ +#define ISC_R_NOTHREADS 3 /*%< no available threads */ +#define ISC_R_ADDRNOTAVAIL 4 /*%< address not available */ +#define ISC_R_ADDRINUSE 5 /*%< address in use */ +#define ISC_R_NOPERM 6 /*%< permission denied */ +#define ISC_R_NOCONN 7 /*%< no pending connections */ +#define ISC_R_NETUNREACH 8 /*%< network unreachable */ +#define ISC_R_HOSTUNREACH 9 /*%< host unreachable */ +#define ISC_R_NETDOWN 10 /*%< network down */ +#define ISC_R_HOSTDOWN 11 /*%< host down */ +#define ISC_R_CONNREFUSED 12 /*%< connection refused */ +#define ISC_R_NORESOURCES 13 /*%< not enough free resources */ +#define ISC_R_EOF 14 /*%< end of file */ +#define ISC_R_BOUND 15 /*%< socket already bound */ +#define ISC_R_RELOAD 16 /*%< reload */ +#define ISC_R_SUSPEND ISC_R_RELOAD /*%< alias of 'reload' */ +#define ISC_R_LOCKBUSY 17 /*%< lock busy */ +#define ISC_R_EXISTS 18 /*%< already exists */ +#define ISC_R_NOSPACE 19 /*%< ran out of space */ +#define ISC_R_CANCELED 20 /*%< operation canceled */ +#define ISC_R_NOTBOUND 21 /*%< socket is not bound */ +#define ISC_R_SHUTTINGDOWN 22 /*%< shutting down */ +#define ISC_R_NOTFOUND 23 /*%< not found */ +#define ISC_R_UNEXPECTEDEND 24 /*%< unexpected end of input */ +#define ISC_R_FAILURE 25 /*%< generic failure */ +#define ISC_R_IOERROR 26 /*%< I/O error */ +#define ISC_R_NOTIMPLEMENTED 27 /*%< not implemented */ +#define ISC_R_UNBALANCED 28 /*%< unbalanced parentheses */ +#define ISC_R_NOMORE 29 /*%< no more */ +#define ISC_R_INVALIDFILE 30 /*%< invalid file */ +#define ISC_R_BADBASE64 31 /*%< bad base64 encoding */ +#define ISC_R_UNEXPECTEDTOKEN 32 /*%< unexpected token */ +#define ISC_R_QUOTA 33 /*%< quota reached */ +#define ISC_R_UNEXPECTED 34 /*%< unexpected error */ +#define ISC_R_ALREADYRUNNING 35 /*%< already running */ +#define ISC_R_IGNORE 36 /*%< ignore */ +#define ISC_R_MASKNONCONTIG 37 /*%< addr mask not contiguous */ +#define ISC_R_FILENOTFOUND 38 /*%< file not found */ +#define ISC_R_FILEEXISTS 39 /*%< file already exists */ +#define ISC_R_NOTCONNECTED 40 /*%< socket is not connected */ +#define ISC_R_RANGE 41 /*%< out of range */ +#define ISC_R_NOENTROPY 42 /*%< out of entropy */ +#define ISC_R_MULTICAST 43 /*%< invalid use of multicast */ +#define ISC_R_NOTFILE 44 /*%< not a file */ +#define ISC_R_NOTDIRECTORY 45 /*%< not a directory */ +#define ISC_R_EMPTY 46 /*%< queue is empty */ +#define ISC_R_FAMILYMISMATCH 47 /*%< address family mismatch */ +#define ISC_R_FAMILYNOSUPPORT 48 /*%< AF not supported */ +#define ISC_R_BADHEX 49 /*%< bad hex encoding */ +#define ISC_R_TOOMANYOPENFILES 50 /*%< too many open files */ +#define ISC_R_NOTBLOCKING 51 /*%< not blocking */ +#define ISC_R_UNBALANCEDQUOTES 52 /*%< unbalanced quotes */ +#define ISC_R_INPROGRESS 53 /*%< operation in progress */ +#define ISC_R_CONNECTIONRESET 54 /*%< connection reset */ +#define ISC_R_SOFTQUOTA 55 /*%< soft quota reached */ +#define ISC_R_BADNUMBER 56 /*%< not a valid number */ +#define ISC_R_DISABLED 57 /*%< disabled */ +#define ISC_R_MAXSIZE 58 /*%< max size */ +#define ISC_R_BADADDRESSFORM 59 /*%< invalid address format */ +#define ISC_R_BADBASE32 60 /*%< bad base32 encoding */ +#define ISC_R_UNSET 61 /*%< unset */ +#define ISC_R_MULTIPLE 62 /*%< multiple */ +#define ISC_R_WOULDBLOCK 63 /*%< would block */ +#define ISC_R_COMPLETE 64 /*%< complete */ +#define ISC_R_CRYPTOFAILURE 65 /*%< cryptography library failure */ +#define ISC_R_DISCQUOTA 66 /*%< disc quota */ +#define ISC_R_DISCFULL 67 /*%< disc full */ +#define ISC_R_DEFAULT 68 /*%< default */ +#define ISC_R_IPV4PREFIX 69 /*%< IPv4 prefix */ +#define ISC_R_TLSERROR 70 /*%< TLS error */ +#define ISC_R_HTTP2ALPNERROR 71 /*%< ALPN for HTTP/2 failed */ + +/*% Not a result code: the number of results. */ +#define ISC_R_NRESULTS 72 + +ISC_LANG_BEGINDECLS + +const char *isc_result_totext(isc_result_t); +/*%< + * Convert an isc_result_t into a string message describing the result. + */ + +const char *isc_result_toid(isc_result_t); +/*%< + * Convert an isc_result_t into a string identifier such as + * "ISC_R_SUCCESS". + */ + +isc_result_t +isc_result_register(unsigned int base, unsigned int nresults, const char **text, + int set); + +isc_result_t +isc_result_registerids(unsigned int base, unsigned int nresults, + const char **ids, int set); + +ISC_LANG_ENDDECLS + +#endif /* ISC_RESULT_H */ diff --git a/lib/isc/include/isc/resultclass.h b/lib/isc/include/isc/resultclass.h new file mode 100644 index 0000000..a3a5079 --- /dev/null +++ b/lib/isc/include/isc/resultclass.h @@ -0,0 +1,43 @@ +/* + * 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. + */ + +#ifndef ISC_RESULTCLASS_H +#define ISC_RESULTCLASS_H 1 + +/*! \file isc/resultclass.h + * \brief Registry of Predefined Result Type Classes + * + * A result class number is an unsigned 16 bit number. Each class may + * contain up to 65536 results. A result code is formed by adding the + * result number within the class to the class number multiplied by 65536. + * + * Classes < 1024 are reserved for ISC use. + * Result classes >= 1024 and <= 65535 are reserved for application use. + */ + +#define ISC_RESULTCLASS_FROMNUM(num) ((num) << 16) +#define ISC_RESULTCLASS_TONUM(rclass) ((rclass) >> 16) +#define ISC_RESULTCLASS_SIZE 65536 +#define ISC_RESULTCLASS_INCLASS(rclass, result) \ + ((rclass) == ((result)&0xFFFF0000)) + +#define ISC_RESULTCLASS_ISC ISC_RESULTCLASS_FROMNUM(0) +#define ISC_RESULTCLASS_DNS ISC_RESULTCLASS_FROMNUM(1) +#define ISC_RESULTCLASS_DST ISC_RESULTCLASS_FROMNUM(2) +#define ISC_RESULTCLASS_DNSRCODE ISC_RESULTCLASS_FROMNUM(3) +#define ISC_RESULTCLASS_OMAPI ISC_RESULTCLASS_FROMNUM(4) +#define ISC_RESULTCLASS_ISCCC ISC_RESULTCLASS_FROMNUM(5) +#define ISC_RESULTCLASS_DHCP ISC_RESULTCLASS_FROMNUM(6) +#define ISC_RESULTCLASS_PK11 ISC_RESULTCLASS_FROMNUM(7) + +#endif /* ISC_RESULTCLASS_H */ diff --git a/lib/isc/include/isc/rwlock.h b/lib/isc/include/isc/rwlock.h new file mode 100644 index 0000000..3309d71 --- /dev/null +++ b/lib/isc/include/isc/rwlock.h @@ -0,0 +1,107 @@ +/* + * 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. + */ + +#ifndef ISC_RWLOCK_H +#define ISC_RWLOCK_H 1 + +#include <inttypes.h> + +/*! \file isc/rwlock.h */ + +#include <isc/atomic.h> +#include <isc/condition.h> +#include <isc/lang.h> +#include <isc/platform.h> +#include <isc/types.h> + +ISC_LANG_BEGINDECLS + +typedef enum { + isc_rwlocktype_none = 0, + isc_rwlocktype_read, + isc_rwlocktype_write +} isc_rwlocktype_t; + +#if USE_PTHREAD_RWLOCK +#include <pthread.h> + +struct isc_rwlock { + pthread_rwlock_t rwlock; + atomic_bool downgrade; +}; + +#else /* USE_PTHREAD_RWLOCK */ + +struct isc_rwlock { + /* Unlocked. */ + unsigned int magic; + isc_mutex_t lock; + atomic_int_fast32_t spins; + + /* + * When some atomic instructions with hardware assistance are + * available, rwlock will use those so that concurrent readers do not + * interfere with each other through mutex as long as no writers + * appear, massively reducing the lock overhead in the typical case. + * + * The basic algorithm of this approach is the "simple + * writer-preference lock" shown in the following URL: + * http://www.cs.rochester.edu/u/scott/synchronization/pseudocode/rw.html + * but our implementation does not rely on the spin lock unlike the + * original algorithm to be more portable as a user space application. + */ + + /* Read or modified atomically. */ + atomic_int_fast32_t write_requests; + atomic_int_fast32_t write_completions; + atomic_int_fast32_t cnt_and_flag; + + /* Locked by lock. */ + isc_condition_t readable; + isc_condition_t writeable; + unsigned int readers_waiting; + + /* Locked by rwlock itself. */ + atomic_uint_fast32_t write_granted; + + /* Unlocked. */ + unsigned int write_quota; +}; + +#endif /* USE_PTHREAD_RWLOCK */ + +void +isc_rwlock_init(isc_rwlock_t *rwl, unsigned int read_quota, + unsigned int write_quota); + +isc_result_t +isc_rwlock_lock(isc_rwlock_t *rwl, isc_rwlocktype_t type); + +isc_result_t +isc_rwlock_trylock(isc_rwlock_t *rwl, isc_rwlocktype_t type); + +isc_result_t +isc_rwlock_unlock(isc_rwlock_t *rwl, isc_rwlocktype_t type); + +isc_result_t +isc_rwlock_tryupgrade(isc_rwlock_t *rwl); + +void +isc_rwlock_downgrade(isc_rwlock_t *rwl); + +void +isc_rwlock_destroy(isc_rwlock_t *rwl); + +ISC_LANG_ENDDECLS + +#endif /* ISC_RWLOCK_H */ diff --git a/lib/isc/include/isc/safe.h b/lib/isc/include/isc/safe.h new file mode 100644 index 0000000..f5bad62 --- /dev/null +++ b/lib/isc/include/isc/safe.h @@ -0,0 +1,47 @@ +/* + * 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. + */ + +#ifndef ISC_SAFE_H +#define ISC_SAFE_H 1 + +/*! \file isc/safe.h */ + +#include <isc/lang.h> + +ISC_LANG_BEGINDECLS + +int +isc_safe_memequal(const void *, const void *, size_t); + +/*%< + * Returns true iff. two blocks of memory are equal, otherwise + * false. + * + */ + +void +isc_safe_memwipe(void *, size_t); + +/*%< + * Clear the memory of length `len` pointed to by `ptr`. + * + * Some crypto code calls memset() on stack allocated buffers just + * before return so that they are wiped. Such memset() calls can be + * optimized away by the compiler. We provide this external non-inline C + * function to perform the memset operation so that the compiler cannot + * infer about what the function does and optimize the call away. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_SAFE_H */ diff --git a/lib/isc/include/isc/serial.h b/lib/isc/include/isc/serial.h new file mode 100644 index 0000000..e2d5225 --- /dev/null +++ b/lib/isc/include/isc/serial.h @@ -0,0 +1,72 @@ +/* + * 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. + */ + +#ifndef ISC_SERIAL_H +#define ISC_SERIAL_H 1 + +#include <inttypes.h> +#include <stdbool.h> + +#include <isc/lang.h> +#include <isc/types.h> + +/*! \file isc/serial.h + * \brief Implement 32 bit serial space arithmetic comparison functions. + * Note: Undefined results are returned as false. + */ + +/*** + *** Functions + ***/ + +ISC_LANG_BEGINDECLS + +bool +isc_serial_lt(uint32_t a, uint32_t b); +/*%< + * Return true if 'a' < 'b' otherwise false. + */ + +bool +isc_serial_gt(uint32_t a, uint32_t b); +/*%< + * Return true if 'a' > 'b' otherwise false. + */ + +bool +isc_serial_le(uint32_t a, uint32_t b); +/*%< + * Return true if 'a' <= 'b' otherwise false. + */ + +bool +isc_serial_ge(uint32_t a, uint32_t b); +/*%< + * Return true if 'a' >= 'b' otherwise false. + */ + +bool +isc_serial_eq(uint32_t a, uint32_t b); +/*%< + * Return true if 'a' == 'b' otherwise false. + */ + +bool +isc_serial_ne(uint32_t a, uint32_t b); +/*%< + * Return true if 'a' != 'b' otherwise false. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_SERIAL_H */ diff --git a/lib/isc/include/isc/siphash.h b/lib/isc/include/isc/siphash.h new file mode 100644 index 0000000..3974c6f --- /dev/null +++ b/lib/isc/include/isc/siphash.h @@ -0,0 +1,37 @@ +/* + * 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. + */ + +/*! \file isc/siphash.h */ + +#pragma once + +#include <isc/lang.h> +#include <isc/platform.h> +#include <isc/types.h> + +#define ISC_SIPHASH24_KEY_LENGTH 128 / 8 +#define ISC_SIPHASH24_TAG_LENGTH 64 / 8 + +#define ISC_HALFSIPHASH24_KEY_LENGTH 64 / 8 +#define ISC_HALFSIPHASH24_TAG_LENGTH 32 / 8 + +ISC_LANG_BEGINDECLS + +void +isc_siphash24(const uint8_t *key, const uint8_t *in, const size_t inlen, + uint8_t *out); +void +isc_halfsiphash24(const uint8_t *key, const uint8_t *in, const size_t inlen, + uint8_t *out); + +ISC_LANG_ENDDECLS diff --git a/lib/isc/include/isc/sockaddr.h b/lib/isc/include/isc/sockaddr.h new file mode 100644 index 0000000..b776bb3 --- /dev/null +++ b/lib/isc/include/isc/sockaddr.h @@ -0,0 +1,254 @@ +/* + * 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. + */ + +#ifndef ISC_SOCKADDR_H +#define ISC_SOCKADDR_H 1 + +/*! \file isc/sockaddr.h */ + +#include <stdbool.h> + +#include <isc/lang.h> +#include <isc/net.h> +#include <isc/types.h> +#ifdef ISC_PLATFORM_HAVESYSUNH +#include <sys/un.h> +#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */ + +/* + * Any updates to this structure should also be applied in + * contrib/modules/dlz/dlz_minmal.h. + */ +struct isc_sockaddr { + union { + struct sockaddr sa; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + struct sockaddr_storage ss; +#ifdef ISC_PLATFORM_HAVESYSUNH + struct sockaddr_un sunix; +#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */ + } type; + unsigned int length; /* XXXRTH beginning? */ + ISC_LINK(struct isc_sockaddr) link; +}; + +#define ISC_SOCKADDR_CMPADDR \ + 0x0001 /*%< compare the address \ + * sin_addr/sin6_addr */ +#define ISC_SOCKADDR_CMPPORT \ + 0x0002 /*%< compare the port \ + * sin_port/sin6_port */ +#define ISC_SOCKADDR_CMPSCOPE \ + 0x0004 /*%< compare the scope \ + * sin6_scope */ +#define ISC_SOCKADDR_CMPSCOPEZERO \ + 0x0008 /*%< when comparing scopes \ + * zero scopes always match */ + +ISC_LANG_BEGINDECLS + +bool +isc_sockaddr_compare(const isc_sockaddr_t *a, const isc_sockaddr_t *b, + unsigned int flags); +/*%< + * Compare the elements of the two address ('a' and 'b') as specified + * by 'flags' and report if they are equal or not. + * + * 'flags' is set from ISC_SOCKADDR_CMP*. + */ + +bool +isc_sockaddr_equal(const isc_sockaddr_t *a, const isc_sockaddr_t *b); +/*%< + * Return true iff the socket addresses 'a' and 'b' are equal. + */ + +bool +isc_sockaddr_eqaddr(const isc_sockaddr_t *a, const isc_sockaddr_t *b); +/*%< + * Return true iff the address parts of the socket addresses + * 'a' and 'b' are equal, ignoring the ports. + */ + +bool +isc_sockaddr_eqaddrprefix(const isc_sockaddr_t *a, const isc_sockaddr_t *b, + unsigned int prefixlen); +/*%< + * Return true iff the most significant 'prefixlen' bits of the + * socket addresses 'a' and 'b' are equal, ignoring the ports. + * If 'b''s scope is zero then 'a''s scope will be ignored. + */ + +unsigned int +isc_sockaddr_hash(const isc_sockaddr_t *sockaddr, bool address_only); +/*%< + * Return a hash value for the socket address 'sockaddr'. If 'address_only' + * is true, the hash value will not depend on the port. + * + * IPv6 addresses containing mapped IPv4 addresses generate the same hash + * value as the equivalent IPv4 address. + */ + +void +isc_sockaddr_any(isc_sockaddr_t *sockaddr); +/*%< + * Return the IPv4 wildcard address. + */ + +void +isc_sockaddr_any6(isc_sockaddr_t *sockaddr); +/*%< + * Return the IPv6 wildcard address. + */ + +void +isc_sockaddr_anyofpf(isc_sockaddr_t *sockaddr, int family); +/*%< + * Set '*sockaddr' to the wildcard address of protocol family + * 'family'. + * + * Requires: + * \li 'family' is AF_INET or AF_INET6. + */ + +void +isc_sockaddr_fromin(isc_sockaddr_t *sockaddr, const struct in_addr *ina, + in_port_t port); +/*%< + * Construct an isc_sockaddr_t from an IPv4 address and port. + */ + +void +isc_sockaddr_fromin6(isc_sockaddr_t *sockaddr, const struct in6_addr *ina6, + in_port_t port); +/*%< + * Construct an isc_sockaddr_t from an IPv6 address and port. + */ + +void +isc_sockaddr_v6fromin(isc_sockaddr_t *sockaddr, const struct in_addr *ina, + in_port_t port); +/*%< + * Construct an IPv6 isc_sockaddr_t representing a mapped IPv4 address. + */ + +void +isc_sockaddr_fromnetaddr(isc_sockaddr_t *sockaddr, const isc_netaddr_t *na, + in_port_t port); +/*%< + * Construct an isc_sockaddr_t from an isc_netaddr_t and port. + */ + +int +isc_sockaddr_pf(const isc_sockaddr_t *sockaddr); +/*%< + * Get the protocol family of 'sockaddr'. + * + * Requires: + * + *\li 'sockaddr' is a valid sockaddr with an address family of AF_INET + * or AF_INET6. + * + * Returns: + * + *\li The protocol family of 'sockaddr', e.g. PF_INET or PF_INET6. + */ + +void +isc_sockaddr_setport(isc_sockaddr_t *sockaddr, in_port_t port); +/*%< + * Set the port of 'sockaddr' to 'port'. + */ + +in_port_t +isc_sockaddr_getport(const isc_sockaddr_t *sockaddr); +/*%< + * Get the port stored in 'sockaddr'. + */ + +isc_result_t +isc_sockaddr_totext(const isc_sockaddr_t *sockaddr, isc_buffer_t *target); +/*%< + * Append a text representation of 'sockaddr' to the buffer 'target'. + * The text will include both the IP address (v4 or v6) and the port. + * The text is null terminated, but the terminating null is not + * part of the buffer's used region. + * + * Returns: + * \li ISC_R_SUCCESS + * \li ISC_R_NOSPACE The text or the null termination did not fit. + */ + +void +isc_sockaddr_format(const isc_sockaddr_t *sa, char *array, unsigned int size); +/*%< + * Format a human-readable representation of the socket address '*sa' + * into the character array 'array', which is of size 'size'. + * The resulting string is guaranteed to be null-terminated. + */ + +bool +isc_sockaddr_ismulticast(const isc_sockaddr_t *sa); +/*%< + * Returns #true if the address is a multicast address. + */ + +bool +isc_sockaddr_isexperimental(const isc_sockaddr_t *sa); +/* + * Returns true if the address is a experimental (CLASS E) address. + */ + +bool +isc_sockaddr_islinklocal(const isc_sockaddr_t *sa); +/*%< + * Returns true if the address is a link local address. + */ + +bool +isc_sockaddr_issitelocal(const isc_sockaddr_t *sa); +/*%< + * Returns true if the address is a sitelocal address. + */ + +bool +isc_sockaddr_isnetzero(const isc_sockaddr_t *sa); +/*%< + * Returns true if the address is in net zero. + */ + +isc_result_t +isc_sockaddr_frompath(isc_sockaddr_t *sockaddr, const char *path); +/* + * Create a UNIX domain sockaddr that refers to path. + * + * Returns: + * \li ISC_R_NOSPACE + * \li ISC_R_NOTIMPLEMENTED + * \li ISC_R_SUCCESS + */ + +isc_result_t +isc_sockaddr_fromsockaddr(isc_sockaddr_t *isa, const struct sockaddr *sa); + +#define ISC_SOCKADDR_FORMATSIZE \ + sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:XXX.XXX.XXX.XXX%SSSSSSSSSS#" \ + "YYYYY") +/*%< + * Minimum size of array to pass to isc_sockaddr_format(). + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_SOCKADDR_H */ diff --git a/lib/isc/include/isc/socket.h b/lib/isc/include/isc/socket.h new file mode 100644 index 0000000..dc39a3f --- /dev/null +++ b/lib/isc/include/isc/socket.h @@ -0,0 +1,914 @@ +/* + * 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. + */ + +#ifndef ISC_SOCKET_H +#define ISC_SOCKET_H 1 + +/***** +***** Module Info +*****/ + +/*! \file isc/socket.h + * \brief Provides TCP and UDP sockets for network I/O. The sockets are event + * sources in the task system. + * + * When I/O completes, a completion event for the socket is posted to the + * event queue of the task which requested the I/O. + * + * \li MP: + * The module ensures appropriate synchronization of data structures it + * creates and manipulates. + * Clients of this module must not be holding a socket's task's lock when + * making a call that affects that socket. Failure to follow this rule + * can result in deadlock. + * The caller must ensure that isc_socketmgr_destroy() is called only + * once for a given manager. + * + * \li Reliability: + * No anticipated impact. + * + * \li Resources: + * TBS + * + * \li Security: + * No anticipated impact. + * + * \li Standards: + * None. + */ + +/*** + *** Imports + ***/ + +#include <inttypes.h> +#include <stdbool.h> + +#include <isc/event.h> +#include <isc/eventclass.h> +#include <isc/lang.h> +#include <isc/region.h> +#include <isc/sockaddr.h> +#include <isc/time.h> +#include <isc/types.h> + +ISC_LANG_BEGINDECLS + +/*** + *** Constants + ***/ + +/*% + * Maximum number of buffers in a scatter/gather read/write. The operating + * system in use must support at least this number (plus one on some.) + */ +#define ISC_SOCKET_MAXSCATTERGATHER 8 + +/*@{*/ +/*! + * Socket options: + * + * _REUSEADDRESS: Set SO_REUSEADDR prior to calling bind(), + * if a non-zero port is specified (applies to + * AF_INET and AF_INET6). + */ +typedef enum { + ISC_SOCKET_REUSEADDRESS = 0x01U, +} isc_socket_options_t; +/*@}*/ + +/*@{*/ +/*! + * _ATTACHED: Internal use only. + * _TRUNC: Packet was truncated on receive. + * _CTRUNC: Packet control information was truncated. This can + * indicate that the packet is not complete, even though + * all the data is valid. + * _TIMESTAMP: The timestamp member is valid. + * _PKTINFO: The pktinfo member is valid. + * _MULTICAST: The UDP packet was received via a multicast transmission. + * _DSCP: The UDP DSCP value is valid. + * _USEMINMTU: Set the per packet IPV6_USE_MIN_MTU flag. + */ +typedef enum { + ISC_SOCKEVENTATTR_ATTACHED = 0x10000000U, /* internal */ + ISC_SOCKEVENTATTR_TRUNC = 0x00800000U, /* public */ + ISC_SOCKEVENTATTR_CTRUNC = 0x00400000U, /* public */ + ISC_SOCKEVENTATTR_TIMESTAMP = 0x00200000U, /* public */ + ISC_SOCKEVENTATTR_PKTINFO = 0x00100000U, /* public */ + ISC_SOCKEVENTATTR_MULTICAST = 0x00080000U, /* public */ + ISC_SOCKEVENTATTR_DSCP = 0x00040000U, /* public */ + ISC_SOCKEVENTATTR_USEMINMTU = 0x00020000U /* public */ +} isc_sockeventattr_t; +/*@}*/ + +/*** + *** Types + ***/ + +struct isc_socketevent { + ISC_EVENT_COMMON(isc_socketevent_t); + isc_result_t result; /*%< OK, EOF, whatever else */ + unsigned int minimum; /*%< minimum i/o for event */ + unsigned int n; /*%< bytes read or written */ + unsigned int offset; /*%< offset into buffer list */ + isc_region_t region; /*%< for single-buffer i/o */ + isc_sockaddr_t address; /*%< source address */ + isc_time_t timestamp; /*%< timestamp of packet recv */ + struct in6_pktinfo pktinfo; /*%< ipv6 pktinfo */ + isc_sockeventattr_t attributes; /*%< see isc_sockeventattr_t + * enum */ + isc_eventdestructor_t destroy; /*%< original destructor */ + unsigned int dscp; /*%< UDP dscp value */ +}; + +typedef struct isc_socket_newconnev isc_socket_newconnev_t; +struct isc_socket_newconnev { + ISC_EVENT_COMMON(isc_socket_newconnev_t); + isc_socket_t *newsocket; + isc_result_t result; /*%< OK, EOF, whatever else */ + isc_sockaddr_t address; /*%< source address */ +}; + +typedef struct isc_socket_connev isc_socket_connev_t; +struct isc_socket_connev { + ISC_EVENT_COMMON(isc_socket_connev_t); + isc_result_t result; /*%< OK, EOF, whatever else */ +}; + +#define ISC_SOCKEVENT_ANYEVENT (0) +#define ISC_SOCKEVENT_RECVDONE (ISC_EVENTCLASS_SOCKET + 1) +#define ISC_SOCKEVENT_SENDDONE (ISC_EVENTCLASS_SOCKET + 2) +#define ISC_SOCKEVENT_NEWCONN (ISC_EVENTCLASS_SOCKET + 3) +#define ISC_SOCKEVENT_CONNECT (ISC_EVENTCLASS_SOCKET + 4) + +/* + * Internal events. + */ +#define ISC_SOCKEVENT_INTR (ISC_EVENTCLASS_SOCKET + 256) +#define ISC_SOCKEVENT_INTW (ISC_EVENTCLASS_SOCKET + 257) + +typedef enum { + isc_sockettype_udp = 1, + isc_sockettype_tcp = 2, + isc_sockettype_unix = 3, + isc_sockettype_raw = 4 +} isc_sockettype_t; + +/*@{*/ +/*! + * How a socket should be shutdown in isc_socket_shutdown() calls. + */ +#define ISC_SOCKSHUT_RECV 0x00000001 /*%< close read side */ +#define ISC_SOCKSHUT_SEND 0x00000002 /*%< close write side */ +#define ISC_SOCKSHUT_ALL 0x00000003 /*%< close them all */ +/*@}*/ + +/*@{*/ +/*! + * What I/O events to cancel in isc_socket_cancel() calls. + */ +#define ISC_SOCKCANCEL_RECV 0x00000001 /*%< cancel recv */ +#define ISC_SOCKCANCEL_SEND 0x00000002 /*%< cancel send */ +#define ISC_SOCKCANCEL_ACCEPT 0x00000004 /*%< cancel accept */ +#define ISC_SOCKCANCEL_CONNECT 0x00000008 /*%< cancel connect */ +#define ISC_SOCKCANCEL_ALL 0x0000000f /*%< cancel everything */ +/*@}*/ + +/*@{*/ +/*! + * Flags for isc_socket_send() and isc_socket_recv() calls. + */ +#define ISC_SOCKFLAG_IMMEDIATE 0x00000001 /*%< send event only if needed */ +#define ISC_SOCKFLAG_NORETRY 0x00000002 /*%< drop failed UDP sends */ +/*@}*/ + +/*** + *** Socket and Socket Manager Functions + *** + *** Note: all Ensures conditions apply only if the result is success for + *** those functions which return an isc_result. + ***/ + +isc_result_t +isc_socket_create(isc_socketmgr_t *manager, int pf, isc_sockettype_t type, + isc_socket_t **socketp); +/*%< + * Create a new 'type' socket managed by 'manager'. + * + * Note: + * + *\li 'pf' is the desired protocol family, e.g. PF_INET or PF_INET6. + * + * Requires: + * + *\li 'manager' is a valid manager + * + *\li 'socketp' is a valid pointer, and *socketp == NULL + * + * Ensures: + * + * '*socketp' is attached to the newly created socket + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + *\li #ISC_R_NORESOURCES + *\li #ISC_R_UNEXPECTED + */ + +isc_result_t +isc_socket_dup(isc_socket_t *sock0, isc_socket_t **socketp); +/*%< + * Duplicate an existing socket, reusing its file descriptor. + */ + +void +isc_socket_cancel(isc_socket_t *sock, isc_task_t *task, unsigned int how); +/*%< + * Cancel pending I/O of the type specified by "how". + * + * Note: if "task" is NULL, then the cancel applies to all tasks using the + * socket. + * + * Requires: + * + * \li "socket" is a valid socket + * + * \li "task" is NULL or a valid task + * + * "how" is a bitmask describing the type of cancellation to perform. + * The type ISC_SOCKCANCEL_ALL will cancel all pending I/O on this + * socket. + * + * \li ISC_SOCKCANCEL_RECV: + * Cancel pending isc_socket_recv() calls. + * + * \li ISC_SOCKCANCEL_SEND: + * Cancel pending isc_socket_send() and isc_socket_sendto() calls. + * + * \li ISC_SOCKCANCEL_ACCEPT: + * Cancel pending isc_socket_accept() calls. + * + * \li ISC_SOCKCANCEL_CONNECT: + * Cancel pending isc_socket_connect() call. + */ + +void +isc_socket_shutdown(isc_socket_t *sock, unsigned int how); +/*%< + * Shutdown 'socket' according to 'how'. + * + * Requires: + * + * \li 'socket' is a valid socket. + * + * \li 'task' is NULL or is a valid task. + * + * \li If 'how' is 'ISC_SOCKSHUT_RECV' or 'ISC_SOCKSHUT_ALL' then + * + * The read queue must be empty. + * + * No further read requests may be made. + * + * \li If 'how' is 'ISC_SOCKSHUT_SEND' or 'ISC_SOCKSHUT_ALL' then + * + * The write queue must be empty. + * + * No further write requests may be made. + */ + +void +isc_socket_attach(isc_socket_t *sock, isc_socket_t **socketp); +/*%< + * Attach *socketp to socket. + * + * Requires: + * + * \li 'socket' is a valid socket. + * + * \li 'socketp' points to a NULL socket. + * + * Ensures: + * + * \li *socketp is attached to socket. + */ + +void +isc_socket_detach(isc_socket_t **socketp); +/*%< + * Detach *socketp from its socket. + * + * Requires: + * + * \li 'socketp' points to a valid socket. + * + * \li If '*socketp' is the last reference to the socket, + * then: + * + * There must be no pending I/O requests. + * + * Ensures: + * + * \li *socketp is NULL. + * + * \li If '*socketp' is the last reference to the socket, + * then: + * + * The socket will be shutdown (both reading and writing) + * for all tasks. + * + * All resources used by the socket have been freed + */ + +isc_result_t +isc_socket_open(isc_socket_t *sock); +/*%< + * Open a new socket file descriptor of the given socket structure. It simply + * opens a new descriptor; all of the other parameters including the socket + * type are inherited from the existing socket. This function is provided to + * avoid overhead of destroying and creating sockets when many short-lived + * sockets are frequently opened and closed. When the efficiency is not an + * issue, it should be safer to detach the unused socket and re-create a new + * one. This optimization may not be available for some systems, in which + * case this function will return ISC_R_NOTIMPLEMENTED and must not be used. + * + * Requires: + * + * \li there must be no other reference to this socket. + * + * \li 'socket' is a valid and previously closed by isc_socket_close() + * + * Returns: + * Same as isc_socket_create(). + * \li ISC_R_NOTIMPLEMENTED + */ + +isc_result_t +isc_socket_close(isc_socket_t *sock); +/*%< + * Close a socket file descriptor of the given socket structure. This function + * is provided as an alternative to destroying an unused socket when overhead + * destroying/re-creating sockets can be significant, and is expected to be + * used with isc_socket_open(). This optimization may not be available for some + * systems, in which case this function will return ISC_R_NOTIMPLEMENTED and + * must not be used. + * + * Requires: + * + * \li The socket must have a valid descriptor. + * + * \li There must be no other reference to this socket. + * + * \li There must be no pending I/O requests. + * + * Returns: + * \li #ISC_R_NOTIMPLEMENTED + */ + +isc_result_t +isc_socket_bind(isc_socket_t *sock, const isc_sockaddr_t *addressp, + isc_socket_options_t options); +/*%< + * Bind 'socket' to '*addressp'. + * + * Requires: + * + * \li 'socket' is a valid socket + * + * \li 'addressp' points to a valid isc_sockaddr. + * + * Returns: + * + * \li ISC_R_SUCCESS + * \li ISC_R_NOPERM + * \li ISC_R_ADDRNOTAVAIL + * \li ISC_R_ADDRINUSE + * \li ISC_R_BOUND + * \li ISC_R_UNEXPECTED + */ + +isc_result_t +isc_socket_filter(isc_socket_t *sock, const char *filter); +/*%< + * Inform the kernel that it should perform accept filtering. + * If filter is NULL the current filter will be removed. + */ + +isc_result_t +isc_socket_listen(isc_socket_t *sock, unsigned int backlog); +/*%< + * Set listen mode on the socket. After this call, the only function that + * can be used (other than attach and detach) is isc_socket_accept(). + * + * Notes: + * + * \li 'backlog' is as in the UNIX system call listen() and may be + * ignored by non-UNIX implementations. + * + * \li If 'backlog' is zero, a reasonable system default is used, usually + * SOMAXCONN. + * + * Requires: + * + * \li 'socket' is a valid, bound TCP socket or a valid, bound UNIX socket. + * + * Returns: + * + * \li ISC_R_SUCCESS + * \li ISC_R_UNEXPECTED + */ + +isc_result_t +isc_socket_accept(isc_socket_t *sock, isc_task_t *task, isc_taskaction_t action, + void *arg); +/*%< + * Queue accept event. When a new connection is received, the task will + * get an ISC_SOCKEVENT_NEWCONN event with the sender set to the listen + * socket. The new socket structure is sent inside the isc_socket_newconnev_t + * event type, and is attached to the task 'task'. + * + * REQUIRES: + * \li 'socket' is a valid TCP socket that isc_socket_listen() was called + * on. + * + * \li 'task' is a valid task + * + * \li 'action' is a valid action + * + * RETURNS: + * \li ISC_R_SUCCESS + * \li ISC_R_NOMEMORY + * \li ISC_R_UNEXPECTED + */ + +isc_result_t +isc_socket_connect(isc_socket_t *sock, const isc_sockaddr_t *addressp, + isc_task_t *task, isc_taskaction_t action, void *arg); +/*%< + * Connect 'socket' to peer with address *saddr. When the connection + * succeeds, or when an error occurs, a CONNECT event with action 'action' + * and arg 'arg' will be posted to the event queue for 'task'. + * + * Requires: + * + * \li 'socket' is a valid TCP socket + * + * \li 'addressp' points to a valid isc_sockaddr + * + * \li 'task' is a valid task + * + * \li 'action' is a valid action + * + * Returns: + * + * \li ISC_R_SUCCESS + * \li ISC_R_NOMEMORY + * \li ISC_R_UNEXPECTED + * + * Posted event's result code: + * + * \li ISC_R_SUCCESS + * \li ISC_R_TIMEDOUT + * \li ISC_R_CONNREFUSED + * \li ISC_R_NETUNREACH + * \li ISC_R_UNEXPECTED + */ + +isc_result_t +isc_socket_getpeername(isc_socket_t *sock, isc_sockaddr_t *addressp); +/*%< + * Get the name of the peer connected to 'socket'. + * + * Requires: + * + * \li 'socket' is a valid TCP socket. + * + * Returns: + * + * \li ISC_R_SUCCESS + * \li ISC_R_TOOSMALL + * \li ISC_R_UNEXPECTED + */ + +isc_result_t +isc_socket_getsockname(isc_socket_t *sock, isc_sockaddr_t *addressp); +/*%< + * Get the name of 'socket'. + * + * Requires: + * + * \li 'socket' is a valid socket. + * + * Returns: + * + * \li ISC_R_SUCCESS + * \li ISC_R_TOOSMALL + * \li ISC_R_UNEXPECTED + */ + +/*@{*/ +isc_result_t +isc_socket_recv(isc_socket_t *sock, isc_region_t *region, unsigned int minimum, + isc_task_t *task, isc_taskaction_t action, void *arg); + +isc_result_t +isc_socket_recv2(isc_socket_t *sock, isc_region_t *region, unsigned int minimum, + isc_task_t *task, isc_socketevent_t *event, + unsigned int flags); + +/*! + * Receive from 'socket', storing the results in region. + * + * Notes: + * + *\li Let 'length' refer to the length of 'region' or to the sum of all + * available regions in the list of buffers '*buflist'. + * + *\li If 'minimum' is non-zero and at least that many bytes are read, + * the completion event will be posted to the task 'task.' If minimum + * is zero, the exact number of bytes requested in the region must + * be read for an event to be posted. This only makes sense for TCP + * connections, and is always set to 1 byte for UDP. + * + *\li The read will complete when the desired number of bytes have been + * read, if end-of-input occurs, or if an error occurs. A read done + * event with the given 'action' and 'arg' will be posted to the + * event queue of 'task'. + * + *\li The caller may not modify 'region', the buffers which are passed + * into this function, or any data they refer to until the completion + * event is received. + * + *\li For isc_socket_recv2(): + * 'event' is not NULL, and the non-socket specific fields are + * expected to be initialized. + * + *\li For isc_socket_recv2(): + * The only defined value for 'flags' is ISC_SOCKFLAG_IMMEDIATE. If + * set and the operation completes, the return value will be + * ISC_R_SUCCESS and the event will be filled in and not sent. If the + * operation does not complete, the return value will be + * ISC_R_INPROGRESS and the event will be sent when the operation + * completes. + * + * Requires: + * + *\li 'socket' is a valid, bound socket. + * + *\li For isc_socket_recv(): + * 'region' is a valid region + * + *\li For isc_socket_recvv(): + * 'buflist' is non-NULL, and '*buflist' contain at least one buffer. + * + *\li 'task' is a valid task + * + *\li For isc_socket_recv() and isc_socket_recvv(): + * action != NULL and is a valid action + * + *\li For isc_socket_recv2(): + * event != NULL + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_INPROGRESS + *\li #ISC_R_NOMEMORY + *\li #ISC_R_UNEXPECTED + * + * Event results: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_UNEXPECTED + *\li XXX needs other net-type errors + */ +/*@}*/ + +/*@{*/ +isc_result_t +isc_socket_send(isc_socket_t *sock, isc_region_t *region, isc_task_t *task, + isc_taskaction_t action, void *arg); +isc_result_t +isc_socket_sendto(isc_socket_t *sock, isc_region_t *region, isc_task_t *task, + isc_taskaction_t action, void *arg, + const isc_sockaddr_t *address, struct in6_pktinfo *pktinfo); +isc_result_t +isc_socket_sendto2(isc_socket_t *sock, isc_region_t *region, isc_task_t *task, + const isc_sockaddr_t *address, struct in6_pktinfo *pktinfo, + isc_socketevent_t *event, unsigned int flags); + +/*! + * Send the contents of 'region' to the socket's peer. + * + * Notes: + * + *\li Shutting down the requestor's task *may* result in any + * still pending writes being dropped or completed, depending on the + * underlying OS implementation. + * + *\li If 'action' is NULL, then no completion event will be posted. + * + *\li The caller may not modify 'region', the buffers which are passed + * into this function, or any data they refer to until the completion + * event is received. + * + *\li For isc_socket_sendto2(): + * 'event' is not NULL, and the non-socket specific fields are + * expected to be initialized. + * + *\li For isc_socket_sendto2(): + * The only defined values for 'flags' are ISC_SOCKFLAG_IMMEDIATE + * and ISC_SOCKFLAG_NORETRY. + * + *\li If ISC_SOCKFLAG_IMMEDIATE is set and the operation completes, the + * return value will be ISC_R_SUCCESS and the event will be filled + * in and not sent. If the operation does not complete, the return + * value will be ISC_R_INPROGRESS and the event will be sent when + * the operation completes. + * + *\li ISC_SOCKFLAG_NORETRY can only be set for UDP sockets. If set + * and the send operation fails due to a transient error, the send + * will not be retried and the error will be indicated in the event. + * Using this option along with ISC_SOCKFLAG_IMMEDIATE allows the caller + * to specify a region that is allocated on the stack. + * + * Requires: + * + *\li 'socket' is a valid, bound socket. + * + *\li For isc_socket_send(): + * 'region' is a valid region + * + *\li For isc_socket_sendv() and isc_socket_sendtov(): + * 'buflist' is non-NULL, and '*buflist' contain at least one buffer. + * + *\li 'task' is a valid task + * + *\li For isc_socket_sendv(), isc_socket_sendtov(), isc_socket_send(), and + * isc_socket_sendto(): + * action == NULL or is a valid action + * + *\li For isc_socket_sendto2(): + * event != NULL + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_INPROGRESS + *\li #ISC_R_NOMEMORY + *\li #ISC_R_UNEXPECTED + * + * Event results: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_UNEXPECTED + *\li XXX needs other net-type errors + */ +/*@}*/ + +isc_result_t +isc_socketmgr_create(isc_mem_t *mctx, isc_socketmgr_t **managerp); + +isc_result_t +isc_socketmgr_create2(isc_mem_t *mctx, isc_socketmgr_t **managerp, + unsigned int maxsocks, int nthreads); +/*%< + * Create a socket manager. If "maxsocks" is non-zero, it specifies the + * maximum number of sockets that the created manager should handle. + * isc_socketmgr_create() is equivalent of isc_socketmgr_create2() with + * "maxsocks" being zero. + * + * Notes: + * + *\li All memory will be allocated in memory context 'mctx'. + * + * Requires: + * + *\li 'mctx' is a valid memory context. + * + *\li 'managerp' points to a NULL isc_socketmgr_t. + * + *\li 'actx' is a valid application context (for createinctx()). + * + * Ensures: + * + *\li '*managerp' is a valid isc_socketmgr_t. + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + *\li #ISC_R_UNEXPECTED + *\li #ISC_R_NOTIMPLEMENTED + */ + +isc_result_t +isc_socketmgr_getmaxsockets(isc_socketmgr_t *manager, unsigned int *nsockp); +/*%< + * Returns in "*nsockp" the maximum number of sockets this manager may open. + * + * Requires: + * + *\li '*manager' is a valid isc_socketmgr_t. + *\li 'nsockp' is not NULL. + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOTIMPLEMENTED + */ + +void +isc_socketmgr_setstats(isc_socketmgr_t *manager, isc_stats_t *stats); +/*%< + * Set a general socket statistics counter set 'stats' for 'manager'. + * + * Requires: + * \li 'manager' is valid, hasn't opened any socket, and doesn't have + * stats already set. + * + *\li stats is a valid statistics supporting socket statistics counters + * (see above). + */ + +void +isc_socketmgr_destroy(isc_socketmgr_t **managerp); +/*%< + * Destroy a socket manager. + * + * Notes: + * + *\li This routine blocks until there are no sockets left in the manager, + * so if the caller holds any socket references using the manager, it + * must detach them before calling isc_socketmgr_destroy() or it will + * block forever. + * + * Requires: + * + *\li '*managerp' is a valid isc_socketmgr_t. + * + *\li All sockets managed by this manager are fully detached. + * + * Ensures: + * + *\li *managerp == NULL + * + *\li All resources used by the manager have been freed. + */ + +isc_sockettype_t +isc_socket_gettype(isc_socket_t *sock); +/*%< + * Returns the socket type for "sock." + * + * Requires: + * + *\li "sock" is a valid socket. + */ + +/*@{*/ +void +isc_socket_ipv6only(isc_socket_t *sock, bool yes); +/*%< + * If the socket is an IPv6 socket set/clear the IPV6_IPV6ONLY socket + * option if the host OS supports this option. + * + * Requires: + *\li 'sock' is a valid socket. + */ +/*@}*/ + +void +isc_socket_dscp(isc_socket_t *sock, isc_dscp_t dscp); +/*%< + * Sets the Differentiated Services Code Point (DSCP) field for packets + * transmitted on this socket. If 'dscp' is -1, return immediately. + * + * Requires: + *\li 'sock' is a valid socket. + */ + +isc_socketevent_t * +isc_socket_socketevent(isc_mem_t *mctx, void *sender, isc_eventtype_t eventtype, + isc_taskaction_t action, void *arg); +/*%< + * Get a isc_socketevent_t to be used with isc_socket_sendto2(), etc. + */ + +void +isc_socket_cleanunix(const isc_sockaddr_t *addr, bool active); + +/*%< + * Cleanup UNIX domain sockets in the file-system. If 'active' is true + * then just unlink the socket. If 'active' is false try to determine + * if there is a listener of the socket or not. If no listener is found + * then unlink socket. + * + * Prior to unlinking the path is tested to see if it a socket. + * + * Note: there are a number of race conditions which cannot be avoided + * both in the filesystem and any application using UNIX domain + * sockets (e.g. socket is tested between bind() and listen(), + * the socket is deleted and replaced in the file-system between + * stat() and unlink()). + */ + +isc_result_t +isc_socket_permunix(const isc_sockaddr_t *sockaddr, uint32_t perm, + uint32_t owner, uint32_t group); +/*%< + * Set ownership and file permissions on the UNIX domain socket. + * + * Note: On Solaris this secures the directory containing + * the socket as Solaris do not honour the filesystem + * permissions on the socket. + * + * Requires: + * \li 'sockaddr' to be a valid UNIX domain sockaddr. + * + * Returns: + * \li #ISC_R_SUCCESS + * \li #ISC_R_FAILURE + */ + +void +isc_socket_setname(isc_socket_t *socket, const char *name, void *tag); +/*%< + * Set the name and optional tag for a socket. This allows tracking of the + * owner or purpose for this socket, and is useful for tracing and statistics + * reporting. + */ + +const char * +isc_socket_getname(isc_socket_t *socket); +/*%< + * Get the name associated with a socket, if any. + */ + +void * +isc_socket_gettag(isc_socket_t *socket); +/*%< + * Get the tag associated with a socket, if any. + */ + +int +isc_socket_getfd(isc_socket_t *socket); +/*%< + * Get the file descriptor associated with a socket + */ + +void +isc_socketmgr_setreserved(isc_socketmgr_t *mgr, uint32_t); +/*%< + * Temporary. For use by named only. + */ + +void +isc_socketmgr_maxudp(isc_socketmgr_t *mgr, unsigned int maxudp); +/*%< + * Test interface. Drop UDP packet > 'maxudp'. + */ + +bool +isc_socket_hasreuseport(void); +/*%< + * Return true if there is SO_REUSEPORT support + */ + +#ifdef HAVE_LIBXML2 +int +isc_socketmgr_renderxml(isc_socketmgr_t *mgr, void *writer0); +/*%< + * Render internal statistics and other state into the XML document. + */ +#endif /* HAVE_LIBXML2 */ + +#ifdef HAVE_JSON_C +isc_result_t +isc_socketmgr_renderjson(isc_socketmgr_t *mgr, void *stats0); +/*%< + * Render internal statistics and other state into JSON format. + */ +#endif /* HAVE_JSON_C */ + +/*%< + * See isc_socketmgr_create() above. + */ +typedef isc_result_t (*isc_socketmgrcreatefunc_t)(isc_mem_t *mctx, + isc_socketmgr_t **managerp); + +ISC_LANG_ENDDECLS + +#endif /* ISC_SOCKET_H */ diff --git a/lib/isc/include/isc/stats.h b/lib/isc/include/isc/stats.h new file mode 100644 index 0000000..5e25bac --- /dev/null +++ b/lib/isc/include/isc/stats.h @@ -0,0 +1,256 @@ +/* + * 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. + */ + +#ifndef ISC_STATS_H +#define ISC_STATS_H 1 + +/*! \file isc/stats.h */ + +#include <inttypes.h> + +#include <isc/types.h> + +/*% + * Statistics counters. Used as isc_statscounter_t values. + */ +enum { + /*% + * Socket statistics counters. + */ + isc_sockstatscounter_udp4open = 0, + isc_sockstatscounter_udp6open = 1, + isc_sockstatscounter_tcp4open = 2, + isc_sockstatscounter_tcp6open = 3, + isc_sockstatscounter_unixopen = 4, + + isc_sockstatscounter_udp4openfail = 5, + isc_sockstatscounter_udp6openfail = 6, + isc_sockstatscounter_tcp4openfail = 7, + isc_sockstatscounter_tcp6openfail = 8, + isc_sockstatscounter_unixopenfail = 9, + + isc_sockstatscounter_udp4close = 10, + isc_sockstatscounter_udp6close = 11, + isc_sockstatscounter_tcp4close = 12, + isc_sockstatscounter_tcp6close = 13, + isc_sockstatscounter_unixclose = 14, + isc_sockstatscounter_fdwatchclose = 15, + + isc_sockstatscounter_udp4bindfail = 16, + isc_sockstatscounter_udp6bindfail = 17, + isc_sockstatscounter_tcp4bindfail = 18, + isc_sockstatscounter_tcp6bindfail = 19, + isc_sockstatscounter_unixbindfail = 20, + isc_sockstatscounter_fdwatchbindfail = 21, + + isc_sockstatscounter_udp4connect = 22, + isc_sockstatscounter_udp6connect = 23, + isc_sockstatscounter_tcp4connect = 24, + isc_sockstatscounter_tcp6connect = 25, + isc_sockstatscounter_unixconnect = 26, + isc_sockstatscounter_fdwatchconnect = 27, + + isc_sockstatscounter_udp4connectfail = 28, + isc_sockstatscounter_udp6connectfail = 29, + isc_sockstatscounter_tcp4connectfail = 30, + isc_sockstatscounter_tcp6connectfail = 31, + isc_sockstatscounter_unixconnectfail = 32, + isc_sockstatscounter_fdwatchconnectfail = 33, + + isc_sockstatscounter_tcp4accept = 34, + isc_sockstatscounter_tcp6accept = 35, + isc_sockstatscounter_unixaccept = 36, + + isc_sockstatscounter_tcp4acceptfail = 37, + isc_sockstatscounter_tcp6acceptfail = 38, + isc_sockstatscounter_unixacceptfail = 39, + + isc_sockstatscounter_udp4sendfail = 40, + isc_sockstatscounter_udp6sendfail = 41, + isc_sockstatscounter_tcp4sendfail = 42, + isc_sockstatscounter_tcp6sendfail = 43, + isc_sockstatscounter_unixsendfail = 44, + isc_sockstatscounter_fdwatchsendfail = 45, + + isc_sockstatscounter_udp4recvfail = 46, + isc_sockstatscounter_udp6recvfail = 47, + isc_sockstatscounter_tcp4recvfail = 48, + isc_sockstatscounter_tcp6recvfail = 49, + isc_sockstatscounter_unixrecvfail = 50, + isc_sockstatscounter_fdwatchrecvfail = 51, + + isc_sockstatscounter_udp4active = 52, + isc_sockstatscounter_udp6active = 53, + isc_sockstatscounter_tcp4active = 54, + isc_sockstatscounter_tcp6active = 55, + isc_sockstatscounter_unixactive = 56, + + isc_sockstatscounter_rawopen = 57, + isc_sockstatscounter_rawopenfail = 58, + isc_sockstatscounter_rawclose = 59, + isc_sockstatscounter_rawrecvfail = 60, + isc_sockstatscounter_rawactive = 61, + + isc_sockstatscounter_max = 62 +}; + +ISC_LANG_BEGINDECLS + +/*%< + * Flag(s) for isc_stats_dump(). + */ +#define ISC_STATSDUMP_VERBOSE 0x00000001 /*%< dump 0-value counters */ + +/*%< + * Dump callback type. + */ +typedef void (*isc_stats_dumper_t)(isc_statscounter_t, uint64_t, void *); + +isc_result_t +isc_stats_create(isc_mem_t *mctx, isc_stats_t **statsp, int ncounters); +/*%< + * Create a statistics counter structure of general type. It counts a general + * set of counters indexed by an ID between 0 and ncounters -1. + * + * Requires: + *\li 'mctx' must be a valid memory context. + * + *\li 'statsp' != NULL && '*statsp' == NULL. + * + * Returns: + *\li ISC_R_SUCCESS -- all ok + * + *\li anything else -- failure + */ + +void +isc_stats_attach(isc_stats_t *stats, isc_stats_t **statsp); +/*%< + * Attach to a statistics set. + * + * Requires: + *\li 'stats' is a valid isc_stats_t. + * + *\li 'statsp' != NULL && '*statsp' == NULL + */ + +void +isc_stats_detach(isc_stats_t **statsp); +/*%< + * Detaches from the statistics set. + * + * Requires: + *\li 'statsp' != NULL and '*statsp' is a valid isc_stats_t. + */ + +int +isc_stats_ncounters(isc_stats_t *stats); +/*%< + * Returns the number of counters contained in stats. + * + * Requires: + *\li 'stats' is a valid isc_stats_t. + * + */ + +void +isc_stats_increment(isc_stats_t *stats, isc_statscounter_t counter); +/*%< + * Increment the counter-th counter of stats. + * + * Requires: + *\li 'stats' is a valid isc_stats_t. + * + *\li counter is less than the maximum available ID for the stats specified + * on creation. + */ + +void +isc_stats_decrement(isc_stats_t *stats, isc_statscounter_t counter); +/*%< + * Decrement the counter-th counter of stats. + * + * Requires: + *\li 'stats' is a valid isc_stats_t. + */ + +void +isc_stats_dump(isc_stats_t *stats, isc_stats_dumper_t dump_fn, void *arg, + unsigned int options); +/*%< + * Dump the current statistics counters in a specified way. For each counter + * in stats, dump_fn is called with its current value and the given argument + * arg. By default counters that have a value of 0 is skipped; if options has + * the ISC_STATSDUMP_VERBOSE flag, even such counters are dumped. + * + * Requires: + *\li 'stats' is a valid isc_stats_t. + */ + +void +isc_stats_set(isc_stats_t *stats, uint64_t val, isc_statscounter_t counter); +/*%< + * Set the given counter to the specified value. + * + * Requires: + *\li 'stats' is a valid isc_stats_t. + */ + +void +isc_stats_set(isc_stats_t *stats, uint64_t val, isc_statscounter_t counter); +/*%< + * Set the given counter to the specified value. + * + * Requires: + *\li 'stats' is a valid isc_stats_t. + */ + +void +isc_stats_update_if_greater(isc_stats_t *stats, isc_statscounter_t counter, + isc_statscounter_t value); +/*%< + * Atomically assigns 'value' to 'counter' if value > counter. + * + * Requires: + *\li 'stats' is a valid isc_stats_t. + * + *\li counter is less than the maximum available ID for the stats specified + * on creation. + */ + +isc_statscounter_t +isc_stats_get_counter(isc_stats_t *stats, isc_statscounter_t counter); +/*%< + * Returns value currently stored in counter. + * + * Requires: + *\li 'stats' is a valid isc_stats_t. + * + *\li counter is less than the maximum available ID for the stats specified + * on creation. + */ + +void +isc_stats_resize(isc_stats_t **stats, int ncounters); +/*%< + * Resize a statistics counter structure of general type. The new set of + * counters are indexed by an ID between 0 and ncounters -1. + * + * Requires: + *\li 'stats' is a valid isc_stats_t. + *\li 'ncounters' is a non-zero positive number. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_STATS_H */ diff --git a/lib/isc/include/isc/stdio.h b/lib/isc/include/isc/stdio.h new file mode 100644 index 0000000..2c391d7 --- /dev/null +++ b/lib/isc/include/isc/stdio.h @@ -0,0 +1,74 @@ +/* + * 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. + */ + +#ifndef ISC_STDIO_H +#define ISC_STDIO_H 1 + +/*! \file isc/stdio.h */ + +/*% + * These functions are wrappers around the corresponding stdio functions. + * + * They return a detailed error code in the form of an an isc_result_t. ANSI C + * does not guarantee that stdio functions set errno, hence these functions + * must use platform dependent methods (e.g., the POSIX errno) to construct the + * error code. + */ + +#include <stdio.h> + +#include <isc/lang.h> +#include <isc/result.h> + +ISC_LANG_BEGINDECLS + +/*% Open */ +isc_result_t +isc_stdio_open(const char *filename, const char *mode, FILE **fp); + +/*% Close */ +isc_result_t +isc_stdio_close(FILE *f); + +/*% Seek */ +isc_result_t +isc_stdio_seek(FILE *f, off_t offset, int whence); + +/*% Tell */ +isc_result_t +isc_stdio_tell(FILE *f, off_t *offsetp); + +/*% Read */ +isc_result_t +isc_stdio_read(void *ptr, size_t size, size_t nmemb, FILE *f, size_t *nret); + +/*% Write */ +isc_result_t +isc_stdio_write(const void *ptr, size_t size, size_t nmemb, FILE *f, + size_t *nret); + +/*% Flush */ +isc_result_t +isc_stdio_flush(FILE *f); + +isc_result_t +isc_stdio_sync(FILE *f); +/*%< + * Invoke fsync() on the file descriptor underlying an stdio stream, or an + * equivalent system-dependent operation. Note that this function has no + * direct counterpart in the stdio library. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_STDIO_H */ diff --git a/lib/isc/include/isc/strerr.h b/lib/isc/include/isc/strerr.h new file mode 100644 index 0000000..ce093b0 --- /dev/null +++ b/lib/isc/include/isc/strerr.h @@ -0,0 +1,23 @@ +/* + * 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. + */ + +#pragma once + +/*! \file isc/strerr.h */ + +#include <isc/string.h> + +#if defined(strerror_r) +#undef strerror_r +#endif /* if defined(strerror_r) */ +#define strerror_r isc_string_strerror_r diff --git a/lib/isc/include/isc/string.h b/lib/isc/include/isc/string.h new file mode 100644 index 0000000..1bc5693 --- /dev/null +++ b/lib/isc/include/isc/string.h @@ -0,0 +1,43 @@ +/* + * 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. + */ + +#pragma once + +/*! \file isc/string.h */ + +#include <string.h> + +#include "isc/lang.h" +#include "isc/platform.h" + +ISC_LANG_BEGINDECLS + +#if !defined(HAVE_STRLCPY) +size_t +strlcpy(char *dst, const char *src, size_t size); +#endif /* !defined(HAVE_STRLCPY) */ + +#if !defined(HAVE_STRLCAT) +size_t +strlcat(char *dst, const char *src, size_t size); +#endif /* if !defined(HAVE_STRLCAT) */ + +#if !defined(HAVE_STRNSTR) +char * +strnstr(const char *s, const char *find, size_t slen); +#endif /* if !defined(HAVE_STRNSTR) */ + +int +isc_string_strerror_r(int errnum, char *buf, size_t buflen); + +ISC_LANG_ENDDECLS diff --git a/lib/isc/include/isc/symtab.h b/lib/isc/include/isc/symtab.h new file mode 100644 index 0000000..9ec8564 --- /dev/null +++ b/lib/isc/include/isc/symtab.h @@ -0,0 +1,138 @@ +/* + * 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. + */ + +#ifndef ISC_SYMTAB_H +#define ISC_SYMTAB_H 1 + +/***** +***** Module Info +*****/ + +/*! \file isc/symtab.h + * \brief Provides a simple memory-based symbol table. + * + * Keys are C strings, and key comparisons are case-insensitive. A type may + * be specified when looking up, defining, or undefining. A type value of + * 0 means "match any type"; any other value will only match the given + * type. + * + * It's possible that a client will attempt to define a <key, type, value> + * tuple when a tuple with the given key and type already exists in the table. + * What to do in this case is specified by the client. Possible policies are: + * + *\li #isc_symexists_reject Disallow the define, returning #ISC_R_EXISTS + *\li #isc_symexists_replace Replace the old value with the new. The + * undefine action (if provided) will be called + * with the old <key, type, value> tuple. + *\li #isc_symexists_add Add the new tuple, leaving the old tuple in + * the table. Subsequent lookups will retrieve + * the most-recently-defined tuple. + * + * A lookup of a key using type 0 will return the most-recently defined + * symbol with that key. An undefine of a key using type 0 will undefine the + * most-recently defined symbol with that key. Trying to define a key with + * type 0 is illegal. + * + * The symbol table library does not make a copy the key field, so the + * caller must ensure that any key it passes to isc_symtab_define() will not + * change until it calls isc_symtab_undefine() or isc_symtab_destroy(). + * + * A user-specified action will be called (if provided) when a symbol is + * undefined. It can be used to free memory associated with keys and/or + * values. + * + * A symbol table is implemented as a hash table of lists; the size of the + * hash table is set by the 'size' parameter to isc_symtbl_create(). When + * the number of entries in the symbol table reaches three quarters of this + * value, the hash table is reallocated with size doubled, in order to + * optimize lookup performance. This has a negative effect on insertion + * performance, which can be mitigated by sizing the table appropriately + * when creating it. + * + * \li MP: + * The callers of this module must ensure any required synchronization. + * + * \li Reliability: + * No anticipated impact. + * + * \li Resources: + * TBS + * + * \li Security: + * No anticipated impact. + * + * \li Standards: + * None. + */ + +/*** + *** Imports. + ***/ + +#include <stdbool.h> + +#include <isc/lang.h> +#include <isc/types.h> + +/* + *** Symbol Tables. + ***/ +/*% Symbol table value. */ +typedef union isc_symvalue { + void *as_pointer; + const void *as_cpointer; + int as_integer; + unsigned int as_uinteger; +} isc_symvalue_t; + +typedef void (*isc_symtabaction_t)(char *key, unsigned int type, + isc_symvalue_t value, void *userarg); +/*% Symbol table exists. */ +typedef enum { + isc_symexists_reject = 0, /*%< Disallow the define */ + isc_symexists_replace = 1, /*%< Replace the old value with the new */ + isc_symexists_add = 2 /*%< Add the new tuple */ +} isc_symexists_t; + +ISC_LANG_BEGINDECLS + +/*% Create a symbol table. */ +isc_result_t +isc_symtab_create(isc_mem_t *mctx, unsigned int size, + isc_symtabaction_t undefine_action, void *undefine_arg, + bool case_sensitive, isc_symtab_t **symtabp); + +/*% Destroy a symbol table. */ +void +isc_symtab_destroy(isc_symtab_t **symtabp); + +/*% Lookup a symbol table. */ +isc_result_t +isc_symtab_lookup(isc_symtab_t *symtab, const char *key, unsigned int type, + isc_symvalue_t *value); + +/*% Define a symbol table. */ +isc_result_t +isc_symtab_define(isc_symtab_t *symtab, const char *key, unsigned int type, + isc_symvalue_t value, isc_symexists_t exists_policy); + +/*% Undefine a symbol table. */ +isc_result_t +isc_symtab_undefine(isc_symtab_t *symtab, const char *key, unsigned int type); + +/*% Return the number of items in a symbol table. */ +unsigned int +isc_symtab_count(isc_symtab_t *symtab); +ISC_LANG_ENDDECLS + +#endif /* ISC_SYMTAB_H */ diff --git a/lib/isc/include/isc/task.h b/lib/isc/include/isc/task.h new file mode 100644 index 0000000..810552d --- /dev/null +++ b/lib/isc/include/isc/task.h @@ -0,0 +1,721 @@ +/* + * 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. + */ + +#pragma once + +/***** + ***** Module Info + *****/ + +/*! \file isc/task.h + * \brief The task system provides a lightweight execution context, which is + * basically an event queue. + * + * When a task's event queue is non-empty, the + * task is runnable. A small work crew of threads, typically one per CPU, + * execute runnable tasks by dispatching the events on the tasks' event + * queues. Context switching between tasks is fast. + * + * \li MP: + * The module ensures appropriate synchronization of data structures it + * creates and manipulates. + * The caller must ensure that isc_taskmgr_destroy() is called only + * once for a given manager. + * + * \li Reliability: + * No anticipated impact. + * + * \li Resources: + * TBS + * + * \li Security: + * No anticipated impact. + * + * \li Standards: + * None. + * + * \section purge Purging and Unsending + * + * Events which have been queued for a task but not delivered may be removed + * from the task's event queue by purging or unsending. + * + * With both types, the caller specifies a matching pattern that selects + * events based upon their sender, type, and tag. + * + * Purging calls isc_event_free() on the matching events. + * + * Unsending returns a list of events that matched the pattern. + * The caller is then responsible for them. + * + * Consumers of events should purge, not unsend. + * + * Producers of events often want to remove events when the caller indicates + * it is no longer interested in the object, e.g. by canceling a timer. + * Sometimes this can be done by purging, but for some event types, the + * calls to isc_event_free() cause deadlock because the event free routine + * wants to acquire a lock the caller is already holding. Unsending instead + * of purging solves this problem. As a general rule, producers should only + * unsend events which they have sent. + */ + +/*** + *** Imports. + ***/ + +#include <stdbool.h> + +#include <isc/eventclass.h> +#include <isc/lang.h> +#include <isc/netmgr.h> +#include <isc/stdtime.h> +#include <isc/types.h> + +#define ISC_TASKEVENT_FIRSTEVENT (ISC_EVENTCLASS_TASK + 0) +#define ISC_TASKEVENT_SHUTDOWN (ISC_EVENTCLASS_TASK + 1) +#define ISC_TASKEVENT_TEST (ISC_EVENTCLASS_TASK + 1) +#define ISC_TASKEVENT_LASTEVENT (ISC_EVENTCLASS_TASK + 65535) + +/***** + ***** Tasks. + *****/ + +ISC_LANG_BEGINDECLS + +/*** + *** Types + ***/ + +typedef enum { + isc_taskmgrmode_normal = 0, + isc_taskmgrmode_privileged +} isc_taskmgrmode_t; + +isc_result_t +isc_task_create(isc_taskmgr_t *manager, unsigned int quantum, + isc_task_t **taskp); +isc_result_t +isc_task_create_bound(isc_taskmgr_t *manager, unsigned int quantum, + isc_task_t **taskp, int threadid); +/*%< + * Create a task, optionally bound to a particular threadid. + * + * Notes: + * + *\li If 'quantum' is non-zero, then only that many events can be dispatched + * before the task must yield to other tasks waiting to execute. If + * quantum is zero, then the default quantum of the task manager will + * be used. + * + *\li The 'quantum' option may be removed from isc_task_create() in the + * future. If this happens, isc_task_getquantum() and + * isc_task_setquantum() will be provided. + * + * Requires: + * + *\li 'manager' is a valid task manager. + * + *\li taskp != NULL && *taskp == NULL + * + * Ensures: + * + *\li On success, '*taskp' is bound to the new task. + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + *\li #ISC_R_UNEXPECTED + *\li #ISC_R_SHUTTINGDOWN + */ + +void +isc_task_ready(isc_task_t *task); +/*%< + * Enqueue the task onto netmgr queue. + */ + +isc_result_t +isc_task_run(isc_task_t *task); +/*%< + * Run all the queued events for the 'task', returning + * when the queue is empty or the number of events executed + * exceeds the 'quantum' specified when the task was created. + * + * Requires: + * + *\li 'task' is a valid task. + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_QUOTA + */ + +void +isc_task_attach(isc_task_t *source, isc_task_t **targetp); +/*%< + * Attach *targetp to source. + * + * Requires: + * + *\li 'source' is a valid task. + * + *\li 'targetp' points to a NULL isc_task_t *. + * + * Ensures: + * + *\li *targetp is attached to source. + */ + +void +isc_task_detach(isc_task_t **taskp); +/*%< + * Detach *taskp from its task. + * + * Requires: + * + *\li '*taskp' is a valid task. + * + * Ensures: + * + *\li *taskp is NULL. + * + *\li If '*taskp' is the last reference to the task, the task is idle (has + * an empty event queue), and has not been shutdown, the task will be + * shutdown. + * + *\li If '*taskp' is the last reference to the task and + * the task has been shutdown, + * all resources used by the task will be freed. + */ + +void +isc_task_send(isc_task_t *task, isc_event_t **eventp); + +void +isc_task_sendto(isc_task_t *task, isc_event_t **eventp, int c); +/*%< + * Send '*event' to 'task', if task is idle try starting it on cpu 'c' + * If 'c' is smaller than 0 then cpu is selected randomly. + * + * Requires: + * + *\li 'task' is a valid task. + *\li eventp != NULL && *eventp != NULL. + * + * Ensures: + * + *\li *eventp == NULL. + */ + +void +isc_task_sendtoanddetach(isc_task_t **taskp, isc_event_t **eventp, int c); + +void +isc_task_sendanddetach(isc_task_t **taskp, isc_event_t **eventp); +/*%< + * Send '*event' to '*taskp' and then detach '*taskp' from its + * task. If task is idle try starting it on cpu 'c' + * If 'c' is smaller than 0 then cpu is selected randomly. + * + * Requires: + * + *\li '*taskp' is a valid task. + *\li eventp != NULL && *eventp != NULL. + * + * Ensures: + * + *\li *eventp == NULL. + * + *\li *taskp == NULL. + * + *\li If '*taskp' is the last reference to the task, the task is + * idle (has an empty event queue), and has not been shutdown, + * the task will be shutdown. + * + *\li If '*taskp' is the last reference to the task and + * the task has been shutdown, + * all resources used by the task will be freed. + */ + +unsigned int +isc_task_purgerange(isc_task_t *task, void *sender, isc_eventtype_t first, + isc_eventtype_t last, void *tag); +/*%< + * Purge events from a task's event queue. + * + * Requires: + * + *\li 'task' is a valid task. + * + *\li last >= first + * + * Ensures: + * + *\li Events in the event queue of 'task' whose sender is 'sender', whose + * type is >= first and <= last, and whose tag is 'tag' will be purged, + * unless they are marked as unpurgable. + * + *\li A sender of NULL will match any sender. A NULL tag matches any + * tag. + * + * Returns: + * + *\li The number of events purged. + */ + +unsigned int +isc_task_purge(isc_task_t *task, void *sender, isc_eventtype_t type, void *tag); +/*%< + * Purge events from a task's event queue. + * + * Notes: + * + *\li This function is equivalent to + * + *\code + * isc_task_purgerange(task, sender, type, type, tag); + *\endcode + * + * Requires: + * + *\li 'task' is a valid task. + * + * Ensures: + * + *\li Events in the event queue of 'task' whose sender is 'sender', whose + * type is 'type', and whose tag is 'tag' will be purged, unless they + * are marked as unpurgable. + * + *\li A sender of NULL will match any sender. A NULL tag matches any + * tag. + * + * Returns: + * + *\li The number of events purged. + */ + +bool +isc_task_purgeevent(isc_task_t *task, isc_event_t *event); +/*%< + * Purge 'event' from a task's event queue. + * + * XXXRTH: WARNING: This method may be removed before beta. + * + * Notes: + * + *\li If 'event' is on the task's event queue, it will be purged, + * unless it is marked as unpurgeable. 'event' does not have to be + * on the task's event queue; in fact, it can even be an invalid + * pointer. Purging only occurs if the event is actually on the task's + * event queue. + * + * \li Purging never changes the state of the task. + * + * Requires: + * + *\li 'task' is a valid task. + * + * Ensures: + * + *\li 'event' is not in the event queue for 'task'. + * + * Returns: + * + *\li #true The event was purged. + *\li #false The event was not in the event queue, + * or was marked unpurgeable. + */ + +unsigned int +isc_task_unsendrange(isc_task_t *task, void *sender, isc_eventtype_t first, + isc_eventtype_t last, void *tag, isc_eventlist_t *events); +/*%< + * Remove events from a task's event queue. + * + * Requires: + * + *\li 'task' is a valid task. + * + *\li last >= first. + * + *\li *events is a valid list. + * + * Ensures: + * + *\li Events in the event queue of 'task' whose sender is 'sender', whose + * type is >= first and <= last, and whose tag is 'tag' will be dequeued + * and appended to *events. + * + *\li A sender of NULL will match any sender. A NULL tag matches any + * tag. + * + * Returns: + * + *\li The number of events unsent. + */ + +unsigned int +isc_task_unsend(isc_task_t *task, void *sender, isc_eventtype_t type, void *tag, + isc_eventlist_t *events); +/*%< + * Remove events from a task's event queue. + * + * Notes: + * + *\li This function is equivalent to + * + *\code + * isc_task_unsendrange(task, sender, type, type, tag, events); + *\endcode + * + * Requires: + * + *\li 'task' is a valid task. + * + *\li *events is a valid list. + * + * Ensures: + * + *\li Events in the event queue of 'task' whose sender is 'sender', whose + * type is 'type', and whose tag is 'tag' will be dequeued and appended + * to *events. + * + * Returns: + * + *\li The number of events unsent. + */ + +isc_result_t +isc_task_onshutdown(isc_task_t *task, isc_taskaction_t action, void *arg); +/*%< + * Send a shutdown event with action 'action' and argument 'arg' when + * 'task' is shutdown. + * + * Notes: + * + *\li Shutdown events are posted in LIFO order. + * + * Requires: + * + *\li 'task' is a valid task. + * + *\li 'action' is a valid task action. + * + * Ensures: + * + *\li When the task is shutdown, shutdown events requested with + * isc_task_onshutdown() will be appended to the task's event queue. + * + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + *\li #ISC_R_SHUTTINGDOWN Task is shutting down. + */ + +void +isc_task_shutdown(isc_task_t *task); +/*%< + * Shutdown 'task'. + * + * Notes: + * + *\li Shutting down a task causes any shutdown events requested with + * isc_task_onshutdown() to be posted (in LIFO order). The task + * moves into a "shutting down" mode which prevents further calls + * to isc_task_onshutdown(). + * + *\li Trying to shutdown a task that has already been shutdown has no + * effect. + * + * Requires: + * + *\li 'task' is a valid task. + * + * Ensures: + * + *\li Any shutdown events requested with isc_task_onshutdown() have been + * posted (in LIFO order). + */ + +void +isc_task_destroy(isc_task_t **taskp); +/*%< + * Destroy '*taskp'. + * + * Notes: + * + *\li This call is equivalent to: + * + *\code + * isc_task_shutdown(*taskp); + * isc_task_detach(taskp); + *\endcode + * + * Requires: + * + * '*taskp' is a valid task. + * + * Ensures: + * + *\li Any shutdown events requested with isc_task_onshutdown() have been + * posted (in LIFO order). + * + *\li *taskp == NULL + * + *\li If '*taskp' is the last reference to the task, + * all resources used by the task will be freed. + */ + +void +isc_task_setname(isc_task_t *task, const char *name, void *tag); +/*%< + * Name 'task'. + * + * Notes: + * + *\li Only the first 15 characters of 'name' will be copied. + * + *\li Naming a task is currently only useful for debugging purposes. + * + * Requires: + * + *\li 'task' is a valid task. + */ + +const char * +isc_task_getname(isc_task_t *task); +/*%< + * Get the name of 'task', as previously set using isc_task_setname(). + * + * Notes: + *\li This function is for debugging purposes only. + * + * Requires: + *\li 'task' is a valid task. + * + * Returns: + *\li A non-NULL pointer to a null-terminated string. + * If the task has not been named, the string is + * empty. + * + */ + +isc_nm_t * +isc_task_getnetmgr(isc_task_t *task); + +void * +isc_task_gettag(isc_task_t *task); +/*%< + * Get the tag value for 'task', as previously set using isc_task_settag(). + * + * Notes: + *\li This function is for debugging purposes only. + * + * Requires: + *\li 'task' is a valid task. + */ + +void +isc_task_setquantum(isc_task_t *task, unsigned int quantum); +/*%< + * Set future 'task' quantum to 'quantum'. The current 'task' quantum will be + * kept for the current isc_task_run() loop, and will be changed for the next + * run. Therefore, the function is safe to use from the event callback as it + * will not affect the current event loop processing. + */ + +isc_result_t +isc_task_beginexclusive(isc_task_t *task); +/*%< + * Request exclusive access for 'task', which must be the calling + * task. Waits for any other concurrently executing tasks to finish their + * current event, and prevents any new events from executing in any of the + * tasks sharing a task manager with 'task'. + * It also pauses processing of network events in netmgr if it was provided + * when taskmgr was created. + * + * The exclusive access must be relinquished by calling + * isc_task_endexclusive() before returning from the current event handler. + * + * Requires: + *\li 'task' is the calling task. + * + * Returns: + *\li #ISC_R_SUCCESS The current task now has exclusive access. + *\li #ISC_R_LOCKBUSY Another task has already requested exclusive + * access. + */ + +void +isc_task_endexclusive(isc_task_t *task); +/*%< + * Relinquish the exclusive access obtained by isc_task_beginexclusive(), + * allowing other tasks to execute. + * + * Requires: + *\li 'task' is the calling task, and has obtained + * exclusive access by calling isc_task_spl(). + */ + +void +isc_task_pause(isc_task_t *task0); +void +isc_task_unpause(isc_task_t *task0); +/*%< + * Pause/unpause this task. Pausing a task removes it from the ready + * queue if it is present there; this ensures that the task will not + * run again until unpaused. This is necessary when the libuv network + * thread executes a function which schedules task manager events; this + * prevents the task manager from executing the next event in a task + * before the network thread has finished. + * + * Requires: + *\li 'task' is a valid task, and is not already paused or shutting down. + */ + +void +isc_task_getcurrenttime(isc_task_t *task, isc_stdtime_t *t); +void +isc_task_getcurrenttimex(isc_task_t *task, isc_time_t *t); +/*%< + * Provide the most recent timestamp on the task. The timestamp is considered + * as the "current time". + * + * isc_task_getcurrentime() returns the time in one-second granularity; + * isc_task_getcurrentimex() returns it in nanosecond granularity. + * + * Requires: + *\li 'task' is a valid task. + *\li 't' is a valid non NULL pointer. + * + * Ensures: + *\li '*t' has the "current time". + */ + +bool +isc_task_exiting(isc_task_t *t); +/*%< + * Returns true if the task is in the process of shutting down, + * false otherwise. + * + * Requires: + *\li 'task' is a valid task. + */ + +void +isc_task_setprivilege(isc_task_t *task, bool priv); +/*%< + * Set or unset the task's "privileged" flag depending on the value of + * 'priv'. + * + * Under normal circumstances this flag has no effect on the task behavior, + * but when the task manager has been set to privileged execution mode via + * isc_taskmgr_setmode(), only tasks with the flag set will be executed, + * and all other tasks will wait until they're done. Once all privileged + * tasks have finished executing, the task manager resumes running + * non-privileged tasks. + * + * Requires: + *\li 'task' is a valid task. + */ + +bool +isc_task_getprivilege(isc_task_t *task); +/*%< + * Returns the current value of the task's privilege flag. + * + * Requires: + *\li 'task' is a valid task. + */ + +bool +isc_task_privileged(isc_task_t *task); +/*%< + * Returns true if the task's privilege flag is set *and* the task + * manager is currently in privileged mode. + * + * Requires: + *\li 'task' is a valid task. + */ + +/***** + ***** Task Manager. + *****/ + +void +isc_taskmgr_attach(isc_taskmgr_t *, isc_taskmgr_t **); +void +isc_taskmgr_detach(isc_taskmgr_t **); +/*%< + * Attach/detach the task manager. + */ + +void +isc_taskmgr_setmode(isc_taskmgr_t *manager, isc_taskmgrmode_t mode); +isc_taskmgrmode_t +isc_taskmgr_mode(isc_taskmgr_t *manager); +/*%< + * Set/get the operating mode of the task manager. Valid modes are: + * + *\li isc_taskmgrmode_normal + *\li isc_taskmgrmode_privileged + * + * In privileged execution mode, only tasks that have had the "privilege" + * flag set via isc_task_setprivilege() can be executed. When all such + * tasks are complete, non-privileged tasks resume running. The task calling + * this function should be in task-exclusive mode; the privileged tasks + * will be run after isc_task_endexclusive() is called. + * + * Requires: + * + *\li 'manager' is a valid task manager. + */ + +void +isc_taskmgr_setexcltask(isc_taskmgr_t *mgr, isc_task_t *task); +/*%< + * Set a task which will be used for all task-exclusive operations. + * + * Requires: + *\li 'manager' is a valid task manager. + * + *\li 'task' is a valid task. + */ + +isc_result_t +isc_taskmgr_excltask(isc_taskmgr_t *mgr, isc_task_t **taskp); +/*%< + * Attach '*taskp' to the task set by isc_taskmgr_getexcltask(). + * This task should be used whenever running in task-exclusive mode, + * so as to prevent deadlock between two exclusive tasks. + * + * Requires: + *\li 'manager' is a valid task manager. + * + *\li taskp != NULL && *taskp == NULL + */ + +#ifdef HAVE_LIBXML2 +int +isc_taskmgr_renderxml(isc_taskmgr_t *mgr, void *writer0); +#endif /* ifdef HAVE_LIBXML2 */ + +#ifdef HAVE_JSON_C +isc_result_t +isc_taskmgr_renderjson(isc_taskmgr_t *mgr, void *tasksobj0); +#endif /* HAVE_JSON_C */ + +ISC_LANG_ENDDECLS diff --git a/lib/isc/include/isc/taskpool.h b/lib/isc/include/isc/taskpool.h new file mode 100644 index 0000000..ea4c569 --- /dev/null +++ b/lib/isc/include/isc/taskpool.h @@ -0,0 +1,138 @@ +/* + * 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. + */ + +#ifndef ISC_TASKPOOL_H +#define ISC_TASKPOOL_H 1 + +/***** +***** Module Info +*****/ + +/*! \file isc/taskpool.h + * \brief A task pool is a mechanism for sharing a small number of tasks + * among a large number of objects such that each object is + * assigned a unique task, but each task may be shared by several + * objects. + * + * Task pools are used to let objects that can exist in large + * numbers (e.g., zones) use tasks for synchronization without + * the memory overhead and unfair scheduling competition that + * could result from creating a separate task for each object. + */ + +/*** + *** Imports. + ***/ + +#include <stdbool.h> + +#include <isc/lang.h> +#include <isc/task.h> + +ISC_LANG_BEGINDECLS + +/***** +***** Types. +*****/ + +typedef struct isc_taskpool isc_taskpool_t; + +/***** +***** Functions. +*****/ + +isc_result_t +isc_taskpool_create(isc_taskmgr_t *tmgr, isc_mem_t *mctx, unsigned int ntasks, + unsigned int quantum, bool priv, isc_taskpool_t **poolp); +/*%< + * Create a task pool of "ntasks" tasks, each with quantum + * "quantum". + * + * Requires: + * + *\li 'tmgr' is a valid task manager. + * + *\li 'mctx' is a valid memory context. + * + *\li poolp != NULL && *poolp == NULL + * + * Ensures: + * + *\li On success, '*taskp' points to the new task pool. + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + *\li #ISC_R_UNEXPECTED + */ + +void +isc_taskpool_gettask(isc_taskpool_t *pool, isc_task_t **targetp); +/*%< + * Attach to a task from the pool. Currently the next task is chosen + * from the pool at random. (This may be changed in the future to + * something that guaratees balance.) + */ + +int +isc_taskpool_size(isc_taskpool_t *pool); +/*%< + * Returns the number of tasks in the task pool 'pool'. + */ + +isc_result_t +isc_taskpool_expand(isc_taskpool_t **sourcep, unsigned int size, bool priv, + isc_taskpool_t **targetp); + +/*%< + * If 'size' is larger than the number of tasks in the pool pointed to by + * 'sourcep', then a new taskpool of size 'size' is allocated, the existing + * tasks from are moved into it, additional tasks are created to bring the + * total number up to 'size', and the resulting pool is attached to + * 'targetp'. + * + * If 'size' is less than or equal to the tasks in pool 'source', then + * 'sourcep' is attached to 'targetp' without any other action being taken. + * + * In either case, 'sourcep' is detached. + * + * Requires: + * + * \li 'sourcep' is not NULL and '*source' is not NULL + * \li 'targetp' is not NULL and '*source' is NULL + * + * Ensures: + * + * \li On success, '*targetp' points to a valid task pool. + * \li On success, '*sourcep' points to NULL. + * + * Returns: + * + * \li #ISC_R_SUCCESS + * \li #ISC_R_NOMEMORY + */ + +void +isc_taskpool_destroy(isc_taskpool_t **poolp); +/*%< + * Destroy a task pool. The tasks in the pool are detached but not + * shut down. + * + * Requires: + * \li '*poolp' is a valid task pool. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_TASKPOOL_H */ diff --git a/lib/isc/include/isc/timer.h b/lib/isc/include/isc/timer.h new file mode 100644 index 0000000..4814a65 --- /dev/null +++ b/lib/isc/include/isc/timer.h @@ -0,0 +1,323 @@ +/* + * 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. + */ + +#ifndef ISC_TIMER_H +#define ISC_TIMER_H 1 + +/***** +***** Module Info +*****/ + +/*! \file isc/timer.h + * \brief Provides timers which are event sources in the task system. + * + * Three types of timers are supported: + * + *\li 'ticker' timers generate a periodic tick event. + * + *\li 'once' timers generate an idle timeout event if they are idle for too + * long, and generate a life timeout event if their lifetime expires. + * They are used to implement both (possibly expiring) idle timers and + * 'one-shot' timers. + * + *\li 'limited' timers generate a periodic tick event until they reach + * their lifetime when they generate a life timeout event. + * + *\li 'inactive' timers generate no events. + * + * Timers can change type. It is typical to create a timer as + * an 'inactive' timer and then change it into a 'ticker' or + * 'once' timer. + * + *\li MP: + * The module ensures appropriate synchronization of data structures it + * creates and manipulates. + * Clients of this module must not be holding a timer's task's lock when + * making a call that affects that timer. Failure to follow this rule + * can result in deadlock. + * The caller must ensure that isc_timermgr_destroy() is called only + * once for a given manager. + * + * \li Reliability: + * No anticipated impact. + * + * \li Resources: + * TBS + * + * \li Security: + * No anticipated impact. + * + * \li Standards: + * None. + */ + +/*** + *** Imports + ***/ + +#include <stdbool.h> + +#include <isc/event.h> +#include <isc/eventclass.h> +#include <isc/lang.h> +#include <isc/time.h> +#include <isc/types.h> + +ISC_LANG_BEGINDECLS + +/*** + *** Types + ***/ + +/*% Timer Type */ +typedef enum { + isc_timertype_undefined = -1, /*%< Undefined */ + isc_timertype_ticker = 0, /*%< Ticker */ + isc_timertype_once = 1, /*%< Once */ + isc_timertype_limited = 2, /*%< Limited */ + isc_timertype_inactive = 3 /*%< Inactive */ +} isc_timertype_t; + +typedef struct isc_timerevent isc_timerevent_t; + +struct isc_timerevent { + struct isc_event common; + isc_time_t due; + ISC_LINK(isc_timerevent_t) ev_timerlink; +}; + +#define ISC_TIMEREVENT_FIRSTEVENT (ISC_EVENTCLASS_TIMER + 0) +#define ISC_TIMEREVENT_TICK (ISC_EVENTCLASS_TIMER + 1) +#define ISC_TIMEREVENT_IDLE (ISC_EVENTCLASS_TIMER + 2) +#define ISC_TIMEREVENT_LIFE (ISC_EVENTCLASS_TIMER + 3) +#define ISC_TIMEREVENT_LASTEVENT (ISC_EVENTCLASS_TIMER + 65535) + +/*** + *** Timer and Timer Manager Functions + *** + *** Note: all Ensures conditions apply only if the result is success for + *** those functions which return an isc_result_t. + ***/ + +isc_result_t +isc_timer_create(isc_timermgr_t *manager, isc_timertype_t type, + const isc_time_t *expires, const isc_interval_t *interval, + isc_task_t *task, isc_taskaction_t action, void *arg, + isc_timer_t **timerp); +/*%< + * Create a new 'type' timer managed by 'manager'. The timers parameters + * are specified by 'expires' and 'interval'. Events will be posted to + * 'task' and when dispatched 'action' will be called with 'arg' as the + * arg value. The new timer is returned in 'timerp'. + * + * Notes: + * + *\li For ticker timers, the timer will generate a 'tick' event every + * 'interval' seconds. The value of 'expires' is ignored. + * + *\li For once timers, 'expires' specifies the time when a life timeout + * event should be generated. If 'expires' is 0 (the epoch), then no life + * timeout will be generated. 'interval' specifies how long the timer + * can be idle before it generates an idle timeout. If 0, then no + * idle timeout will be generated. + * + *\li If 'expires' is NULL, the epoch will be used. + * + * If 'interval' is NULL, the zero interval will be used. + * + * Requires: + * + *\li 'manager' is a valid manager + * + *\li 'task' is a valid task + * + *\li 'action' is a valid action + * + *\li 'expires' points to a valid time, or is NULL. + * + *\li 'interval' points to a valid interval, or is NULL. + * + *\li type == isc_timertype_inactive || + * ('expires' and 'interval' are not both 0) + * + *\li 'timerp' is a valid pointer, and *timerp == NULL + * + * Ensures: + * + *\li '*timerp' is attached to the newly created timer + * + *\li The timer is attached to the task + * + *\li An idle timeout will not be generated until at least Now + the + * timer's interval if 'timer' is a once timer with a non-zero + * interval. + * + * Returns: + * + *\li Success + *\li No memory + *\li Unexpected error + */ + +isc_result_t +isc_timer_reset(isc_timer_t *timer, isc_timertype_t type, + const isc_time_t *expires, const isc_interval_t *interval, + bool purge); +/*%< + * Change the timer's type, expires, and interval values to the given + * values. If 'purge' is TRUE, any pending events from this timer + * are purged from its task's event queue. + * + * Notes: + * + *\li If 'expires' is NULL, the epoch will be used. + * + *\li If 'interval' is NULL, the zero interval will be used. + * + * Requires: + * + *\li 'timer' is a valid timer + * + *\li The same requirements that isc_timer_create() imposes on 'type', + * 'expires' and 'interval' apply. + * + * Ensures: + * + *\li An idle timeout will not be generated until at least Now + the + * timer's interval if 'timer' is a once timer with a non-zero + * interval. + * + * Returns: + * + *\li Success + *\li No memory + *\li Unexpected error + */ + +isc_result_t +isc_timer_touch(isc_timer_t *timer); +/*%< + * Set the last-touched time of 'timer' to the current time. + * + * Requires: + * + *\li 'timer' is a valid once timer. + * + * Ensures: + * + *\li An idle timeout will not be generated until at least Now + the + * timer's interval if 'timer' is a once timer with a non-zero + * interval. + * + * Returns: + * + *\li Success + *\li Unexpected error + */ + +void +isc_timer_destroy(isc_timer_t **timerp); +/*%< + * Destroy *timerp. + * + * Requires: + * + *\li 'timerp' points to a valid timer. + * + * Ensures: + * + *\li *timerp is NULL. + * + *\code + * The timer will be shutdown + * + * The timer will detach from its task + * + * All resources used by the timer have been freed + * + * Any events already posted by the timer will be purged. + * Therefore, if isc_timer_destroy() is called in the context + * of the timer's task, it is guaranteed that no more + * timer event callbacks will run after the call. + * + * If this function is called from the timer event callback + * the event itself must be destroyed before the timer + * itself. + *\endcode + */ + +isc_timertype_t +isc_timer_gettype(isc_timer_t *timer); +/*%< + * Return the timer type. + * + * Requires: + * + *\li 'timer' to be a valid timer. + */ + +isc_result_t +isc_timermgr_create(isc_mem_t *mctx, isc_timermgr_t **managerp); +/*%< + * Create a timer manager. + * + * Notes: + * + *\li All memory will be allocated in memory context 'mctx'. + * + * Requires: + * + *\li 'mctx' is a valid memory context. + * + *\li 'managerp' points to a NULL isc_timermgr_t. + * + * Ensures: + * + *\li '*managerp' is a valid isc_timermgr_t. + * + * Returns: + * + *\li Success + *\li No memory + *\li Unexpected error + */ + +void +isc_timermgr_destroy(isc_timermgr_t **managerp); +/*%< + * Destroy a timer manager. + * + * Notes: + * + *\li This routine blocks until there are no timers left in the manager, + * so if the caller holds any timer references using the manager, it + * must detach them before calling isc_timermgr_destroy() or it will + * block forever. + * + * Requires: + * + *\li '*managerp' is a valid isc_timermgr_t. + * + * Ensures: + * + *\li *managerp == NULL + * + *\li All resources used by the manager have been freed. + */ + +void +isc_timermgr_poke(isc_timermgr_t *m); + +ISC_LANG_ENDDECLS + +#endif /* ISC_TIMER_H */ diff --git a/lib/isc/include/isc/tm.h b/lib/isc/include/isc/tm.h new file mode 100644 index 0000000..dd13448 --- /dev/null +++ b/lib/isc/include/isc/tm.h @@ -0,0 +1,42 @@ +/* + * 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. + */ + +#ifndef ISC_TM_H +#define ISC_TM_H 1 + +/*! \file isc/tm.h + * Provides portable conversion routines for struct tm. + */ +#include <time.h> + +#include <isc/lang.h> +#include <isc/types.h> + +ISC_LANG_BEGINDECLS + +time_t +isc_tm_timegm(struct tm *tm); +/* + * Convert a tm structure to time_t, using UTC rather than the local + * time zone. + */ + +char * +isc_tm_strptime(const char *buf, const char *fmt, struct tm *tm); +/* + * Parse a formatted date string into struct tm. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_TIMER_H */ diff --git a/lib/isc/include/isc/types.h b/lib/isc/include/isc/types.h new file mode 100644 index 0000000..60e7114 --- /dev/null +++ b/lib/isc/include/isc/types.h @@ -0,0 +1,131 @@ +/* + * 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. + */ + +#ifndef ISC_TYPES_H +#define ISC_TYPES_H 1 + +#include <isc/bind9.h> + +/*! \file isc/types.h + * \brief + * OS-specific types, from the OS-specific include directories. + */ +#include <inttypes.h> +#include <stdbool.h> + +#include <isc/offset.h> + +/* + * XXXDCL This is just for ISC_LIST and ISC_LINK, but gets all of the other + * list macros too. + */ +#include <isc/list.h> + +/* Core Types. Alphabetized by defined type. */ + +typedef struct isc_astack isc_astack_t; /*%< Array-based fast stack */ +typedef struct isc_appctx isc_appctx_t; /*%< Application context */ +typedef struct isc_backtrace_symmap isc_backtrace_symmap_t; /*%< Symbol Table + * Entry */ +typedef struct isc_buffer isc_buffer_t; /*%< Buffer */ +typedef ISC_LIST(isc_buffer_t) isc_bufferlist_t; /*%< Buffer List */ +typedef struct isc_constregion isc_constregion_t; /*%< Const region */ +typedef struct isc_consttextregion isc_consttextregion_t; /*%< Const Text Region + */ +typedef struct isc_counter isc_counter_t; /*%< Counter */ +typedef int16_t isc_dscp_t; /*%< Diffserv code point */ +typedef struct isc_event isc_event_t; /*%< Event */ +typedef ISC_LIST(isc_event_t) isc_eventlist_t; /*%< Event List */ +typedef unsigned int isc_eventtype_t; /*%< Event Type */ +typedef uint32_t isc_fsaccess_t; /*%< FS Access */ +typedef struct isc_hash isc_hash_t; /*%< Hash */ +typedef struct isc_httpd isc_httpd_t; /*%< HTTP client */ +typedef void(isc_httpdfree_t)(isc_buffer_t *, void *); /*%< HTTP free function + */ +typedef struct isc_httpdmgr isc_httpdmgr_t; /*%< HTTP manager */ +typedef struct isc_httpdurl isc_httpdurl_t; /*%< HTTP URL */ +typedef void(isc_httpdondestroy_t)(void *); /*%< Callback on destroying httpd */ +typedef struct isc_interface isc_interface_t; /*%< Interface */ +typedef struct isc_interfaceiter isc_interfaceiter_t; /*%< Interface Iterator */ +typedef struct isc_interval isc_interval_t; /*%< Interval */ +typedef struct isc_lex isc_lex_t; /*%< Lex */ +typedef struct isc_log isc_log_t; /*%< Log */ +typedef struct isc_logcategory isc_logcategory_t; /*%< Log Category */ +typedef struct isc_logconfig isc_logconfig_t; /*%< Log Configuration */ +typedef struct isc_logmodule isc_logmodule_t; /*%< Log Module */ +typedef struct isc_mem isc_mem_t; /*%< Memory */ +typedef struct isc_mempool isc_mempool_t; /*%< Memory Pool */ +typedef struct isc_netaddr isc_netaddr_t; /*%< Net Address */ +typedef struct isc_nm isc_nm_t; /*%< Network manager */ +typedef struct isc_nmsocket isc_nmsocket_t; /*%< Network manager socket */ +typedef struct isc_nmhandle isc_nmhandle_t; /*%< Network manager handle */ +typedef struct isc_portset isc_portset_t; /*%< Port Set */ +typedef struct isc_quota isc_quota_t; /*%< Quota */ +typedef struct isc_ratelimiter isc_ratelimiter_t; /*%< Rate Limiter */ +typedef struct isc_region isc_region_t; /*%< Region */ +typedef uint64_t isc_resourcevalue_t; /*%< Resource Value */ +typedef unsigned int isc_result_t; /*%< Result */ +typedef struct isc_rwlock isc_rwlock_t; /*%< Read Write Lock */ +typedef struct isc_sockaddr isc_sockaddr_t; /*%< Socket Address */ +typedef ISC_LIST(isc_sockaddr_t) isc_sockaddrlist_t; /*%< Socket Address List + * */ +typedef struct isc_socket isc_socket_t; /*%< Socket */ +typedef struct isc_socketevent isc_socketevent_t; /*%< Socket Event */ +typedef struct isc_socketmgr isc_socketmgr_t; /*%< Socket Manager */ +typedef struct isc_stats isc_stats_t; /*%< Statistics */ +#if defined(_WIN32) && !defined(_WIN64) +typedef int_fast32_t isc_statscounter_t; /*%< Statistics Counter */ +#else /* if defined(_WIN32) && !defined(_WIN64) */ +typedef int_fast64_t isc_statscounter_t; +#endif /* if defined(_WIN32) && !defined(_WIN64) */ +typedef struct isc_symtab isc_symtab_t; /*%< Symbol Table */ +typedef struct isc_task isc_task_t; /*%< Task */ +typedef ISC_LIST(isc_task_t) isc_tasklist_t; /*%< Task List */ +typedef struct isc_taskmgr isc_taskmgr_t; /*%< Task Manager */ +typedef struct isc_textregion isc_textregion_t; /*%< Text Region */ +typedef struct isc_time isc_time_t; /*%< Time */ +typedef struct isc_timer isc_timer_t; /*%< Timer */ +typedef struct isc_timermgr isc_timermgr_t; /*%< Timer Manager */ + +typedef void (*isc_taskaction_t)(isc_task_t *, isc_event_t *); +typedef int (*isc_sockfdwatch_t)(isc_task_t *, isc_socket_t *, void *, int); + +/* The following cannot be listed alphabetically due to forward reference */ +typedef isc_result_t(isc_httpdaction_t)( + const char *url, isc_httpdurl_t *urlinfo, const char *querystring, + const char *headers, void *arg, unsigned int *retcode, + const char **retmsg, const char **mimetype, isc_buffer_t *body, + isc_httpdfree_t **freecb, void **freecb_args); +typedef bool(isc_httpdclientok_t)(const isc_sockaddr_t *, void *); + +/*% Resource */ +typedef enum { + isc_resource_coresize = 1, + isc_resource_cputime, + isc_resource_datasize, + isc_resource_filesize, + isc_resource_lockedmemory, + isc_resource_openfiles, + isc_resource_processes, + isc_resource_residentsize, + isc_resource_stacksize +} isc_resource_t; + +/*% Statistics formats (text file or XML) */ +typedef enum { + isc_statsformat_file, + isc_statsformat_xml, + isc_statsformat_json +} isc_statsformat_t; + +#endif /* ISC_TYPES_H */ diff --git a/lib/isc/include/isc/url.h b/lib/isc/include/isc/url.h new file mode 100644 index 0000000..4894a45 --- /dev/null +++ b/lib/isc/include/isc/url.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 and MIT + * + * 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. + */ + +/* + * Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <stdbool.h> +#include <stdint.h> + +#include <isc/result.h> + +/* + * Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run + * faster + */ +#ifndef HTTP_PARSER_STRICT +#define HTTP_PARSER_STRICT 1 +#endif + +typedef enum { + ISC_UF_SCHEMA = 0, + ISC_UF_HOST = 1, + ISC_UF_PORT = 2, + ISC_UF_PATH = 3, + ISC_UF_QUERY = 4, + ISC_UF_FRAGMENT = 5, + ISC_UF_USERINFO = 6, + ISC_UF_MAX = 7 +} isc_url_field_t; + +/* Result structure for isc_url_parse(). + * + * Callers should index into field_data[] with UF_* values iff field_set + * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and + * because we probably have padding left over), we convert any port to + * a uint16_t. + */ +typedef struct { + uint16_t field_set; /* Bitmask of (1 << UF_*) values */ + uint16_t port; /* Converted UF_PORT string */ + + struct { + uint16_t off; /* Offset into buffer in which field starts */ + uint16_t len; /* Length of run in buffer */ + } field_data[ISC_UF_MAX]; +} isc_url_parser_t; + +isc_result_t +isc_url_parse(const char *buf, size_t buflen, bool is_connect, + isc_url_parser_t *up); +/*%< + * Parse a URL; return nonzero on failure + */ diff --git a/lib/isc/include/isc/utf8.h b/lib/isc/include/isc/utf8.h new file mode 100644 index 0000000..5643c5d --- /dev/null +++ b/lib/isc/include/isc/utf8.h @@ -0,0 +1,43 @@ +/* + * 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. + */ + +/*! \file isc/utf8.h */ + +#pragma once + +#include <isc/lang.h> +#include <isc/types.h> + +ISC_LANG_BEGINDECLS + +bool +isc_utf8_bom(const unsigned char *buf, size_t len); +/*< + * Returns 'true' if the string of bytes in 'buf' starts + * with an UTF-8 Byte Order Mark. + * + * Requires: + *\li 'buf' != NULL + */ + +bool +isc_utf8_valid(const unsigned char *buf, size_t len); +/*< + * Returns 'true' if the string of bytes in 'buf' is a valid UTF-8 + * byte sequence otherwise 'false' is returned. + * + * Requires: + *\li 'buf' != NULL + */ + +ISC_LANG_ENDDECLS diff --git a/lib/isc/include/isc/util.h b/lib/isc/include/isc/util.h new file mode 100644 index 0000000..3396559 --- /dev/null +++ b/lib/isc/include/isc/util.h @@ -0,0 +1,445 @@ +/* + * 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. + */ + +#ifndef ISC_UTIL_H +#define ISC_UTIL_H 1 + +#include <inttypes.h> + +/*! \file isc/util.h + * NOTE: + * + * This file is not to be included from any <isc/???.h> (or other) library + * files. + * + * \brief + * Including this file puts several macros in your name space that are + * not protected (as all the other ISC functions/macros do) by prepending + * ISC_ or isc_ to the name. + */ + +/*** + *** Clang Compatibility Macros + ***/ + +#if !defined(__has_attribute) +#define __has_attribute(x) 0 +#endif /* if !defined(__has_attribute) */ + +#if !defined(__has_c_attribute) +#define __has_c_attribute(x) 0 +#endif /* if !defined(__has_c_attribute) */ + +#if !defined(__has_feature) +#define __has_feature(x) 0 +#endif /* if !defined(__has_feature) */ + +/*** + *** General Macros. + ***/ + +/*% + * Use this to hide unused function arguments. + * \code + * int + * foo(char *bar) + * { + * UNUSED(bar); + * } + * \endcode + */ +#define UNUSED(x) (void)(x) + +#if __GNUC__ >= 8 && !defined(__clang__) +#define ISC_NONSTRING __attribute__((nonstring)) +#else /* if __GNUC__ >= 8 && !defined(__clang__) */ +#define ISC_NONSTRING +#endif /* __GNUC__ */ + +#if __has_c_attribute(fallthrough) +#define FALLTHROUGH [[fallthrough]] +#elif __GNUC__ >= 7 && !defined(__clang__) +#define FALLTHROUGH __attribute__((fallthrough)) +#else +/* clang-format off */ +#define FALLTHROUGH do {} while (0) /* FALLTHROUGH */ +/* clang-format on */ +#endif + +#if HAVE_FUNC_ATTRIBUTE_CONSTRUCTOR && HAVE_FUNC_ATTRIBUTE_DESTRUCTOR +#define ISC_CONSTRUCTOR __attribute__((constructor)) +#define ISC_DESTRUCTOR __attribute__((destructor)) +#elif WIN32 +#define ISC_CONSTRUCTOR +#define ISC_DESTRUCTOR +#endif + +/*% + * The opposite: silent warnings about stored values which are never read. + */ +#define POST(x) (void)(x) + +#define ISC_MAX(a, b) ((a) > (b) ? (a) : (b)) +#define ISC_MIN(a, b) ((a) < (b) ? (a) : (b)) + +#define ISC_CLAMP(v, x, y) ((v) < (x) ? (x) : ((v) > (y) ? (y) : (v))) + +/*% + * Use this to remove the const qualifier of a variable to assign it to + * a non-const variable or pass it as a non-const function argument ... + * but only when you are sure it won't then be changed! + * This is necessary to sometimes shut up some compilers + * (as with gcc -Wcast-qual) when there is just no other good way to avoid the + * situation. + */ +#define DE_CONST(konst, var) \ + do { \ + union { \ + const void *k; \ + void *v; \ + } _u; \ + _u.k = konst; \ + var = _u.v; \ + } while (0) + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) + +/*% + * Use this in translation units that would otherwise be empty, to + * suppress compiler warnings. + */ +#define EMPTY_TRANSLATION_UNIT extern int isc__empty; + +/*% + * We use macros instead of calling the routines directly because + * the capital letters make the locking stand out. + * We RUNTIME_CHECK for success since in general there's no way + * for us to continue if they fail. + */ + +#ifdef ISC_UTIL_TRACEON +#define ISC_UTIL_TRACE(a) a +#include <stdio.h> /* Required for fprintf/stderr when tracing. */ +#else /* ifdef ISC_UTIL_TRACEON */ +#define ISC_UTIL_TRACE(a) +#endif /* ifdef ISC_UTIL_TRACEON */ + +#include <isc/result.h> /* Contractual promise. */ + +#define LOCK(lp) \ + do { \ + ISC_UTIL_TRACE(fprintf(stderr, "LOCKING %p %s %d\n", (lp), \ + __FILE__, __LINE__)); \ + RUNTIME_CHECK(isc_mutex_lock((lp)) == ISC_R_SUCCESS); \ + ISC_UTIL_TRACE(fprintf(stderr, "LOCKED %p %s %d\n", (lp), \ + __FILE__, __LINE__)); \ + } while (0) +#define UNLOCK(lp) \ + do { \ + RUNTIME_CHECK(isc_mutex_unlock((lp)) == ISC_R_SUCCESS); \ + ISC_UTIL_TRACE(fprintf(stderr, "UNLOCKED %p %s %d\n", (lp), \ + __FILE__, __LINE__)); \ + } while (0) + +#define BROADCAST(cvp) \ + do { \ + ISC_UTIL_TRACE(fprintf(stderr, "BROADCAST %p %s %d\n", (cvp), \ + __FILE__, __LINE__)); \ + RUNTIME_CHECK(isc_condition_broadcast((cvp)) == \ + ISC_R_SUCCESS); \ + } while (0) +#define SIGNAL(cvp) \ + do { \ + ISC_UTIL_TRACE(fprintf(stderr, "SIGNAL %p %s %d\n", (cvp), \ + __FILE__, __LINE__)); \ + RUNTIME_CHECK(isc_condition_signal((cvp)) == ISC_R_SUCCESS); \ + } while (0) +#define WAIT(cvp, lp) \ + do { \ + ISC_UTIL_TRACE(fprintf(stderr, "WAIT %p LOCK %p %s %d\n", \ + (cvp), (lp), __FILE__, __LINE__)); \ + RUNTIME_CHECK(isc_condition_wait((cvp), (lp)) == \ + ISC_R_SUCCESS); \ + ISC_UTIL_TRACE(fprintf(stderr, "WAITED %p LOCKED %p %s %d\n", \ + (cvp), (lp), __FILE__, __LINE__)); \ + } while (0) + +/* + * isc_condition_waituntil can return ISC_R_TIMEDOUT, so we + * don't RUNTIME_CHECK the result. + * + * XXX Also, can't really debug this then... + */ + +#define WAITUNTIL(cvp, lp, tp) isc_condition_waituntil((cvp), (lp), (tp)) + +#define RWLOCK(lp, t) \ + do { \ + ISC_UTIL_TRACE(fprintf(stderr, "RWLOCK %p, %d %s %d\n", (lp), \ + (t), __FILE__, __LINE__)); \ + RUNTIME_CHECK(isc_rwlock_lock((lp), (t)) == ISC_R_SUCCESS); \ + ISC_UTIL_TRACE(fprintf(stderr, "RWLOCKED %p, %d %s %d\n", \ + (lp), (t), __FILE__, __LINE__)); \ + } while (0) +#define RWUNLOCK(lp, t) \ + do { \ + ISC_UTIL_TRACE(fprintf(stderr, "RWUNLOCK %p, %d %s %d\n", \ + (lp), (t), __FILE__, __LINE__)); \ + RUNTIME_CHECK(isc_rwlock_unlock((lp), (t)) == ISC_R_SUCCESS); \ + } while (0) + +/* + * List Macros. + */ +#include <isc/list.h> /* Contractual promise. */ + +#define LIST(type) ISC_LIST(type) +#define INIT_LIST(type) ISC_LIST_INIT(type) +#define LINK(type) ISC_LINK(type) +#define INIT_LINK(elt, link) ISC_LINK_INIT(elt, link) +#define HEAD(list) ISC_LIST_HEAD(list) +#define TAIL(list) ISC_LIST_TAIL(list) +#define EMPTY(list) ISC_LIST_EMPTY(list) +#define PREV(elt, link) ISC_LIST_PREV(elt, link) +#define NEXT(elt, link) ISC_LIST_NEXT(elt, link) +#define APPEND(list, elt, link) ISC_LIST_APPEND(list, elt, link) +#define PREPEND(list, elt, link) ISC_LIST_PREPEND(list, elt, link) +#define UNLINK(list, elt, link) ISC_LIST_UNLINK(list, elt, link) +#define ENQUEUE(list, elt, link) ISC_LIST_APPEND(list, elt, link) +#define DEQUEUE(list, elt, link) ISC_LIST_UNLINK(list, elt, link) +#define INSERTBEFORE(li, b, e, ln) ISC_LIST_INSERTBEFORE(li, b, e, ln) +#define INSERTAFTER(li, a, e, ln) ISC_LIST_INSERTAFTER(li, a, e, ln) +#define APPENDLIST(list1, list2, link) ISC_LIST_APPENDLIST(list1, list2, link) + +/*% + * Performance + */ +#include <isc/likely.h> + +/* GCC defines __SANITIZE_ADDRESS__, so reuse the macro for clang */ +#if __has_feature(address_sanitizer) +#define __SANITIZE_ADDRESS__ 1 +#endif /* if __has_feature(address_sanitizer) */ + +#if __has_feature(thread_sanitizer) +#define __SANITIZE_THREAD__ 1 +#endif /* if __has_feature(thread_sanitizer) */ + +#if __SANITIZE_THREAD__ +#define ISC_NO_SANITIZE_THREAD __attribute__((no_sanitize("thread"))) +#else /* if __SANITIZE_THREAD__ */ +#define ISC_NO_SANITIZE_THREAD +#endif /* if __SANITIZE_THREAD__ */ + +#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR >= 6) +#define STATIC_ASSERT(cond, msg) _Static_assert(cond, msg) +#elif __has_feature(c_static_assert) +#define STATIC_ASSERT(cond, msg) _Static_assert(cond, msg) +#else /* if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR >= 6) */ +#define STATIC_ASSERT(cond, msg) INSIST(cond) +#endif /* if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR >= 6) */ + +#ifdef UNIT_TESTING +extern void +mock_assert(const int result, const char *const expression, + const char *const file, const int line); +/* + * Allow clang to determine that the following code is not reached + * by calling abort() if the condition fails. The abort() will + * never be executed as mock_assert() and _assert_true() longjmp + * or exit if the condition is false. + */ +#define REQUIRE(expression) \ + ((!(expression)) \ + ? (mock_assert(0, #expression, __FILE__, __LINE__), abort()) \ + : (void)0) +#define ENSURE(expression) \ + ((!(int)(expression)) \ + ? (mock_assert(0, #expression, __FILE__, __LINE__), abort()) \ + : (void)0) +#define INSIST(expression) \ + ((!(expression)) \ + ? (mock_assert(0, #expression, __FILE__, __LINE__), abort()) \ + : (void)0) +#define INVARIANT(expression) \ + ((!(expression)) \ + ? (mock_assert(0, #expression, __FILE__, __LINE__), abort()) \ + : (void)0) +#define UNREACHABLE() \ + (mock_assert(0, "unreachable", __FILE__, __LINE__), abort()) +#define _assert_true(c, e, f, l) \ + ((c) ? (void)0 : (_assert_true(0, e, f, l), abort())) +#define _assert_int_equal(a, b, f, l) \ + (((a) == (b)) ? (void)0 : (_assert_int_equal(a, b, f, l), abort())) +#define _assert_int_not_equal(a, b, f, l) \ + (((a) != (b)) ? (void)0 : (_assert_int_not_equal(a, b, f, l), abort())) +#else /* UNIT_TESTING */ + +#ifndef CPPCHECK + +/* + * Assertions + */ +#include <isc/assertions.h> /* Contractual promise. */ + +/*% Require Assertion */ +#define REQUIRE(e) ISC_REQUIRE(e) +/*% Ensure Assertion */ +#define ENSURE(e) ISC_ENSURE(e) +/*% Insist Assertion */ +#define INSIST(e) ISC_INSIST(e) +/*% Invariant Assertion */ +#define INVARIANT(e) ISC_INVARIANT(e) + +#define UNREACHABLE() ISC_UNREACHABLE() + +#else /* CPPCHECK */ + +/*% Require Assertion */ +#define REQUIRE(e) \ + if (!(e)) \ + abort() +/*% Ensure Assertion */ +#define ENSURE(e) \ + if (!(e)) \ + abort() +/*% Insist Assertion */ +#define INSIST(e) \ + if (!(e)) \ + abort() +/*% Invariant Assertion */ +#define INVARIANT(e) \ + if (!(e)) \ + abort() + +#define UNREACHABLE() abort() + +#endif /* CPPCHECK */ + +#endif /* UNIT_TESTING */ + +/* + * Errors + */ +#include <isc/error.h> /* Contractual promise. */ + +/*% Unexpected Error */ +#define UNEXPECTED_ERROR isc_error_unexpected +/*% Fatal Error */ +#define FATAL_ERROR isc_error_fatal + +#ifdef UNIT_TESTING + +#define RUNTIME_CHECK(expression) \ + ((!(expression)) \ + ? (mock_assert(0, #expression, __FILE__, __LINE__), abort()) \ + : (void)0) + +#else /* UNIT_TESTING */ + +#ifndef CPPCHECK +/*% Runtime Check */ +#define RUNTIME_CHECK(cond) ISC_ERROR_RUNTIMECHECK(cond) +#else /* ifndef CPPCHECK */ +#define RUNTIME_CHECK(e) \ + if (!(e)) \ + abort() +#endif /* ifndef CPPCHECK */ + +#endif /* UNIT_TESTING */ + +/*% + * Time + */ +#define TIME_NOW(tp) RUNTIME_CHECK(isc_time_now((tp)) == ISC_R_SUCCESS) +#define TIME_NOW_HIRES(tp) \ + RUNTIME_CHECK(isc_time_now_hires((tp)) == ISC_R_SUCCESS) + +/*% + * Alignment + */ +#ifdef __GNUC__ +#define ISC_ALIGN(x, a) (((x) + (a)-1) & ~((typeof(x))(a)-1)) +#else /* ifdef __GNUC__ */ +#define ISC_ALIGN(x, a) (((x) + (a)-1) & ~((uintmax_t)(a)-1)) +#endif /* ifdef __GNUC__ */ + +#if HAVE_FUNC_ATTRIBUTE_RETURNS_NONNULL +#define ISC_ATTR_RETURNS_NONNULL __attribute__((returns_nonnull)) +#else +#define ISC_ATTR_RETURNS_NONNULL +#endif + +/* + * Support for malloc attributes. + */ +#ifdef HAVE_FUNC_ATTRIBUTE_MALLOC +/* + * Indicates that a function is malloc-like, i.e., that the + * pointer P returned by the function cannot alias any other + * pointer valid when the function returns. + */ +#define ISC_ATTR_MALLOC __attribute__((malloc)) +#if HAVE_MALLOC_EXT_ATTR +/* + * Associates deallocator as a suitable deallocation function + * for pointers returned from the function marked with the attribute. + */ +#define ISC_ATTR_DEALLOCATOR(deallocator) __attribute__((malloc(deallocator))) +/* + * Similar to ISC_ATTR_DEALLOCATOR, but allows to speficy an index "idx", + * which denotes the positional argument to which when the pointer is passed + * in calls to deallocator has the effect of deallocating it. + */ +#define ISC_ATTR_DEALLOCATOR_IDX(deallocator, idx) \ + __attribute__((malloc(deallocator, idx))) +/* + * Combines both ISC_ATTR_MALLOC and ISC_ATTR_DEALLOCATOR attributes. + */ +#define ISC_ATTR_MALLOC_DEALLOCATOR(deallocator) \ + __attribute__((malloc, malloc(deallocator))) +/* + * Similar to ISC_ATTR_MALLOC_DEALLOCATOR, but allows to speficy an index "idx", + * which denotes the positional argument to which when the pointer is passed + * in calls to deallocator has the effect of deallocating it. + */ +#define ISC_ATTR_MALLOC_DEALLOCATOR_IDX(deallocator, idx) \ + __attribute__((malloc, malloc(deallocator, idx))) +#else /* #ifdef HAVE_MALLOC_EXT_ATTR */ +/* + * There is support for malloc attribute but not for + * extended attributes, so macros that combine attribute malloc + * with a deallocator will only expand to malloc attribute. + */ +#define ISC_ATTR_DEALLOCATOR(deallocator) +#define ISC_ATTR_DEALLOCATOR_IDX(deallocator, idx) +#define ISC_ATTR_MALLOC_DEALLOCATOR(deallocator) ISC_ATTR_MALLOC +#define ISC_ATTR_MALLOC_DEALLOCATOR_IDX(deallocator, idx) ISC_ATTR_MALLOC +#endif +#else /* #ifdef HAVE_FUNC_ATTRIBUTE_MALLOC */ +/* + * There is no support for malloc attribute. + */ +#define ISC_ATTR_MALLOC +#define ISC_ATTR_DEALLOCATOR(deallocator) +#define ISC_ATTR_DEALLOCATOR_IDX(deallocator, idx) +#define ISC_ATTR_MALLOC_DEALLOCATOR(deallocator) +#define ISC_ATTR_MALLOC_DEALLOCATOR_IDX(deallocator, idx) +#endif /* HAVE_FUNC_ATTRIBUTE_MALLOC */ + +/*% + * Misc + */ +#include <isc/deprecated.h> + +#endif /* ISC_UTIL_H */ diff --git a/lib/isc/include/isc/version.h b/lib/isc/include/isc/version.h new file mode 100644 index 0000000..4b81e44 --- /dev/null +++ b/lib/isc/include/isc/version.h @@ -0,0 +1,18 @@ +/* + * 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. + */ + +/*! \file isc/version.h */ + +#include <isc/platform.h> + +LIBISC_EXTERNAL_DATA extern const char isc_version[]; diff --git a/lib/isc/include/pk11/Makefile.in b/lib/isc/include/pk11/Makefile.in new file mode 100644 index 0000000..3ba0c1f --- /dev/null +++ b/lib/isc/include/pk11/Makefile.in @@ -0,0 +1,40 @@ +# 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. + +srcdir = @srcdir@ +VPATH = @srcdir@ +top_srcdir = @top_srcdir@ + +VERSION=@BIND9_VERSION@ + +# +# Only list headers that are to be installed and are not +# machine generated. The latter are handled specially in the +# install target below. +# +HEADERS = constants.h internal.h pk11.h result.h site.h +SUBDIRS = +TARGETS = + +@BIND9_MAKE_RULES@ + +installdirs: + $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${includedir}/pk11 + +install:: installdirs + for i in ${HEADERS}; do \ + ${INSTALL_DATA} ${srcdir}/$$i ${DESTDIR}${includedir}/pk11 || exit 1; \ + done + +uninstall:: + for i in ${HEADERS}; do \ + rm -f ${DESTDIR}${includedir}/pk11/$$i || exit 1; \ + done diff --git a/lib/isc/include/pk11/constants.h b/lib/isc/include/pk11/constants.h new file mode 100644 index 0000000..c682053 --- /dev/null +++ b/lib/isc/include/pk11/constants.h @@ -0,0 +1,37 @@ +/* + * 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. + */ + +#pragma once + +#include <inttypes.h> + +/*! \file pk11/constants.h */ + +/*% + * Static arrays of data used for key template initialization + */ +#define PK11_ECC_PRIME256V1 \ + (uint8_t[]) { \ + 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07 \ + } +#define PK11_ECC_SECP384R1 \ + (uint8_t[]) { 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22 } +#define PK11_ECX_ED25519 \ + (uint8_t[]) { \ + 0x13, 0xc, 'e', 'd', 'w', 'a', 'r', 'd', 's', '2', '5', '5', \ + '1', '9' \ + } +#define PK11_ECX_ED448 \ + (uint8_t[]) { \ + 0x13, 0xa, 'e', 'd', 'w', 'a', 'r', 'd', 's', '4', '4', '8' \ + } diff --git a/lib/isc/include/pk11/internal.h b/lib/isc/include/pk11/internal.h new file mode 100644 index 0000000..38bd751 --- /dev/null +++ b/lib/isc/include/pk11/internal.h @@ -0,0 +1,49 @@ +/* + * 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. + */ + +#ifndef PK11_INTERNAL_H +#define PK11_INTERNAL_H 1 + +/*! \file pk11/internal.h */ + +#include <pk11/pk11.h> + +ISC_LANG_BEGINDECLS + +const char * +pk11_get_lib_name(void); + +void * +pk11_mem_get(size_t size); + +void +pk11_mem_put(void *ptr, size_t size); + +CK_SLOT_ID +pk11_get_best_token(pk11_optype_t optype); + +isc_result_t +pk11_numbits(CK_BYTE_PTR data, unsigned int bytecnt, unsigned int *bits); + +CK_ATTRIBUTE * +pk11_attribute_first(const pk11_object_t *obj); + +CK_ATTRIBUTE * +pk11_attribute_next(const pk11_object_t *obj, CK_ATTRIBUTE *attr); + +CK_ATTRIBUTE * +pk11_attribute_bytype(const pk11_object_t *obj, CK_ATTRIBUTE_TYPE type); + +ISC_LANG_ENDDECLS + +#endif /* PK11_INTERNAL_H */ diff --git a/lib/isc/include/pk11/pk11.h b/lib/isc/include/pk11/pk11.h new file mode 100644 index 0000000..076c119 --- /dev/null +++ b/lib/isc/include/pk11/pk11.h @@ -0,0 +1,290 @@ +/* + * 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. + */ + +#ifndef PK11_PK11_H +#define PK11_PK11_H 1 + +/*! \file pk11/pk11.h */ + +#include <stdbool.h> +#include <unistd.h> + +#include <isc/lang.h> +#include <isc/magic.h> +#include <isc/types.h> + +#define PK11_FATALCHECK(func, args) \ + ((void)(((rv = (func)args) == CKR_OK) || \ + ((pk11_error_fatalcheck)(__FILE__, __LINE__, #func, rv), 0))) + +#include <pk11/site.h> +#include <pkcs11/pkcs11.h> + +ISC_LANG_BEGINDECLS + +#define SES_MAGIC ISC_MAGIC('P', 'K', 'S', 'S') +#define TOK_MAGIC ISC_MAGIC('P', 'K', 'T', 'K') + +#define VALID_SES(x) ISC_MAGIC_VALID(x, SES_MAGIC) +#define VALID_TOK(x) ISC_MAGIC_VALID(x, TOK_MAGIC) + +typedef struct pk11_context pk11_context_t; + +struct pk11_object { + CK_OBJECT_HANDLE object; + CK_SLOT_ID slot; + CK_BBOOL ontoken; + CK_BBOOL reqlogon; + CK_BYTE attrcnt; + CK_ATTRIBUTE *repr; +}; + +struct pk11_context { + void *handle; + CK_SESSION_HANDLE session; + CK_BBOOL ontoken; + CK_OBJECT_HANDLE object; +}; + +typedef struct pk11_object pk11_object_t; + +typedef enum { + OP_ANY = 0, + OP_RSA = 1, + OP_DH = 3, + OP_ECDSA = 4, + OP_EDDSA = 5, + OP_MAX = 6 +} pk11_optype_t; + +/*% + * Global flag to make choose_slots() verbose + */ +LIBISC_EXTERNAL_DATA extern bool pk11_verbose_init; + +/*% + * Function prototypes + */ + +void +pk11_set_lib_name(const char *lib_name); +/*%< + * Set the PKCS#11 provider (aka library) path/name. + */ + +isc_result_t +pk11_initialize(isc_mem_t *mctx, const char *engine); +/*%< + * Initialize PKCS#11 device + * + * mctx: memory context to attach to pk11_mctx. + * engine: PKCS#11 provider (aka library) path/name. + * + * returns: + * ISC_R_SUCCESS + * PK11_R_NOPROVIDER: can't load the provider + * PK11_R_INITFAILED: C_Initialize() failed + * PK11_R_NORANDOMSERVICE: can't find required random service + * PK11_R_NODIGESTSERVICE: can't find required digest service + * PK11_R_NOAESSERVICE: can't find required AES service + */ + +isc_result_t +pk11_get_session(pk11_context_t *ctx, pk11_optype_t optype, bool need_services, + bool rw, bool logon, const char *pin, CK_SLOT_ID slot); +/*%< + * Initialize PKCS#11 device and acquire a session. + * + * need_services: + * if true, this session requires full PKCS#11 API + * support including random and digest services, and + * the lack of these services will cause the session not + * to be initialized. If false, the function will return + * an error code indicating the missing service, but the + * session will be usable for other purposes. + * rw: if true, session will be read/write (useful for + * generating or destroying keys); otherwise read-only. + * login: indicates whether to log in to the device + * pin: optional PIN, overriding any PIN currently associated + * with the + * slot: device slot ID + */ + +void +pk11_return_session(pk11_context_t *ctx); +/*%< + * Release an active PKCS#11 session for reuse. + */ + +isc_result_t +pk11_finalize(void); +/*%< + * Shut down PKCS#11 device and free all sessions. + */ + +isc_result_t +pk11_parse_uri(pk11_object_t *obj, const char *label, isc_mem_t *mctx, + pk11_optype_t optype); + +ISC_PLATFORM_NORETURN_PRE void +pk11_error_fatalcheck(const char *file, int line, const char *funcname, + CK_RV rv) ISC_PLATFORM_NORETURN_POST; + +void +pk11_dump_tokens(void); + +CK_RV +pkcs_C_Initialize(CK_VOID_PTR pReserved); + +char * +pk11_get_load_error_message(void); + +CK_RV +pkcs_C_Finalize(CK_VOID_PTR pReserved); + +CK_RV +pkcs_C_GetSlotList(CK_BBOOL tokenPresent, CK_SLOT_ID_PTR pSlotList, + CK_ULONG_PTR pulCount); + +CK_RV +pkcs_C_GetTokenInfo(CK_SLOT_ID slotID, CK_TOKEN_INFO_PTR pInfo); + +CK_RV +pkcs_C_GetMechanismInfo(CK_SLOT_ID slotID, CK_MECHANISM_TYPE type, + CK_MECHANISM_INFO_PTR pInfo); + +CK_RV +pkcs_C_OpenSession(CK_SLOT_ID slotID, CK_FLAGS flags, CK_VOID_PTR pApplication, + CK_RV (*Notify)(CK_SESSION_HANDLE hSession, + CK_NOTIFICATION event, + CK_VOID_PTR pApplication), + CK_SESSION_HANDLE_PTR phSession); + +CK_RV +pkcs_C_CloseSession(CK_SESSION_HANDLE hSession); + +CK_RV +pkcs_C_Login(CK_SESSION_HANDLE hSession, CK_USER_TYPE userType, + CK_CHAR_PTR pPin, CK_ULONG usPinLen); + +CK_RV +pkcs_C_Logout(CK_SESSION_HANDLE hSession); + +CK_RV +pkcs_C_CreateObject(CK_SESSION_HANDLE hSession, CK_ATTRIBUTE_PTR pTemplate, + CK_ULONG usCount, CK_OBJECT_HANDLE_PTR phObject); + +CK_RV +pkcs_C_DestroyObject(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject); + +CK_RV +pkcs_C_GetAttributeValue(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject, + CK_ATTRIBUTE_PTR pTemplate, CK_ULONG usCount); + +CK_RV +pkcs_C_SetAttributeValue(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject, + CK_ATTRIBUTE_PTR pTemplate, CK_ULONG usCount); + +CK_RV +pkcs_C_FindObjectsInit(CK_SESSION_HANDLE hSession, CK_ATTRIBUTE_PTR pTemplate, + CK_ULONG usCount); + +CK_RV +pkcs_C_FindObjects(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE_PTR phObject, + CK_ULONG usMaxObjectCount, CK_ULONG_PTR pusObjectCount); + +CK_RV +pkcs_C_FindObjectsFinal(CK_SESSION_HANDLE hSession); + +CK_RV +pkcs_C_EncryptInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, + CK_OBJECT_HANDLE hKey); + +CK_RV +pkcs_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, + CK_ULONG ulDataLen, CK_BYTE_PTR pEncryptedData, + CK_ULONG_PTR pulEncryptedDataLen); + +CK_RV +pkcs_C_DigestInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism); + +CK_RV +pkcs_C_DigestUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, + CK_ULONG ulPartLen); + +CK_RV +pkcs_C_DigestFinal(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pDigest, + CK_ULONG_PTR pulDigestLen); + +CK_RV +pkcs_C_SignInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, + CK_OBJECT_HANDLE hKey); + +CK_RV +pkcs_C_Sign(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, CK_ULONG ulDataLen, + CK_BYTE_PTR pSignature, CK_ULONG_PTR pulSignatureLen); + +CK_RV +pkcs_C_SignUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, + CK_ULONG ulPartLen); + +CK_RV +pkcs_C_SignFinal(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pSignature, + CK_ULONG_PTR pulSignatureLen); + +CK_RV +pkcs_C_VerifyInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, + CK_OBJECT_HANDLE hKey); + +CK_RV +pkcs_C_Verify(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, CK_ULONG ulDataLen, + CK_BYTE_PTR pSignature, CK_ULONG ulSignatureLen); + +CK_RV +pkcs_C_VerifyUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, + CK_ULONG ulPartLen); + +CK_RV +pkcs_C_VerifyFinal(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pSignature, + CK_ULONG ulSignatureLen); + +CK_RV +pkcs_C_GenerateKey(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, + CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount, + CK_OBJECT_HANDLE_PTR phKey); + +CK_RV +pkcs_C_GenerateKeyPair(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, + CK_ATTRIBUTE_PTR pPublicKeyTemplate, + CK_ULONG usPublicKeyAttributeCount, + CK_ATTRIBUTE_PTR pPrivateKeyTemplate, + CK_ULONG usPrivateKeyAttributeCount, + CK_OBJECT_HANDLE_PTR phPrivateKey, + CK_OBJECT_HANDLE_PTR phPublicKey); + +CK_RV +pkcs_C_DeriveKey(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, + CK_OBJECT_HANDLE hBaseKey, CK_ATTRIBUTE_PTR pTemplate, + CK_ULONG ulAttributeCount, CK_OBJECT_HANDLE_PTR phKey); + +CK_RV +pkcs_C_SeedRandom(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pSeed, + CK_ULONG ulSeedLen); + +CK_RV +pkcs_C_GenerateRandom(CK_SESSION_HANDLE hSession, CK_BYTE_PTR RandomData, + CK_ULONG ulRandomLen); + +ISC_LANG_ENDDECLS + +#endif /* PK11_PK11_H */ diff --git a/lib/isc/include/pk11/result.h b/lib/isc/include/pk11/result.h new file mode 100644 index 0000000..d92b15d --- /dev/null +++ b/lib/isc/include/pk11/result.h @@ -0,0 +1,48 @@ +/* + * 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. + */ + +#ifndef PK11_RESULT_H +#define PK11_RESULT_H 1 + +/*! \file pk11/result.h */ + +#include <isc/lang.h> +#include <isc/resultclass.h> +#include <isc/types.h> + +/* + * Nothing in this file truly depends on <isc/result.h>, but the + * PK11 result codes are considered to be publicly derived from + * the ISC result codes, so including this file buys you the ISC_R_ + * namespace too. + */ +#include <isc/result.h> /* Contractual promise. */ + +#define PK11_R_INITFAILED (ISC_RESULTCLASS_PK11 + 0) +#define PK11_R_NOPROVIDER (ISC_RESULTCLASS_PK11 + 1) +#define PK11_R_NORANDOMSERVICE (ISC_RESULTCLASS_PK11 + 2) +#define PK11_R_NODIGESTSERVICE (ISC_RESULTCLASS_PK11 + 3) +#define PK11_R_NOAESSERVICE (ISC_RESULTCLASS_PK11 + 4) + +#define PK11_R_NRESULTS 5 /* Number of results */ + +ISC_LANG_BEGINDECLS + +const char *pk11_result_totext(isc_result_t); + +void +pk11_result_register(void); + +ISC_LANG_ENDDECLS + +#endif /* PK11_RESULT_H */ diff --git a/lib/isc/include/pk11/site.h b/lib/isc/include/pk11/site.h new file mode 100644 index 0000000..ceebb59 --- /dev/null +++ b/lib/isc/include/pk11/site.h @@ -0,0 +1,16 @@ +/* + * 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. + */ + +#pragma once + +/*! \file pk11/site.h */ diff --git a/lib/isc/include/pkcs11/Makefile.in b/lib/isc/include/pkcs11/Makefile.in new file mode 100644 index 0000000..8822f5a --- /dev/null +++ b/lib/isc/include/pkcs11/Makefile.in @@ -0,0 +1,40 @@ +# 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. + +srcdir = @srcdir@ +VPATH = @srcdir@ +top_srcdir = @top_srcdir@ + +VERSION=@BIND9_VERSION@ + +# +# Only list headers that are to be installed and are not +# machine generated. The latter are handled specially in the +# install target below. +# +HEADERS = pkcs11.h +SUBDIRS = +TARGETS = + +@BIND9_MAKE_RULES@ + +installdirs: + $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${includedir}/pkcs11 + +install:: installdirs + for i in ${HEADERS}; do \ + ${INSTALL_DATA} ${srcdir}/$$i ${DESTDIR}${includedir}/pkcs11 || exit 1; \ + done + +uninstall:: + for i in ${HEADERS}; do \ + rm -f ${DESTDIR}${includedir}/pkcs11/$$i || exit 1; \ + done diff --git a/lib/isc/include/pkcs11/pkcs11.h b/lib/isc/include/pkcs11/pkcs11.h new file mode 100644 index 0000000..30228d2 --- /dev/null +++ b/lib/isc/include/pkcs11/pkcs11.h @@ -0,0 +1,1655 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * Copyright 2006 Andreas Jellinghaus + * Copyright 2006, 2007 g10 Code GmbH + * Copyright 2017 Red Hat, Inc. + * + * 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. + */ + +/* Please submit any changes back to the p11-kit project at + * https://github.com/p11-glue/p11-kit/, so that + * they can be picked up by other projects from there as well. */ + +/* This file is a modified implementation of the PKCS #11 standard by + * OASIS group. It is mostly a drop-in replacement, with the + * following change: + * + * This header file does not require any macro definitions by the user + * (like CK_DEFINE_FUNCTION etc). In fact, it defines those macros + * for you (if useful, some are missing, let me know if you need + * more). + * + * There is an additional API available that does comply better to the + * GNU coding standard. It can be switched on by defining + * CRYPTOKI_GNU before including this header file. For this, the + * following changes are made to the specification: + * + * All structure types are changed to a "struct ck_foo" where CK_FOO + * is the type name in PKCS #11. + * + * All non-structure types are changed to ck_foo_t where CK_FOO is the + * lowercase version of the type name in PKCS #11. The basic types + * (CK_ULONG et al.) are removed without substitute. + * + * All members of structures are modified in the following way: Type + * indication prefixes are removed, and underscore characters are + * inserted before words. Then the result is lowercased. + * + * Note that function names are still in the original case, as they + * need for ABI compatibility. + * + * CK_FALSE, CK_TRUE and NULL_PTR are removed without substitute. Use + * <stdbool.h>. + * + * If CRYPTOKI_COMPAT is defined before including this header file, + * then none of the API changes above take place, and the API is the + * one defined by the PKCS #11 standard. */ + +#ifndef PKCS11_H +#define PKCS11_H 1 + +#if defined(__cplusplus) +extern "C" { +#endif /* if defined(__cplusplus) */ + +/* The version of cryptoki we implement. The revision is changed with + * each modification of this file. */ +#define CRYPTOKI_VERSION_MAJOR 2 +#define CRYPTOKI_VERSION_MINOR 40 +#define P11_KIT_CRYPTOKI_VERSION_REVISION 0 + +/* Compatibility interface is default, unless CRYPTOKI_GNU is + * given. */ +#ifndef CRYPTOKI_GNU +#ifndef CRYPTOKI_COMPAT +#define CRYPTOKI_COMPAT 1 +#endif /* ifndef CRYPTOKI_COMPAT */ +#endif /* ifndef CRYPTOKI_GNU */ + +/* System dependencies. */ + +#if defined(_WIN32) || defined(CRYPTOKI_FORCE_WIN32) + +/* There is a matching pop below. */ +#pragma pack(push, cryptoki, 1) + +#ifdef CRYPTOKI_EXPORTS +#define CK_SPEC __declspec(dllexport) +#else /* ifdef CRYPTOKI_EXPORTS */ +#define CK_SPEC __declspec(dllimport) +#endif /* ifdef CRYPTOKI_EXPORTS */ + +#else /* if defined(_WIN32) || defined(CRYPTOKI_FORCE_WIN32) */ + +#define CK_SPEC + +#endif /* if defined(_WIN32) || defined(CRYPTOKI_FORCE_WIN32) */ + +#ifdef CRYPTOKI_COMPAT +/* If we are in compatibility mode, switch all exposed names to the + * PKCS #11 variant. There are corresponding #undefs below. */ + +#define ck_flags_t CK_FLAGS +#define ck_version _CK_VERSION + +#define ck_info _CK_INFO +#define cryptoki_version cryptokiVersion +#define manufacturer_id manufacturerID +#define library_description libraryDescription +#define library_version libraryVersion + +#define ck_notification_t CK_NOTIFICATION +#define ck_slot_id_t CK_SLOT_ID + +#define ck_slot_info _CK_SLOT_INFO +#define slot_description slotDescription +#define hardware_version hardwareVersion +#define firmware_version firmwareVersion + +#define ck_token_info _CK_TOKEN_INFO +#define serial_number serialNumber +#define max_session_count ulMaxSessionCount +#define session_count ulSessionCount +#define max_rw_session_count ulMaxRwSessionCount +#define rw_session_count ulRwSessionCount +#define max_pin_len ulMaxPinLen +#define min_pin_len ulMinPinLen +#define total_public_memory ulTotalPublicMemory +#define free_public_memory ulFreePublicMemory +#define total_private_memory ulTotalPrivateMemory +#define free_private_memory ulFreePrivateMemory +#define utc_time utcTime + +#define ck_session_handle_t CK_SESSION_HANDLE +#define ck_user_type_t CK_USER_TYPE +#define ck_state_t CK_STATE + +#define ck_session_info _CK_SESSION_INFO +#define slot_id slotID +#define device_error ulDeviceError + +#define ck_object_handle_t CK_OBJECT_HANDLE +#define ck_object_class_t CK_OBJECT_CLASS +#define ck_hw_feature_type_t CK_HW_FEATURE_TYPE +#define ck_key_type_t CK_KEY_TYPE +#define ck_certificate_type_t CK_CERTIFICATE_TYPE +#define ck_attribute_type_t CK_ATTRIBUTE_TYPE + +#define ck_attribute _CK_ATTRIBUTE +#define value pValue +#define value_len ulValueLen + +#define count ulCount + +#define ck_date _CK_DATE + +#define ck_mechanism_type_t CK_MECHANISM_TYPE + +#define ck_mechanism _CK_MECHANISM +#define parameter pParameter +#define parameter_len ulParameterLen + +#define params pParams + +#define ck_mechanism_info _CK_MECHANISM_INFO +#define min_key_size ulMinKeySize +#define max_key_size ulMaxKeySize + +#define ck_param_type CK_PARAM_TYPE +#define ck_otp_param CK_OTP_PARAM +#define ck_otp_params CK_OTP_PARAMS +#define ck_otp_signature_info CK_OTP_SIGNATURE_INFO + +#define ck_rv_t CK_RV +#define ck_notify_t CK_NOTIFY + +#define ck_function_list _CK_FUNCTION_LIST + +#define ck_createmutex_t CK_CREATEMUTEX +#define ck_destroymutex_t CK_DESTROYMUTEX +#define ck_lockmutex_t CK_LOCKMUTEX +#define ck_unlockmutex_t CK_UNLOCKMUTEX + +#define ck_c_initialize_args _CK_C_INITIALIZE_ARGS +#define create_mutex CreateMutex +#define destroy_mutex DestroyMutex +#define lock_mutex LockMutex +#define unlock_mutex UnlockMutex +#define reserved pReserved + +#define ck_rsa_pkcs_mgf_type_t CK_RSA_PKCS_MGF_TYPE +#define ck_rsa_pkcs_oaep_source_type_t CK_RSA_PKCS_OAEP_SOURCE_TYPE +#define hash_alg hashAlg +#define s_len sLen +#define source_data pSourceData +#define source_data_len ulSourceDataLen + +#define counter_bits ulCounterBits +#define iv_ptr pIv +#define iv_len ulIvLen +#define iv_bits ulIvBits +#define aad_ptr pAAD +#define aad_len ulAADLen +#define tag_bits ulTagBits +#define shared_data_len ulSharedDataLen +#define shared_data pSharedData +#define public_data_len ulPublicDataLen +#define public_data pPublicData +#define string_data pData +#define string_data_len ulLen +#define data_params pData +#endif /* CRYPTOKI_COMPAT */ + +typedef unsigned long ck_flags_t; + +struct ck_version { + unsigned char major; + unsigned char minor; +}; + +struct ck_info { + struct ck_version cryptoki_version; + unsigned char manufacturer_id[32]; + ck_flags_t flags; + unsigned char library_description[32]; + struct ck_version library_version; +}; + +typedef unsigned long ck_notification_t; + +#define CKN_SURRENDER (0UL) + +typedef unsigned long ck_slot_id_t; + +struct ck_slot_info { + unsigned char slot_description[64]; + unsigned char manufacturer_id[32]; + ck_flags_t flags; + struct ck_version hardware_version; + struct ck_version firmware_version; +}; + +#define CKF_TOKEN_PRESENT (1UL << 0) +#define CKF_REMOVABLE_DEVICE (1UL << 1) +#define CKF_HW_SLOT (1UL << 2) +#define CKF_ARRAY_ATTRIBUTE (1UL << 30) + +struct ck_token_info { + unsigned char label[32]; + unsigned char manufacturer_id[32]; + unsigned char model[16]; + unsigned char serial_number[16]; + ck_flags_t flags; + unsigned long max_session_count; + unsigned long session_count; + unsigned long max_rw_session_count; + unsigned long rw_session_count; + unsigned long max_pin_len; + unsigned long min_pin_len; + unsigned long total_public_memory; + unsigned long free_public_memory; + unsigned long total_private_memory; + unsigned long free_private_memory; + struct ck_version hardware_version; + struct ck_version firmware_version; + unsigned char utc_time[16]; +}; + +#define CKF_RNG (1UL << 0) +#define CKF_WRITE_PROTECTED (1UL << 1) +#define CKF_LOGIN_REQUIRED (1UL << 2) +#define CKF_USER_PIN_INITIALIZED (1UL << 3) +#define CKF_RESTORE_KEY_NOT_NEEDED (1UL << 5) +#define CKF_CLOCK_ON_TOKEN (1UL << 6) +#define CKF_PROTECTED_AUTHENTICATION_PATH (1UL << 8) +#define CKF_DUAL_CRYPTO_OPERATIONS (1UL << 9) +#define CKF_TOKEN_INITIALIZED (1UL << 10) +#define CKF_SECONDARY_AUTHENTICATION (1UL << 11) +#define CKF_USER_PIN_COUNT_LOW (1UL << 16) +#define CKF_USER_PIN_FINAL_TRY (1UL << 17) +#define CKF_USER_PIN_LOCKED (1UL << 18) +#define CKF_USER_PIN_TO_BE_CHANGED (1UL << 19) +#define CKF_SO_PIN_COUNT_LOW (1UL << 20) +#define CKF_SO_PIN_FINAL_TRY (1UL << 21) +#define CKF_SO_PIN_LOCKED (1UL << 22) +#define CKF_SO_PIN_TO_BE_CHANGED (1UL << 23) + +#define CK_UNAVAILABLE_INFORMATION ((unsigned long)-1L) +#define CK_EFFECTIVELY_INFINITE (0UL) + +typedef unsigned long ck_session_handle_t; + +#define CK_INVALID_HANDLE (0UL) + +typedef unsigned long ck_user_type_t; + +#define CKU_SO (0UL) +#define CKU_USER (1UL) +#define CKU_CONTEXT_SPECIFIC (2UL) + +typedef unsigned long ck_state_t; + +#define CKS_RO_PUBLIC_SESSION (0UL) +#define CKS_RO_USER_FUNCTIONS (1UL) +#define CKS_RW_PUBLIC_SESSION (2UL) +#define CKS_RW_USER_FUNCTIONS (3UL) +#define CKS_RW_SO_FUNCTIONS (4UL) + +struct ck_session_info { + ck_slot_id_t slot_id; + ck_state_t state; + ck_flags_t flags; + unsigned long device_error; +}; + +#define CKF_RW_SESSION (1UL << 1) +#define CKF_SERIAL_SESSION (1UL << 2) + +typedef unsigned long ck_object_handle_t; + +typedef unsigned long ck_object_class_t; + +#define CKO_DATA (0UL) +#define CKO_CERTIFICATE (1UL) +#define CKO_PUBLIC_KEY (2UL) +#define CKO_PRIVATE_KEY (3UL) +#define CKO_SECRET_KEY (4UL) +#define CKO_HW_FEATURE (5UL) +#define CKO_DOMAIN_PARAMETERS (6UL) +#define CKO_MECHANISM (7UL) +#define CKO_OTP_KEY (8UL) +#define CKO_VENDOR_DEFINED ((unsigned long)(1UL << 31)) + +typedef unsigned long ck_hw_feature_type_t; + +#define CKH_MONOTONIC_COUNTER (1UL) +#define CKH_CLOCK (2UL) +#define CKH_USER_INTERFACE (3UL) +#define CKH_VENDOR_DEFINED ((unsigned long)(1UL << 31)) + +typedef unsigned long ck_key_type_t; + +#define CKK_RSA (0UL) +#define CKK_DSA (1UL) +#define CKK_DH (2UL) +#define CKK_ECDSA (3UL) +#define CKK_EC (3UL) +#define CKK_X9_42_DH (4UL) +#define CKK_KEA (5UL) +#define CKK_GENERIC_SECRET (0x10UL) +#define CKK_RC2 (0x11UL) +#define CKK_RC4 (0x12UL) +#define CKK_DES (0x13UL) +#define CKK_DES2 (0x14UL) +#define CKK_DES3 (0x15UL) +#define CKK_CAST (0x16UL) +#define CKK_CAST3 (0x17UL) +#define CKK_CAST128 (0x18UL) +#define CKK_RC5 (0x19UL) +#define CKK_IDEA (0x1aUL) +#define CKK_SKIPJACK (0x1bUL) +#define CKK_BATON (0x1cUL) +#define CKK_JUNIPER (0x1dUL) +#define CKK_CDMF (0x1eUL) +#define CKK_AES (0x1fUL) +#define CKK_BLOWFISH (0x20UL) +#define CKK_TWOFISH (0x21UL) +#define CKK_SECURID (0x22UL) +#define CKK_HOTP (0x23UL) +#define CKK_ACTI (0x24UL) +#define CKK_CAMELLIA (0x25UL) +#define CKK_ARIA (0x26UL) +#define CKK_MD5_HMAC (0x27UL) +#define CKK_SHA_1_HMAC (0x28UL) +#define CKK_RIPEMD128_HMAC (0x29UL) +#define CKK_RIPEMD160_HMAC (0x2aUL) +#define CKK_SHA256_HMAC (0x2bUL) +#define CKK_SHA384_HMAC (0x2cUL) +#define CKK_SHA512_HMAC (0x2dUL) +#define CKK_SHA224_HMAC (0x2eUL) +#define CKK_SEED (0x2fUL) +#define CKK_GOSTR3410 (0x30UL) +#define CKK_GOSTR3411 (0x31UL) +#define CKK_GOST28147 (0x32UL) +#define CKK_EC_EDWARDS (0x40UL) +#define CKK_VENDOR_DEFINED ((unsigned long)(1UL << 31)) + +typedef unsigned long ck_certificate_type_t; + +#define CKC_X_509 (0UL) +#define CKC_X_509_ATTR_CERT (1UL) +#define CKC_WTLS (2UL) +#define CKC_VENDOR_DEFINED ((unsigned long)(1UL << 31)) + +#define CKC_OPENPGP (CKC_VENDOR_DEFINED | 0x504750UL) + +typedef unsigned long ck_attribute_type_t; + +#define CKA_CLASS (0UL) +#define CKA_TOKEN (1UL) +#define CKA_PRIVATE (2UL) +#define CKA_LABEL (3UL) +#define CKA_APPLICATION (0x10UL) +#define CKA_VALUE (0x11UL) +#define CKA_OBJECT_ID (0x12UL) +#define CKA_CERTIFICATE_TYPE (0x80UL) +#define CKA_ISSUER (0x81UL) +#define CKA_SERIAL_NUMBER (0x82UL) +#define CKA_AC_ISSUER (0x83UL) +#define CKA_OWNER (0x84UL) +#define CKA_ATTR_TYPES (0x85UL) +#define CKA_TRUSTED (0x86UL) +#define CKA_CERTIFICATE_CATEGORY (0x87UL) +#define CKA_JAVA_MIDP_SECURITY_DOMAIN (0x88UL) +#define CKA_URL (0x89UL) +#define CKA_HASH_OF_SUBJECT_PUBLIC_KEY (0x8aUL) +#define CKA_HASH_OF_ISSUER_PUBLIC_KEY (0x8bUL) +#define CKA_NAME_HASH_ALGORITHM (0x8cUL) +#define CKA_CHECK_VALUE (0x90UL) +#define CKA_KEY_TYPE (0x100UL) +#define CKA_SUBJECT (0x101UL) +#define CKA_ID (0x102UL) +#define CKA_SENSITIVE (0x103UL) +#define CKA_ENCRYPT (0x104UL) +#define CKA_DECRYPT (0x105UL) +#define CKA_WRAP (0x106UL) +#define CKA_UNWRAP (0x107UL) +#define CKA_SIGN (0x108UL) +#define CKA_SIGN_RECOVER (0x109UL) +#define CKA_VERIFY (0x10aUL) +#define CKA_VERIFY_RECOVER (0x10bUL) +#define CKA_DERIVE (0x10cUL) +#define CKA_START_DATE (0x110UL) +#define CKA_END_DATE (0x111UL) +#define CKA_MODULUS (0x120UL) +#define CKA_MODULUS_BITS (0x121UL) +#define CKA_PUBLIC_EXPONENT (0x122UL) +#define CKA_PRIVATE_EXPONENT (0x123UL) +#define CKA_PRIME_1 (0x124UL) +#define CKA_PRIME_2 (0x125UL) +#define CKA_EXPONENT_1 (0x126UL) +#define CKA_EXPONENT_2 (0x127UL) +#define CKA_COEFFICIENT (0x128UL) +#define CKA_PUBLIC_KEY_INFO (0x129UL) +#define CKA_PRIME (0x130UL) +#define CKA_SUBPRIME (0x131UL) +#define CKA_BASE (0x132UL) +#define CKA_PRIME_BITS (0x133UL) +#define CKA_SUB_PRIME_BITS (0x134UL) +#define CKA_VALUE_BITS (0x160UL) +#define CKA_VALUE_LEN (0x161UL) +#define CKA_EXTRACTABLE (0x162UL) +#define CKA_LOCAL (0x163UL) +#define CKA_NEVER_EXTRACTABLE (0x164UL) +#define CKA_ALWAYS_SENSITIVE (0x165UL) +#define CKA_KEY_GEN_MECHANISM (0x166UL) +#define CKA_MODIFIABLE (0x170UL) +#define CKA_COPYABLE (0x171UL) +#define CKA_DESTROYABLE (0x172UL) +#define CKA_ECDSA_PARAMS (0x180UL) +#define CKA_EC_PARAMS (0x180UL) +#define CKA_EC_POINT (0x181UL) +#define CKA_SECONDARY_AUTH (0x200UL) +#define CKA_AUTH_PIN_FLAGS (0x201UL) +#define CKA_ALWAYS_AUTHENTICATE (0x202UL) +#define CKA_WRAP_WITH_TRUSTED (0x210UL) +#define CKA_OTP_FORMAT (0x220UL) +#define CKA_OTP_LENGTH (0x221UL) +#define CKA_OTP_TIME_INTERVAL (0x222UL) +#define CKA_OTP_USER_FRIENDLY_MODE (0x223UL) +#define CKA_OTP_CHALLENGE_REQUIREMENT (0x224UL) +#define CKA_OTP_TIME_REQUIREMENT (0x225UL) +#define CKA_OTP_COUNTER_REQUIREMENT (0x226UL) +#define CKA_OTP_PIN_REQUIREMENT (0x227UL) +#define CKA_OTP_USER_IDENTIFIER (0x22AUL) +#define CKA_OTP_SERVICE_IDENTIFIER (0x22BUL) +#define CKA_OTP_SERVICE_LOGO (0x22CUL) +#define CKA_OTP_SERVICE_LOGO_TYPE (0x22DUL) +#define CKA_OTP_COUNTER (0x22EUL) +#define CKA_OTP_TIME (0x22FUL) +#define CKA_GOSTR3410_PARAMS (0x250UL) +#define CKA_GOSTR3411_PARAMS (0x251UL) +#define CKA_GOST28147_PARAMS (0x252UL) +#define CKA_HW_FEATURE_TYPE (0x300UL) +#define CKA_RESET_ON_INIT (0x301UL) +#define CKA_HAS_RESET (0x302UL) +#define CKA_PIXEL_X (0x400UL) +#define CKA_PIXEL_Y (0x401UL) +#define CKA_RESOLUTION (0x402UL) +#define CKA_CHAR_ROWS (0x403UL) +#define CKA_CHAR_COLUMNS (0x404UL) +#define CKA_COLOR (0x405UL) +#define CKA_BITS_PER_PIXEL (0x406UL) +#define CKA_CHAR_SETS (0x480UL) +#define CKA_ENCODING_METHODS (0x481UL) +#define CKA_MIME_TYPES (0x482UL) +#define CKA_MECHANISM_TYPE (0x500UL) +#define CKA_REQUIRED_CMS_ATTRIBUTES (0x501UL) +#define CKA_DEFAULT_CMS_ATTRIBUTES (0x502UL) +#define CKA_SUPPORTED_CMS_ATTRIBUTES (0x503UL) +#define CKA_WRAP_TEMPLATE (CKF_ARRAY_ATTRIBUTE | 0x211UL) +#define CKA_UNWRAP_TEMPLATE (CKF_ARRAY_ATTRIBUTE | 0x212UL) +#define CKA_DERIVE_TEMPLATE (CKF_ARRAY_ATTRIBUTE | 0x213UL) +#define CKA_ALLOWED_MECHANISMS (CKF_ARRAY_ATTRIBUTE | 0x600UL) +#define CKA_VENDOR_DEFINED ((unsigned long)(1UL << 31)) + +struct ck_attribute { + ck_attribute_type_t type; + void *value; + unsigned long value_len; +}; + +struct ck_date { + unsigned char year[4]; + unsigned char month[2]; + unsigned char day[2]; +}; + +typedef unsigned long ck_mechanism_type_t; + +#define CKM_RSA_PKCS_KEY_PAIR_GEN (0UL) +#define CKM_RSA_PKCS (1UL) +#define CKM_RSA_9796 (2UL) +#define CKM_RSA_X_509 (3UL) +#define CKM_MD2_RSA_PKCS (4UL) +#define CKM_MD5_RSA_PKCS (5UL) +#define CKM_SHA1_RSA_PKCS (6UL) +#define CKM_RIPEMD128_RSA_PKCS (7UL) +#define CKM_RIPEMD160_RSA_PKCS (8UL) +#define CKM_RSA_PKCS_OAEP (9UL) +#define CKM_RSA_X9_31_KEY_PAIR_GEN (0xaUL) +#define CKM_RSA_X9_31 (0xbUL) +#define CKM_SHA1_RSA_X9_31 (0xcUL) +#define CKM_RSA_PKCS_PSS (0xdUL) +#define CKM_SHA1_RSA_PKCS_PSS (0xeUL) +#define CKM_DSA_KEY_PAIR_GEN (0x10UL) +#define CKM_DSA (0x11UL) +#define CKM_DSA_SHA1 (0x12UL) +#define CKM_DSA_SHA224 (0x13UL) +#define CKM_DSA_SHA256 (0x14UL) +#define CKM_DSA_SHA384 (0x15UL) +#define CKM_DSA_SHA512 (0x16UL) +#define CKM_DH_PKCS_KEY_PAIR_GEN (0x20UL) +#define CKM_DH_PKCS_DERIVE (0x21UL) +#define CKM_X9_42_DH_KEY_PAIR_GEN (0x30UL) +#define CKM_X9_42_DH_DERIVE (0x31UL) +#define CKM_X9_42_DH_HYBRID_DERIVE (0x32UL) +#define CKM_X9_42_MQV_DERIVE (0x33UL) +#define CKM_SHA256_RSA_PKCS (0x40UL) +#define CKM_SHA384_RSA_PKCS (0x41UL) +#define CKM_SHA512_RSA_PKCS (0x42UL) +#define CKM_SHA256_RSA_PKCS_PSS (0x43UL) +#define CKM_SHA384_RSA_PKCS_PSS (0x44UL) +#define CKM_SHA512_RSA_PKCS_PSS (0x45UL) +#define CKM_SHA512_224 (0x48UL) +#define CKM_SHA512_224_HMAC (0x49UL) +#define CKM_SHA512_224_HMAC_GENERAL (0x4aUL) +#define CKM_SHA512_224_KEY_DERIVATION (0x4bUL) +#define CKM_SHA512_256 (0x4cUL) +#define CKM_SHA512_256_HMAC (0x4dUL) +#define CKM_SHA512_256_HMAC_GENERAL (0x4eUL) +#define CKM_SHA512_256_KEY_DERIVATION (0x4fUL) +#define CKM_SHA512_T (0x50UL) +#define CKM_SHA512_T_HMAC (0x51UL) +#define CKM_SHA512_T_HMAC_GENERAL (0x52UL) +#define CKM_SHA512_T_KEY_DERIVATION (0x53UL) +#define CKM_RC2_KEY_GEN (0x100UL) +#define CKM_RC2_ECB (0x101UL) +#define CKM_RC2_CBC (0x102UL) +#define CKM_RC2_MAC (0x103UL) +#define CKM_RC2_MAC_GENERAL (0x104UL) +#define CKM_RC2_CBC_PAD (0x105UL) +#define CKM_RC4_KEY_GEN (0x110UL) +#define CKM_RC4 (0x111UL) +#define CKM_DES_KEY_GEN (0x120UL) +#define CKM_DES_ECB (0x121UL) +#define CKM_DES_CBC (0x122UL) +#define CKM_DES_MAC (0x123UL) +#define CKM_DES_MAC_GENERAL (0x124UL) +#define CKM_DES_CBC_PAD (0x125UL) +#define CKM_DES2_KEY_GEN (0x130UL) +#define CKM_DES3_KEY_GEN (0x131UL) +#define CKM_DES3_ECB (0x132UL) +#define CKM_DES3_CBC (0x133UL) +#define CKM_DES3_MAC (0x134UL) +#define CKM_DES3_MAC_GENERAL (0x135UL) +#define CKM_DES3_CBC_PAD (0x136UL) +#define CKM_DES3_CMAC_GENERAL (0x137UL) +#define CKM_DES3_CMAC (0x138UL) +#define CKM_CDMF_KEY_GEN (0x140UL) +#define CKM_CDMF_ECB (0x141UL) +#define CKM_CDMF_CBC (0x142UL) +#define CKM_CDMF_MAC (0x143UL) +#define CKM_CDMF_MAC_GENERAL (0x144UL) +#define CKM_CDMF_CBC_PAD (0x145UL) +#define CKM_DES_OFB64 (0x150UL) +#define CKM_DES_OFB8 (0x151UL) +#define CKM_DES_CFB64 (0x152UL) +#define CKM_DES_CFB8 (0x153UL) +#define CKM_MD2 (0x200UL) +#define CKM_MD2_HMAC (0x201UL) +#define CKM_MD2_HMAC_GENERAL (0x202UL) +#define CKM_MD5 (0x210UL) +#define CKM_MD5_HMAC (0x211UL) +#define CKM_MD5_HMAC_GENERAL (0x212UL) +#define CKM_SHA_1 (0x220UL) +#define CKM_SHA_1_HMAC (0x221UL) +#define CKM_SHA_1_HMAC_GENERAL (0x222UL) +#define CKM_RIPEMD128 (0x230UL) +#define CKM_RIPEMD128_HMAC (0x231UL) +#define CKM_RIPEMD128_HMAC_GENERAL (0x232UL) +#define CKM_RIPEMD160 (0x240UL) +#define CKM_RIPEMD160_HMAC (0x241UL) +#define CKM_RIPEMD160_HMAC_GENERAL (0x242UL) +#define CKM_SHA256 (0x250UL) +#define CKM_SHA256_HMAC (0x251UL) +#define CKM_SHA256_HMAC_GENERAL (0x252UL) +#define CKM_SHA384 (0x260UL) +#define CKM_SHA384_HMAC (0x261UL) +#define CKM_SHA384_HMAC_GENERAL (0x262UL) +#define CKM_SHA512 (0x270UL) +#define CKM_SHA512_HMAC (0x271UL) +#define CKM_SHA512_HMAC_GENERAL (0x272UL) +#define CKM_SECURID_KEY_GEN (0x280UL) +#define CKM_SECURID (0x282UL) +#define CKM_HOTP_KEY_GEN (0x290UL) +#define CKM_HOTP (0x291UL) +#define CKM_ACTI (0x2a0UL) +#define CKM_ACTI_KEY_GEN (0x2a1UL) +#define CKM_CAST_KEY_GEN (0x300UL) +#define CKM_CAST_ECB (0x301UL) +#define CKM_CAST_CBC (0x302UL) +#define CKM_CAST_MAC (0x303UL) +#define CKM_CAST_MAC_GENERAL (0x304UL) +#define CKM_CAST_CBC_PAD (0x305UL) +#define CKM_CAST3_KEY_GEN (0x310UL) +#define CKM_CAST3_ECB (0x311UL) +#define CKM_CAST3_CBC (0x312UL) +#define CKM_CAST3_MAC (0x313UL) +#define CKM_CAST3_MAC_GENERAL (0x314UL) +#define CKM_CAST3_CBC_PAD (0x315UL) +#define CKM_CAST5_KEY_GEN (0x320UL) +#define CKM_CAST128_KEY_GEN (0x320UL) +#define CKM_CAST5_ECB (0x321UL) +#define CKM_CAST128_ECB (0x321UL) +#define CKM_CAST5_CBC (0x322UL) +#define CKM_CAST128_CBC (0x322UL) +#define CKM_CAST5_MAC (0x323UL) +#define CKM_CAST128_MAC (0x323UL) +#define CKM_CAST5_MAC_GENERAL (0x324UL) +#define CKM_CAST128_MAC_GENERAL (0x324UL) +#define CKM_CAST5_CBC_PAD (0x325UL) +#define CKM_CAST128_CBC_PAD (0x325UL) +#define CKM_RC5_KEY_GEN (0x330UL) +#define CKM_RC5_ECB (0x331UL) +#define CKM_RC5_CBC (0x332UL) +#define CKM_RC5_MAC (0x333UL) +#define CKM_RC5_MAC_GENERAL (0x334UL) +#define CKM_RC5_CBC_PAD (0x335UL) +#define CKM_IDEA_KEY_GEN (0x340UL) +#define CKM_IDEA_ECB (0x341UL) +#define CKM_IDEA_CBC (0x342UL) +#define CKM_IDEA_MAC (0x343UL) +#define CKM_IDEA_MAC_GENERAL (0x344UL) +#define CKM_IDEA_CBC_PAD (0x345UL) +#define CKM_GENERIC_SECRET_KEY_GEN (0x350UL) +#define CKM_CONCATENATE_BASE_AND_KEY (0x360UL) +#define CKM_CONCATENATE_BASE_AND_DATA (0x362UL) +#define CKM_CONCATENATE_DATA_AND_BASE (0x363UL) +#define CKM_XOR_BASE_AND_DATA (0x364UL) +#define CKM_EXTRACT_KEY_FROM_KEY (0x365UL) +#define CKM_SSL3_PRE_MASTER_KEY_GEN (0x370UL) +#define CKM_SSL3_MASTER_KEY_DERIVE (0x371UL) +#define CKM_SSL3_KEY_AND_MAC_DERIVE (0x372UL) +#define CKM_SSL3_MASTER_KEY_DERIVE_DH (0x373UL) +#define CKM_TLS_PRE_MASTER_KEY_GEN (0x374UL) +#define CKM_TLS_MASTER_KEY_DERIVE (0x375UL) +#define CKM_TLS_KEY_AND_MAC_DERIVE (0x376UL) +#define CKM_TLS_MASTER_KEY_DERIVE_DH (0x377UL) +#define CKM_TLS_PRF (0x378UL) +#define CKM_SSL3_MD5_MAC (0x380UL) +#define CKM_SSL3_SHA1_MAC (0x381UL) +#define CKM_MD5_KEY_DERIVATION (0x390UL) +#define CKM_MD2_KEY_DERIVATION (0x391UL) +#define CKM_SHA1_KEY_DERIVATION (0x392UL) +#define CKM_SHA256_KEY_DERIVATION (0x393UL) +#define CKM_SHA384_KEY_DERIVATION (0x394UL) +#define CKM_SHA512_KEY_DERIVATION (0x395UL) +#define CKM_PBE_MD2_DES_CBC (0x3a0UL) +#define CKM_PBE_MD5_DES_CBC (0x3a1UL) +#define CKM_PBE_MD5_CAST_CBC (0x3a2UL) +#define CKM_PBE_MD5_CAST3_CBC (0x3a3UL) +#define CKM_PBE_MD5_CAST5_CBC (0x3a4UL) +#define CKM_PBE_MD5_CAST128_CBC (0x3a4UL) +#define CKM_PBE_SHA1_CAST5_CBC (0x3a5UL) +#define CKM_PBE_SHA1_CAST128_CBC (0x3a5UL) +#define CKM_PBE_SHA1_RC4_128 (0x3a6UL) +#define CKM_PBE_SHA1_RC4_40 (0x3a7UL) +#define CKM_PBE_SHA1_DES3_EDE_CBC (0x3a8UL) +#define CKM_PBE_SHA1_DES2_EDE_CBC (0x3a9UL) +#define CKM_PBE_SHA1_RC2_128_CBC (0x3aaUL) +#define CKM_PBE_SHA1_RC2_40_CBC (0x3abUL) +#define CKM_PKCS5_PBKD2 (0x3b0UL) +#define CKM_PBA_SHA1_WITH_SHA1_HMAC (0x3c0UL) +#define CKM_WTLS_PRE_MASTER_KEY_GEN (0x3d0UL) +#define CKM_WTLS_MASTER_KEY_DERIVE (0x3d1UL) +#define CKM_WTLS_MASTER_KEY_DERIVE_DH_ECC (0x3d2UL) +#define CKM_WTLS_PRF (0x3d3UL) +#define CKM_WTLS_SERVER_KEY_AND_MAC_DERIVE (0x3d4UL) +#define CKM_WTLS_CLIENT_KEY_AND_MAC_DERIVE (0x3d5UL) +#define CKM_TLS10_MAC_SERVER (0x3d6UL) +#define CKM_TLS10_MAC_CLIENT (0x3d7UL) +#define CKM_TLS12_MAC (0x3d8UL) +#define CKM_TLS12_KDF (0x3d9UL) +#define CKM_TLS12_MASTER_KEY_DERIVE (0x3e0UL) +#define CKM_TLS12_KEY_AND_MAC_DERIVE (0x3e1UL) +#define CKM_TLS12_MASTER_KEY_DERIVE_DH (0x3e2UL) +#define CKM_TLS12_KEY_SAFE_DERIVE (0x3e3UL) +#define CKM_TLS_MAC (0x3e4UL) +#define CKM_TLS_KDF (0x3e5UL) +#define CKM_KEY_WRAP_LYNKS (0x400UL) +#define CKM_KEY_WRAP_SET_OAEP (0x401UL) +#define CKM_CMS_SIG (0x500UL) +#define CKM_KIP_DERIVE (0x510UL) +#define CKM_KIP_WRAP (0x511UL) +#define CKM_KIP_MAC (0x512UL) +#define CKM_ARIA_KEY_GEN (0x560UL) +#define CKM_ARIA_ECB (0x561UL) +#define CKM_ARIA_CBC (0x562UL) +#define CKM_ARIA_MAC (0x563UL) +#define CKM_ARIA_MAC_GENERAL (0x564UL) +#define CKM_ARIA_CBC_PAD (0x565UL) +#define CKM_ARIA_ECB_ENCRYPT_DATA (0x566UL) +#define CKM_ARIA_CBC_ENCRYPT_DATA (0x567UL) +#define CKM_SEED_KEY_GEN (0x650UL) +#define CKM_SEED_ECB (0x651UL) +#define CKM_SEED_CBC (0x652UL) +#define CKM_SEED_MAC (0x653UL) +#define CKM_SEED_MAC_GENERAL (0x654UL) +#define CKM_SEED_CBC_PAD (0x655UL) +#define CKM_SEED_ECB_ENCRYPT_DATA (0x656UL) +#define CKM_SEED_CBC_ENCRYPT_DATA (0x657UL) +#define CKM_SKIPJACK_KEY_GEN (0x1000UL) +#define CKM_SKIPJACK_ECB64 (0x1001UL) +#define CKM_SKIPJACK_CBC64 (0x1002UL) +#define CKM_SKIPJACK_OFB64 (0x1003UL) +#define CKM_SKIPJACK_CFB64 (0x1004UL) +#define CKM_SKIPJACK_CFB32 (0x1005UL) +#define CKM_SKIPJACK_CFB16 (0x1006UL) +#define CKM_SKIPJACK_CFB8 (0x1007UL) +#define CKM_SKIPJACK_WRAP (0x1008UL) +#define CKM_SKIPJACK_PRIVATE_WRAP (0x1009UL) +#define CKM_SKIPJACK_RELAYX (0x100aUL) +#define CKM_KEA_KEY_PAIR_GEN (0x1010UL) +#define CKM_KEA_KEY_DERIVE (0x1011UL) +#define CKM_FORTEZZA_TIMESTAMP (0x1020UL) +#define CKM_BATON_KEY_GEN (0x1030UL) +#define CKM_BATON_ECB128 (0x1031UL) +#define CKM_BATON_ECB96 (0x1032UL) +#define CKM_BATON_CBC128 (0x1033UL) +#define CKM_BATON_COUNTER (0x1034UL) +#define CKM_BATON_SHUFFLE (0x1035UL) +#define CKM_BATON_WRAP (0x1036UL) +#define CKM_ECDSA_KEY_PAIR_GEN (0x1040UL) +#define CKM_EC_KEY_PAIR_GEN (0x1040UL) +#define CKM_ECDSA (0x1041UL) +#define CKM_ECDSA_SHA1 (0x1042UL) +#define CKM_ECDSA_SHA224 (0x1043UL) +#define CKM_ECDSA_SHA256 (0x1044UL) +#define CKM_ECDSA_SHA384 (0x1045UL) +#define CKM_ECDSA_SHA512 (0x1046UL) +#define CKM_ECDH1_DERIVE (0x1050UL) +#define CKM_ECDH1_COFACTOR_DERIVE (0x1051UL) +#define CKM_ECMQV_DERIVE (0x1052UL) +#define CKM_ECDH_AES_KEY_WRAP (0x1053UL) +#define CKM_RSA_AES_KEY_WRAP (0x1054UL) +#define CKM_JUNIPER_KEY_GEN (0x1060UL) +#define CKM_JUNIPER_ECB128 (0x1061UL) +#define CKM_JUNIPER_CBC128 (0x1062UL) +#define CKM_JUNIPER_COUNTER (0x1063UL) +#define CKM_JUNIPER_SHUFFLE (0x1064UL) +#define CKM_JUNIPER_WRAP (0x1065UL) +#define CKM_FASTHASH (0x1070UL) +#define CKM_AES_KEY_GEN (0x1080UL) +#define CKM_AES_ECB (0x1081UL) +#define CKM_AES_CBC (0x1082UL) +#define CKM_AES_MAC (0x1083UL) +#define CKM_AES_MAC_GENERAL (0x1084UL) +#define CKM_AES_CBC_PAD (0x1085UL) +#define CKM_AES_CTR (0x1086UL) +#define CKM_AES_GCM (0x1087UL) +#define CKM_AES_CCM (0x1088UL) +#define CKM_AES_CTS (0x1089UL) +#define CKM_AES_CMAC (0x108aUL) +#define CKM_AES_CMAC_GENERAL (0x108bUL) +#define CKM_AES_XCBC_MAC (0x108cUL) +#define CKM_AES_XCBC_MAC_96 (0x108dUL) +#define CKM_AES_GMAC (0x108eUL) +#define CKM_BLOWFISH_KEY_GEN (0x1090UL) +#define CKM_BLOWFISH_CBC (0x1091UL) +#define CKM_TWOFISH_KEY_GEN (0x1092UL) +#define CKM_TWOFISH_CBC (0x1093UL) +#define CKM_BLOWFISH_CBC_PAD (0x1094UL) +#define CKM_TWOFISH_CBC_PAD (0x1095UL) +#define CKM_DES_ECB_ENCRYPT_DATA (0x1100UL) +#define CKM_DES_CBC_ENCRYPT_DATA (0x1101UL) +#define CKM_DES3_ECB_ENCRYPT_DATA (0x1102UL) +#define CKM_DES3_CBC_ENCRYPT_DATA (0x1103UL) +#define CKM_AES_ECB_ENCRYPT_DATA (0x1104UL) +#define CKM_AES_CBC_ENCRYPT_DATA (0x1105UL) +#define CKM_GOSTR3410_KEY_PAIR_GEN (0x1200UL) +#define CKM_GOSTR3410 (0x1201UL) +#define CKM_GOSTR3410_WITH_GOSTR3411 (0x1202UL) +#define CKM_GOSTR3410_KEY_WRAP (0x1203UL) +#define CKM_GOSTR3410_DERIVE (0x1204UL) +#define CKM_GOSTR3411 (0x1210UL) +#define CKM_GOSTR3411_HMAC (0x1211UL) +#define CKM_GOST28147_KEY_GEN (0x1220UL) +#define CKM_GOST28147_ECB (0x1221UL) +#define CKM_GOST28147 (0x1222UL) +#define CKM_GOST28147_MAC (0x1223UL) +#define CKM_GOST28147_KEY_WRAP (0x1224UL) +#define CKM_DSA_PARAMETER_GEN (0x2000UL) +#define CKM_DH_PKCS_PARAMETER_GEN (0x2001UL) +#define CKM_X9_42_DH_PARAMETER_GEN (0x2002UL) +#define CKM_DSA_PROBABLISTIC_PARAMETER_GEN (0x2003UL) +#define CKM_DSA_SHAWE_TAYLOR_PARAMETER_GEN (0x2004UL) +#define CKM_AES_OFB (0x2104UL) +#define CKM_AES_CFB64 (0x2105UL) +#define CKM_AES_CFB8 (0x2106UL) +#define CKM_AES_CFB128 (0x2107UL) +#define CKM_AES_CFB1 (0x2108UL) + +#define CKM_VENDOR_DEFINED ((unsigned long)(1UL << 31)) + +/* Amendments */ +#define CKM_SHA224 (0x255UL) +#define CKM_SHA224_HMAC (0x256UL) +#define CKM_SHA224_HMAC_GENERAL (0x257UL) +#define CKM_SHA224_RSA_PKCS (0x46UL) +#define CKM_SHA224_RSA_PKCS_PSS (0x47UL) +#define CKM_SHA224_KEY_DERIVATION (0x396UL) + +#define CKM_CAMELLIA_KEY_GEN (0x550UL) +#define CKM_CAMELLIA_ECB (0x551UL) +#define CKM_CAMELLIA_CBC (0x552UL) +#define CKM_CAMELLIA_MAC (0x553UL) +#define CKM_CAMELLIA_MAC_GENERAL (0x554UL) +#define CKM_CAMELLIA_CBC_PAD (0x555UL) +#define CKM_CAMELLIA_ECB_ENCRYPT_DATA (0x556UL) +#define CKM_CAMELLIA_CBC_ENCRYPT_DATA (0x557UL) +#define CKM_CAMELLIA_CTR (0x558UL) + +#define CKM_AES_KEY_WRAP (0x2109UL) +#define CKM_AES_KEY_WRAP_PAD (0x210aUL) + +#define CKM_RSA_PKCS_TPM_1_1 (0x4001UL) +#define CKM_RSA_PKCS_OAEP_TPM_1_1 (0x4002UL) + +/* From version 3.0 */ +#define CKM_EC_EDWARDS_KEY_PAIR_GEN (0x1055UL) +#define CKM_EDDSA (0x1057UL) + +/* Attribute and other constants related to OTP */ +#define CK_OTP_FORMAT_DECIMAL (0UL) +#define CK_OTP_FORMAT_HEXADECIMAL (1UL) +#define CK_OTP_FORMAT_ALPHANUMERIC (2UL) +#define CK_OTP_FORMAT_BINARY (3UL) +#define CK_OTP_PARAM_IGNORED (0UL) +#define CK_OTP_PARAM_OPTIONAL (1UL) +#define CK_OTP_PARAM_MANDATORY (2UL) + +#define CK_OTP_VALUE (0UL) +#define CK_OTP_PIN (1UL) +#define CK_OTP_CHALLENGE (2UL) +#define CK_OTP_TIME (3UL) +#define CK_OTP_COUNTER (4UL) +#define CK_OTP_FLAGS (5UL) +#define CK_OTP_OUTPUT_LENGTH (6UL) +#define CK_OTP_FORMAT (7UL) + +/* OTP mechanism flags */ +#define CKF_NEXT_OTP (0x01UL) +#define CKF_EXCLUDE_TIME (0x02UL) +#define CKF_EXCLUDE_COUNTER (0x04UL) +#define CKF_EXCLUDE_CHALLENGE (0x08UL) +#define CKF_EXCLUDE_PIN (0x10UL) +#define CKF_USER_FRIENDLY_OTP (0x20UL) + +#define CKN_OTP_CHANGED (0x01UL) + +struct ck_mechanism { + ck_mechanism_type_t mechanism; + void *parameter; + unsigned long parameter_len; +}; + +struct ck_mechanism_info { + unsigned long min_key_size; + unsigned long max_key_size; + ck_flags_t flags; +}; + +typedef unsigned long ck_param_type; + +typedef struct ck_otp_param { + ck_param_type type; + void *value; + unsigned long value_len; +} ck_otp_param; + +typedef struct ck_otp_params { + struct ck_otp_param *params; + unsigned long count; +} ck_otp_params; + +typedef struct ck_otp_signature_info { + struct ck_otp_param *params; + unsigned long count; +} ck_otp_signature_info; + +#define CKG_MGF1_SHA1 0x00000001UL +#define CKG_MGF1_SHA224 0x00000005UL +#define CKG_MGF1_SHA256 0x00000002UL +#define CKG_MGF1_SHA384 0x00000003UL +#define CKG_MGF1_SHA512 0x00000004UL + +typedef unsigned long ck_rsa_pkcs_mgf_type_t; + +struct ck_rsa_pkcs_pss_params { + ck_mechanism_type_t hash_alg; + ck_rsa_pkcs_mgf_type_t mgf; + unsigned long s_len; +}; + +typedef unsigned long ck_rsa_pkcs_oaep_source_type_t; + +struct ck_rsa_pkcs_oaep_params { + ck_mechanism_type_t hash_alg; + ck_rsa_pkcs_mgf_type_t mgf; + ck_rsa_pkcs_oaep_source_type_t source; + void *source_data; + unsigned long source_data_len; +}; + +struct ck_aes_ctr_params { + unsigned long counter_bits; + unsigned char cb[16]; +}; + +struct ck_gcm_params { + unsigned char *iv_ptr; + unsigned long iv_len; + unsigned long iv_bits; + unsigned char *aad_ptr; + unsigned long aad_len; + unsigned long tag_bits; +}; + +/* The following EC Key Derivation Functions are defined */ +#define CKD_NULL (0x01UL) +#define CKD_SHA1_KDF (0x02UL) + +/* The following X9.42 DH key derivation functions are defined */ +#define CKD_SHA1_KDF_ASN1 (0x03UL) +#define CKD_SHA1_KDF_CONCATENATE (0x04UL) +#define CKD_SHA224_KDF (0x05UL) +#define CKD_SHA256_KDF (0x06UL) +#define CKD_SHA384_KDF (0x07UL) +#define CKD_SHA512_KDF (0x08UL) +#define CKD_CPDIVERSIFY_KDF (0x09UL) + +typedef unsigned long ck_ec_kdf_t; + +struct ck_ecdh1_derive_params { + ck_ec_kdf_t kdf; + unsigned long shared_data_len; + unsigned char *shared_data; + unsigned long public_data_len; + unsigned char *public_data; +}; + +struct ck_key_derivation_string_data { + unsigned char *string_data; + unsigned long string_data_len; +}; + +struct ck_des_cbc_encrypt_data_params { + unsigned char iv[8]; + unsigned char *data_params; + unsigned long length; +}; + +struct ck_aes_cbc_encrypt_data_params { + unsigned char iv[16]; + unsigned char *data_params; + unsigned long length; +}; + +#define CKF_HW (1UL << 0) +#define CKF_ENCRYPT (1UL << 8) +#define CKF_DECRYPT (1UL << 9) +#define CKF_DIGEST (1UL << 10) +#define CKF_SIGN (1UL << 11) +#define CKF_SIGN_RECOVER (1UL << 12) +#define CKF_VERIFY (1UL << 13) +#define CKF_VERIFY_RECOVER (1UL << 14) +#define CKF_GENERATE (1UL << 15) +#define CKF_GENERATE_KEY_PAIR (1UL << 16) +#define CKF_WRAP (1UL << 17) +#define CKF_UNWRAP (1UL << 18) +#define CKF_DERIVE (1UL << 19) +#define CKF_EXTENSION ((unsigned long)(1UL << 31)) + +#define CKF_EC_F_P (1UL << 20) +#define CKF_EC_NAMEDCURVE (1UL << 23) +#define CKF_EC_UNCOMPRESS (1UL << 24) +#define CKF_EC_COMPRESS (1UL << 25) + +/* Flags for C_WaitForSlotEvent. */ +#define CKF_DONT_BLOCK (1UL) + +typedef unsigned long ck_rv_t; + +typedef ck_rv_t (*ck_notify_t)(ck_session_handle_t session, + ck_notification_t event, void *application); + +/* Forward reference. */ +struct ck_function_list; + +#define _CK_DECLARE_FUNCTION(name, args) \ + typedef ck_rv_t(*CK_##name) args; \ + ck_rv_t CK_SPEC name args + +_CK_DECLARE_FUNCTION(C_Initialize, (void *init_args)); +_CK_DECLARE_FUNCTION(C_Finalize, (void *reserved)); +_CK_DECLARE_FUNCTION(C_GetInfo, (struct ck_info * info)); +_CK_DECLARE_FUNCTION(C_GetFunctionList, + (struct ck_function_list * *function_list)); + +_CK_DECLARE_FUNCTION(C_GetSlotList, + (unsigned char token_present, ck_slot_id_t *slot_list, + unsigned long *count)); +_CK_DECLARE_FUNCTION(C_GetSlotInfo, + (ck_slot_id_t slot_id, struct ck_slot_info *info)); +_CK_DECLARE_FUNCTION(C_GetTokenInfo, + (ck_slot_id_t slot_id, struct ck_token_info *info)); +_CK_DECLARE_FUNCTION(C_WaitForSlotEvent, + (ck_flags_t flags, ck_slot_id_t *slot, void *reserved)); +_CK_DECLARE_FUNCTION(C_GetMechanismList, + (ck_slot_id_t slot_id, ck_mechanism_type_t *mechanism_list, + unsigned long *count)); +_CK_DECLARE_FUNCTION(C_GetMechanismInfo, + (ck_slot_id_t slot_id, ck_mechanism_type_t type, + struct ck_mechanism_info *info)); +_CK_DECLARE_FUNCTION(C_InitToken, + (ck_slot_id_t slot_id, unsigned char *pin, + unsigned long pin_len, unsigned char *label)); +_CK_DECLARE_FUNCTION(C_InitPIN, (ck_session_handle_t session, + unsigned char *pin, unsigned long pin_len)); +_CK_DECLARE_FUNCTION(C_SetPIN, (ck_session_handle_t session, + unsigned char *old_pin, unsigned long old_len, + unsigned char *new_pin, unsigned long new_len)); + +_CK_DECLARE_FUNCTION(C_OpenSession, + (ck_slot_id_t slot_id, ck_flags_t flags, void *application, + ck_notify_t notify, ck_session_handle_t *session)); +_CK_DECLARE_FUNCTION(C_CloseSession, (ck_session_handle_t session)); +_CK_DECLARE_FUNCTION(C_CloseAllSessions, (ck_slot_id_t slot_id)); +_CK_DECLARE_FUNCTION(C_GetSessionInfo, (ck_session_handle_t session, + struct ck_session_info *info)); +_CK_DECLARE_FUNCTION(C_GetOperationState, (ck_session_handle_t session, + unsigned char *operation_state, + unsigned long *operation_state_len)); +_CK_DECLARE_FUNCTION(C_SetOperationState, + (ck_session_handle_t session, + unsigned char *operation_state, + unsigned long operation_state_len, + ck_object_handle_t encryption_key, + ck_object_handle_t authentiation_key)); +_CK_DECLARE_FUNCTION(C_Login, + (ck_session_handle_t session, ck_user_type_t user_type, + unsigned char *pin, unsigned long pin_len)); +_CK_DECLARE_FUNCTION(C_Logout, (ck_session_handle_t session)); + +_CK_DECLARE_FUNCTION(C_CreateObject, + (ck_session_handle_t session, struct ck_attribute *templ, + unsigned long count, ck_object_handle_t *object)); +_CK_DECLARE_FUNCTION(C_CopyObject, + (ck_session_handle_t session, ck_object_handle_t object, + struct ck_attribute *templ, unsigned long count, + ck_object_handle_t *new_object)); +_CK_DECLARE_FUNCTION(C_DestroyObject, + (ck_session_handle_t session, ck_object_handle_t object)); +_CK_DECLARE_FUNCTION(C_GetObjectSize, + (ck_session_handle_t session, ck_object_handle_t object, + unsigned long *size)); +_CK_DECLARE_FUNCTION(C_GetAttributeValue, + (ck_session_handle_t session, ck_object_handle_t object, + struct ck_attribute *templ, unsigned long count)); +_CK_DECLARE_FUNCTION(C_SetAttributeValue, + (ck_session_handle_t session, ck_object_handle_t object, + struct ck_attribute *templ, unsigned long count)); +_CK_DECLARE_FUNCTION(C_FindObjectsInit, + (ck_session_handle_t session, struct ck_attribute *templ, + unsigned long count)); +_CK_DECLARE_FUNCTION(C_FindObjects, + (ck_session_handle_t session, ck_object_handle_t *object, + unsigned long max_object_count, + unsigned long *object_count)); +_CK_DECLARE_FUNCTION(C_FindObjectsFinal, (ck_session_handle_t session)); + +_CK_DECLARE_FUNCTION(C_EncryptInit, + (ck_session_handle_t session, + struct ck_mechanism *mechanism, ck_object_handle_t key)); +_CK_DECLARE_FUNCTION(C_Encrypt, + (ck_session_handle_t session, unsigned char *data, + unsigned long data_len, unsigned char *encrypted_data, + unsigned long *encrypted_data_len)); +_CK_DECLARE_FUNCTION(C_EncryptUpdate, + (ck_session_handle_t session, unsigned char *part, + unsigned long part_len, unsigned char *encrypted_part, + unsigned long *encrypted_part_len)); +_CK_DECLARE_FUNCTION(C_EncryptFinal, (ck_session_handle_t session, + unsigned char *last_encrypted_part, + unsigned long *last_encrypted_part_len)); + +_CK_DECLARE_FUNCTION(C_DecryptInit, + (ck_session_handle_t session, + struct ck_mechanism *mechanism, ck_object_handle_t key)); +_CK_DECLARE_FUNCTION(C_Decrypt, (ck_session_handle_t session, + unsigned char *encrypted_data, + unsigned long encrypted_data_len, + unsigned char *data, unsigned long *data_len)); +_CK_DECLARE_FUNCTION(C_DecryptUpdate, + (ck_session_handle_t session, + unsigned char *encrypted_part, + unsigned long encrypted_part_len, unsigned char *part, + unsigned long *part_len)); +_CK_DECLARE_FUNCTION(C_DecryptFinal, + (ck_session_handle_t session, unsigned char *last_part, + unsigned long *last_part_len)); + +_CK_DECLARE_FUNCTION(C_DigestInit, (ck_session_handle_t session, + struct ck_mechanism *mechanism)); +_CK_DECLARE_FUNCTION(C_Digest, + (ck_session_handle_t session, unsigned char *data, + unsigned long data_len, unsigned char *digest, + unsigned long *digest_len)); +_CK_DECLARE_FUNCTION(C_DigestUpdate, + (ck_session_handle_t session, unsigned char *part, + unsigned long part_len)); +_CK_DECLARE_FUNCTION(C_DigestKey, + (ck_session_handle_t session, ck_object_handle_t key)); +_CK_DECLARE_FUNCTION(C_DigestFinal, + (ck_session_handle_t session, unsigned char *digest, + unsigned long *digest_len)); + +_CK_DECLARE_FUNCTION(C_SignInit, + (ck_session_handle_t session, + struct ck_mechanism *mechanism, ck_object_handle_t key)); +_CK_DECLARE_FUNCTION(C_Sign, (ck_session_handle_t session, unsigned char *data, + unsigned long data_len, unsigned char *signature, + unsigned long *signature_len)); +_CK_DECLARE_FUNCTION(C_SignUpdate, + (ck_session_handle_t session, unsigned char *part, + unsigned long part_len)); +_CK_DECLARE_FUNCTION(C_SignFinal, + (ck_session_handle_t session, unsigned char *signature, + unsigned long *signature_len)); +_CK_DECLARE_FUNCTION(C_SignRecoverInit, + (ck_session_handle_t session, + struct ck_mechanism *mechanism, ck_object_handle_t key)); +_CK_DECLARE_FUNCTION(C_SignRecover, + (ck_session_handle_t session, unsigned char *data, + unsigned long data_len, unsigned char *signature, + unsigned long *signature_len)); + +_CK_DECLARE_FUNCTION(C_VerifyInit, + (ck_session_handle_t session, + struct ck_mechanism *mechanism, ck_object_handle_t key)); +_CK_DECLARE_FUNCTION(C_Verify, + (ck_session_handle_t session, unsigned char *data, + unsigned long data_len, unsigned char *signature, + unsigned long signature_len)); +_CK_DECLARE_FUNCTION(C_VerifyUpdate, + (ck_session_handle_t session, unsigned char *part, + unsigned long part_len)); +_CK_DECLARE_FUNCTION(C_VerifyFinal, + (ck_session_handle_t session, unsigned char *signature, + unsigned long signature_len)); +_CK_DECLARE_FUNCTION(C_VerifyRecoverInit, + (ck_session_handle_t session, + struct ck_mechanism *mechanism, ck_object_handle_t key)); +_CK_DECLARE_FUNCTION(C_VerifyRecover, + (ck_session_handle_t session, unsigned char *signature, + unsigned long signature_len, unsigned char *data, + unsigned long *data_len)); + +_CK_DECLARE_FUNCTION(C_DigestEncryptUpdate, + (ck_session_handle_t session, unsigned char *part, + unsigned long part_len, unsigned char *encrypted_part, + unsigned long *encrypted_part_len)); +_CK_DECLARE_FUNCTION(C_DecryptDigestUpdate, + (ck_session_handle_t session, + unsigned char *encrypted_part, + unsigned long encrypted_part_len, unsigned char *part, + unsigned long *part_len)); +_CK_DECLARE_FUNCTION(C_SignEncryptUpdate, + (ck_session_handle_t session, unsigned char *part, + unsigned long part_len, unsigned char *encrypted_part, + unsigned long *encrypted_part_len)); +_CK_DECLARE_FUNCTION(C_DecryptVerifyUpdate, + (ck_session_handle_t session, + unsigned char *encrypted_part, + unsigned long encrypted_part_len, unsigned char *part, + unsigned long *part_len)); + +_CK_DECLARE_FUNCTION(C_GenerateKey, + (ck_session_handle_t session, + struct ck_mechanism *mechanism, + struct ck_attribute *templ, unsigned long count, + ck_object_handle_t *key)); +_CK_DECLARE_FUNCTION(C_GenerateKeyPair, + (ck_session_handle_t session, + struct ck_mechanism *mechanism, + struct ck_attribute *public_key_template, + unsigned long public_key_attribute_count, + struct ck_attribute *private_key_template, + unsigned long private_key_attribute_count, + ck_object_handle_t *public_key, + ck_object_handle_t *private_key)); +_CK_DECLARE_FUNCTION(C_WrapKey, + (ck_session_handle_t session, + struct ck_mechanism *mechanism, + ck_object_handle_t wrapping_key, ck_object_handle_t key, + unsigned char *wrapped_key, + unsigned long *wrapped_key_len)); +_CK_DECLARE_FUNCTION(C_UnwrapKey, + (ck_session_handle_t session, + struct ck_mechanism *mechanism, + ck_object_handle_t unwrapping_key, + unsigned char *wrapped_key, unsigned long wrapped_key_len, + struct ck_attribute *templ, unsigned long attribute_count, + ck_object_handle_t *key)); +_CK_DECLARE_FUNCTION(C_DeriveKey, + (ck_session_handle_t session, + struct ck_mechanism *mechanism, + ck_object_handle_t base_key, struct ck_attribute *templ, + unsigned long attribute_count, ck_object_handle_t *key)); + +_CK_DECLARE_FUNCTION(C_SeedRandom, + (ck_session_handle_t session, unsigned char *seed, + unsigned long seed_len)); +_CK_DECLARE_FUNCTION(C_GenerateRandom, + (ck_session_handle_t session, unsigned char *random_data, + unsigned long random_len)); + +_CK_DECLARE_FUNCTION(C_GetFunctionStatus, (ck_session_handle_t session)); +_CK_DECLARE_FUNCTION(C_CancelFunction, (ck_session_handle_t session)); + +struct ck_function_list { + struct ck_version version; + CK_C_Initialize C_Initialize; + CK_C_Finalize C_Finalize; + CK_C_GetInfo C_GetInfo; + CK_C_GetFunctionList C_GetFunctionList; + CK_C_GetSlotList C_GetSlotList; + CK_C_GetSlotInfo C_GetSlotInfo; + CK_C_GetTokenInfo C_GetTokenInfo; + CK_C_GetMechanismList C_GetMechanismList; + CK_C_GetMechanismInfo C_GetMechanismInfo; + CK_C_InitToken C_InitToken; + CK_C_InitPIN C_InitPIN; + CK_C_SetPIN C_SetPIN; + CK_C_OpenSession C_OpenSession; + CK_C_CloseSession C_CloseSession; + CK_C_CloseAllSessions C_CloseAllSessions; + CK_C_GetSessionInfo C_GetSessionInfo; + CK_C_GetOperationState C_GetOperationState; + CK_C_SetOperationState C_SetOperationState; + CK_C_Login C_Login; + CK_C_Logout C_Logout; + CK_C_CreateObject C_CreateObject; + CK_C_CopyObject C_CopyObject; + CK_C_DestroyObject C_DestroyObject; + CK_C_GetObjectSize C_GetObjectSize; + CK_C_GetAttributeValue C_GetAttributeValue; + CK_C_SetAttributeValue C_SetAttributeValue; + CK_C_FindObjectsInit C_FindObjectsInit; + CK_C_FindObjects C_FindObjects; + CK_C_FindObjectsFinal C_FindObjectsFinal; + CK_C_EncryptInit C_EncryptInit; + CK_C_Encrypt C_Encrypt; + CK_C_EncryptUpdate C_EncryptUpdate; + CK_C_EncryptFinal C_EncryptFinal; + CK_C_DecryptInit C_DecryptInit; + CK_C_Decrypt C_Decrypt; + CK_C_DecryptUpdate C_DecryptUpdate; + CK_C_DecryptFinal C_DecryptFinal; + CK_C_DigestInit C_DigestInit; + CK_C_Digest C_Digest; + CK_C_DigestUpdate C_DigestUpdate; + CK_C_DigestKey C_DigestKey; + CK_C_DigestFinal C_DigestFinal; + CK_C_SignInit C_SignInit; + CK_C_Sign C_Sign; + CK_C_SignUpdate C_SignUpdate; + CK_C_SignFinal C_SignFinal; + CK_C_SignRecoverInit C_SignRecoverInit; + CK_C_SignRecover C_SignRecover; + CK_C_VerifyInit C_VerifyInit; + CK_C_Verify C_Verify; + CK_C_VerifyUpdate C_VerifyUpdate; + CK_C_VerifyFinal C_VerifyFinal; + CK_C_VerifyRecoverInit C_VerifyRecoverInit; + CK_C_VerifyRecover C_VerifyRecover; + CK_C_DigestEncryptUpdate C_DigestEncryptUpdate; + CK_C_DecryptDigestUpdate C_DecryptDigestUpdate; + CK_C_SignEncryptUpdate C_SignEncryptUpdate; + CK_C_DecryptVerifyUpdate C_DecryptVerifyUpdate; + CK_C_GenerateKey C_GenerateKey; + CK_C_GenerateKeyPair C_GenerateKeyPair; + CK_C_WrapKey C_WrapKey; + CK_C_UnwrapKey C_UnwrapKey; + CK_C_DeriveKey C_DeriveKey; + CK_C_SeedRandom C_SeedRandom; + CK_C_GenerateRandom C_GenerateRandom; + CK_C_GetFunctionStatus C_GetFunctionStatus; + CK_C_CancelFunction C_CancelFunction; + CK_C_WaitForSlotEvent C_WaitForSlotEvent; +}; + +typedef ck_rv_t (*ck_createmutex_t)(void **mutex); +typedef ck_rv_t (*ck_destroymutex_t)(void *mutex); +typedef ck_rv_t (*ck_lockmutex_t)(void *mutex); +typedef ck_rv_t (*ck_unlockmutex_t)(void *mutex); + +struct ck_c_initialize_args { + ck_createmutex_t create_mutex; + ck_destroymutex_t destroy_mutex; + ck_lockmutex_t lock_mutex; + ck_unlockmutex_t unlock_mutex; + ck_flags_t flags; + void *reserved; +}; + +#define CKF_LIBRARY_CANT_CREATE_OS_THREADS (1UL << 0) +#define CKF_OS_LOCKING_OK (1UL << 1) + +#define CKR_OK (0UL) +#define CKR_CANCEL (1UL) +#define CKR_HOST_MEMORY (2UL) +#define CKR_SLOT_ID_INVALID (3UL) +#define CKR_GENERAL_ERROR (5UL) +#define CKR_FUNCTION_FAILED (6UL) +#define CKR_ARGUMENTS_BAD (7UL) +#define CKR_NO_EVENT (8UL) +#define CKR_NEED_TO_CREATE_THREADS (9UL) +#define CKR_CANT_LOCK (0xaUL) +#define CKR_ATTRIBUTE_READ_ONLY (0x10UL) +#define CKR_ATTRIBUTE_SENSITIVE (0x11UL) +#define CKR_ATTRIBUTE_TYPE_INVALID (0x12UL) +#define CKR_ATTRIBUTE_VALUE_INVALID (0x13UL) +#define CKR_ACTION_PROHIBITED (0x1BUL) +#define CKR_DATA_INVALID (0x20UL) +#define CKR_DATA_LEN_RANGE (0x21UL) +#define CKR_DEVICE_ERROR (0x30UL) +#define CKR_DEVICE_MEMORY (0x31UL) +#define CKR_DEVICE_REMOVED (0x32UL) +#define CKR_ENCRYPTED_DATA_INVALID (0x40UL) +#define CKR_ENCRYPTED_DATA_LEN_RANGE (0x41UL) +#define CKR_FUNCTION_CANCELED (0x50UL) +#define CKR_FUNCTION_NOT_PARALLEL (0x51UL) +#define CKR_FUNCTION_NOT_SUPPORTED (0x54UL) +#define CKR_KEY_HANDLE_INVALID (0x60UL) +#define CKR_KEY_SIZE_RANGE (0x62UL) +#define CKR_KEY_TYPE_INCONSISTENT (0x63UL) +#define CKR_KEY_NOT_NEEDED (0x64UL) +#define CKR_KEY_CHANGED (0x65UL) +#define CKR_KEY_NEEDED (0x66UL) +#define CKR_KEY_INDIGESTIBLE (0x67UL) +#define CKR_KEY_FUNCTION_NOT_PERMITTED (0x68UL) +#define CKR_KEY_NOT_WRAPPABLE (0x69UL) +#define CKR_KEY_UNEXTRACTABLE (0x6aUL) +#define CKR_MECHANISM_INVALID (0x70UL) +#define CKR_MECHANISM_PARAM_INVALID (0x71UL) +#define CKR_OBJECT_HANDLE_INVALID (0x82UL) +#define CKR_OPERATION_ACTIVE (0x90UL) +#define CKR_OPERATION_NOT_INITIALIZED (0x91UL) +#define CKR_PIN_INCORRECT (0xa0UL) +#define CKR_PIN_INVALID (0xa1UL) +#define CKR_PIN_LEN_RANGE (0xa2UL) +#define CKR_PIN_EXPIRED (0xa3UL) +#define CKR_PIN_LOCKED (0xa4UL) +#define CKR_SESSION_CLOSED (0xb0UL) +#define CKR_SESSION_COUNT (0xb1UL) +#define CKR_SESSION_HANDLE_INVALID (0xb3UL) +#define CKR_SESSION_PARALLEL_NOT_SUPPORTED (0xb4UL) +#define CKR_SESSION_READ_ONLY (0xb5UL) +#define CKR_SESSION_EXISTS (0xb6UL) +#define CKR_SESSION_READ_ONLY_EXISTS (0xb7UL) +#define CKR_SESSION_READ_WRITE_SO_EXISTS (0xb8UL) +#define CKR_SIGNATURE_INVALID (0xc0UL) +#define CKR_SIGNATURE_LEN_RANGE (0xc1UL) +#define CKR_TEMPLATE_INCOMPLETE (0xd0UL) +#define CKR_TEMPLATE_INCONSISTENT (0xd1UL) +#define CKR_TOKEN_NOT_PRESENT (0xe0UL) +#define CKR_TOKEN_NOT_RECOGNIZED (0xe1UL) +#define CKR_TOKEN_WRITE_PROTECTED (0xe2UL) +#define CKR_UNWRAPPING_KEY_HANDLE_INVALID (0xf0UL) +#define CKR_UNWRAPPING_KEY_SIZE_RANGE (0xf1UL) +#define CKR_UNWRAPPING_KEY_TYPE_INCONSISTENT (0xf2UL) +#define CKR_USER_ALREADY_LOGGED_IN (0x100UL) +#define CKR_USER_NOT_LOGGED_IN (0x101UL) +#define CKR_USER_PIN_NOT_INITIALIZED (0x102UL) +#define CKR_USER_TYPE_INVALID (0x103UL) +#define CKR_USER_ANOTHER_ALREADY_LOGGED_IN (0x104UL) +#define CKR_USER_TOO_MANY_TYPES (0x105UL) +#define CKR_WRAPPED_KEY_INVALID (0x110UL) +#define CKR_WRAPPED_KEY_LEN_RANGE (0x112UL) +#define CKR_WRAPPING_KEY_HANDLE_INVALID (0x113UL) +#define CKR_WRAPPING_KEY_SIZE_RANGE (0x114UL) +#define CKR_WRAPPING_KEY_TYPE_INCONSISTENT (0x115UL) +#define CKR_RANDOM_SEED_NOT_SUPPORTED (0x120UL) +#define CKR_RANDOM_NO_RNG (0x121UL) +#define CKR_DOMAIN_PARAMS_INVALID (0x130UL) +#define CKR_BUFFER_TOO_SMALL (0x150UL) +#define CKR_SAVED_STATE_INVALID (0x160UL) +#define CKR_INFORMATION_SENSITIVE (0x170UL) +#define CKR_STATE_UNSAVEABLE (0x180UL) +#define CKR_CRYPTOKI_NOT_INITIALIZED (0x190UL) +#define CKR_CRYPTOKI_ALREADY_INITIALIZED (0x191UL) +#define CKR_MUTEX_BAD (0x1a0UL) +#define CKR_MUTEX_NOT_LOCKED (0x1a1UL) +#define CKR_NEW_PIN_MODE (0x1b0UL) +#define CKR_NEXT_OTP (0x1b1UL) +#define CKR_EXCEEDED_MAX_ITERATIONS (0x1c0UL) +#define CKR_FIPS_SELF_TEST_FAILED (0x1c1UL) +#define CKR_LIBRARY_LOAD_FAILED (0x1c2UL) +#define CKR_PIN_TOO_WEAK (0x1c3UL) +#define CKR_PUBLIC_KEY_INVALID (0x1c4UL) +#define CKR_FUNCTION_REJECTED (0x200UL) +#define CKR_VENDOR_DEFINED ((unsigned long)(1UL << 31)) + +#define CKZ_DATA_SPECIFIED (0x01UL) + +/* Compatibility layer. */ + +#ifdef CRYPTOKI_COMPAT + +#undef CK_DEFINE_FUNCTION +#define CK_DEFINE_FUNCTION(retval, name) retval CK_SPEC name + +/* For NULL. */ +#include <stddef.h> + +typedef unsigned char CK_BYTE; +typedef unsigned char CK_CHAR; +typedef unsigned char CK_UTF8CHAR; +typedef unsigned char CK_BBOOL; +typedef unsigned long int CK_ULONG; +typedef long int CK_LONG; +typedef CK_BYTE *CK_BYTE_PTR; +typedef CK_CHAR *CK_CHAR_PTR; +typedef CK_UTF8CHAR *CK_UTF8CHAR_PTR; +typedef CK_ULONG *CK_ULONG_PTR; +typedef void *CK_VOID_PTR; +typedef void **CK_VOID_PTR_PTR; +#define CK_FALSE 0 +#define CK_TRUE 1 +#ifndef CK_DISABLE_TRUE_FALSE +#ifndef FALSE +#define FALSE 0 +#endif /* ifndef FALSE */ +#ifndef TRUE +#define TRUE 1 +#endif /* ifndef TRUE */ +#endif /* ifndef CK_DISABLE_TRUE_FALSE */ + +typedef struct ck_version CK_VERSION; +typedef struct ck_version *CK_VERSION_PTR; + +typedef struct ck_info CK_INFO; +typedef struct ck_info *CK_INFO_PTR; + +typedef ck_slot_id_t *CK_SLOT_ID_PTR; + +typedef struct ck_slot_info CK_SLOT_INFO; +typedef struct ck_slot_info *CK_SLOT_INFO_PTR; + +typedef struct ck_token_info CK_TOKEN_INFO; +typedef struct ck_token_info *CK_TOKEN_INFO_PTR; + +typedef ck_session_handle_t *CK_SESSION_HANDLE_PTR; + +typedef struct ck_session_info CK_SESSION_INFO; +typedef struct ck_session_info *CK_SESSION_INFO_PTR; + +typedef ck_object_handle_t *CK_OBJECT_HANDLE_PTR; + +typedef ck_object_class_t *CK_OBJECT_CLASS_PTR; + +typedef struct ck_attribute CK_ATTRIBUTE; +typedef struct ck_attribute *CK_ATTRIBUTE_PTR; + +typedef struct ck_date CK_DATE; +typedef struct ck_date *CK_DATE_PTR; + +typedef ck_mechanism_type_t *CK_MECHANISM_TYPE_PTR; + +typedef struct ck_mechanism CK_MECHANISM; +typedef struct ck_mechanism *CK_MECHANISM_PTR; + +typedef struct ck_mechanism_info CK_MECHANISM_INFO; +typedef struct ck_mechanism_info *CK_MECHANISM_INFO_PTR; + +typedef struct ck_otp_mechanism_info CK_OTP_MECHANISM_INFO; +typedef struct ck_otp_mechanism_info *CK_OTP_MECHANISM_INFO_PTR; + +typedef struct ck_function_list CK_FUNCTION_LIST; +typedef struct ck_function_list *CK_FUNCTION_LIST_PTR; +typedef struct ck_function_list **CK_FUNCTION_LIST_PTR_PTR; + +typedef struct ck_c_initialize_args CK_C_INITIALIZE_ARGS; +typedef struct ck_c_initialize_args *CK_C_INITIALIZE_ARGS_PTR; + +typedef struct ck_rsa_pkcs_pss_params CK_RSA_PKCS_PSS_PARAMS; +typedef struct ck_rsa_pkcs_pss_params *CK_RSA_PKCS_PSS_PARAMS_PTR; + +typedef struct ck_rsa_pkcs_oaep_params CK_RSA_PKCS_OAEP_PARAMS; +typedef struct ck_rsa_pkcs_oaep_params *CK_RSA_PKCS_OAEP_PARAMS_PTR; + +typedef struct ck_aes_ctr_params CK_AES_CTR_PARAMS; +typedef struct ck_aes_ctr_params *CK_AES_CTR_PARAMS_PTR; + +typedef struct ck_gcm_params CK_GCM_PARAMS; +typedef struct ck_gcm_params *CK_GCM_PARAMS_PTR; + +typedef struct ck_ecdh1_derive_params CK_ECDH1_DERIVE_PARAMS; +typedef struct ck_ecdh1_derive_params *CK_ECDH1_DERIVE_PARAMS_PTR; + +typedef struct ck_key_derivation_string_data CK_KEY_DERIVATION_STRING_DATA; +typedef struct ck_key_derivation_string_data *CK_KEY_DERIVATION_STRING_DATA_PTR; + +typedef struct ck_des_cbc_encrypt_data_params CK_DES_CBC_ENCRYPT_DATA_PARAMS; +typedef struct ck_des_cbc_encrypt_data_params + *CK_DES_CBC_ENCRYPT_DATA_PARAMS_PTR; + +typedef struct ck_aes_cbc_encrypt_data_params CK_AES_CBC_ENCRYPT_DATA_PARAMS; +typedef struct ck_aes_cbc_encrypt_data_params + *CK_AES_CBC_ENCRYPT_DATA_PARAMS_PTR; + +#ifndef NULL_PTR +#define NULL_PTR NULL +#endif /* ifndef NULL_PTR */ + +/* Delete the helper macros defined at the top of the file. */ +#undef ck_flags_t +#undef ck_version + +#undef ck_info +#undef cryptoki_version +#undef manufacturer_id +#undef library_description +#undef library_version + +#undef ck_notification_t +#undef ck_slot_id_t + +#undef ck_slot_info +#undef slot_description +#undef hardware_version +#undef firmware_version + +#undef ck_token_info +#undef serial_number +#undef max_session_count +#undef session_count +#undef max_rw_session_count +#undef rw_session_count +#undef max_pin_len +#undef min_pin_len +#undef total_public_memory +#undef free_public_memory +#undef total_private_memory +#undef free_private_memory +#undef utc_time + +#undef ck_session_handle_t +#undef ck_user_type_t +#undef ck_state_t + +#undef ck_session_info +#undef slot_id +#undef device_error + +#undef ck_object_handle_t +#undef ck_object_class_t +#undef ck_hw_feature_type_t +#undef ck_key_type_t +#undef ck_certificate_type_t +#undef ck_attribute_type_t + +#undef ck_attribute +#undef value +#undef value_len + +#undef params +#undef count + +#undef ck_date + +#undef ck_mechanism_type_t + +#undef ck_mechanism +#undef parameter +#undef parameter_len + +#undef ck_mechanism_info + +#undef ck_param_type +#undef ck_otp_param +#undef ck_otp_params +#undef ck_otp_signature_info + +#undef min_key_size +#undef max_key_size + +#undef ck_rv_t +#undef ck_notify_t + +#undef ck_function_list + +#undef ck_createmutex_t +#undef ck_destroymutex_t +#undef ck_lockmutex_t +#undef ck_unlockmutex_t + +#undef ck_c_initialize_args +#undef create_mutex +#undef destroy_mutex +#undef lock_mutex +#undef unlock_mutex +#undef reserved + +#endif /* CRYPTOKI_COMPAT */ + +/* System dependencies. */ +#if defined(_WIN32) || defined(CRYPTOKI_FORCE_WIN32) +#pragma pack(pop, cryptoki) +#endif /* if defined(_WIN32) || defined(CRYPTOKI_FORCE_WIN32) */ + +#if defined(__cplusplus) +} +#endif /* if defined(__cplusplus) */ + +#endif /* PKCS11_H */ diff --git a/lib/isc/iterated_hash.c b/lib/isc/iterated_hash.c new file mode 100644 index 0000000..1f95353 --- /dev/null +++ b/lib/isc/iterated_hash.c @@ -0,0 +1,64 @@ +/* + * 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. + */ + +#include <stdio.h> + +#include <openssl/opensslv.h> +#include <openssl/sha.h> + +#include <isc/iterated_hash.h> +#include <isc/thread.h> +#include <isc/util.h> + +int +isc_iterated_hash(unsigned char *out, const unsigned int hashalg, + const int iterations, const unsigned char *salt, + const int saltlength, const unsigned char *in, + const int inlength) { + REQUIRE(out != NULL); + + int n = 0; + size_t len; + const unsigned char *buf; + SHA_CTX ctx; + + if (hashalg != 1) { + return (0); + } + + buf = in; + len = inlength; + + do { + if (SHA1_Init(&ctx) != 1) { + return (0); + } + + if (SHA1_Update(&ctx, buf, len) != 1) { + return (0); + } + + if (SHA1_Update(&ctx, salt, saltlength) != 1) { + return (0); + } + + if (SHA1_Final(out, &ctx) != 1) { + return (0); + } + + buf = out; + len = SHA_DIGEST_LENGTH; + } while (n++ < iterations); + + return (SHA_DIGEST_LENGTH); +} diff --git a/lib/isc/lex.c b/lib/isc/lex.c new file mode 100644 index 0000000..5878d53 --- /dev/null +++ b/lib/isc/lex.c @@ -0,0 +1,1133 @@ +/* + * 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. + */ + +/*! \file */ + +#include <ctype.h> +#include <errno.h> +#include <inttypes.h> +#include <stdbool.h> +#include <stdlib.h> + +#include <isc/buffer.h> +#include <isc/file.h> +#include <isc/lex.h> +#include <isc/mem.h> +#include <isc/parseint.h> +#include <isc/print.h> +#include <isc/stdio.h> +#include <isc/string.h> +#include <isc/util.h> + +typedef struct inputsource { + isc_result_t result; + bool is_file; + bool need_close; + bool at_eof; + bool last_was_eol; + isc_buffer_t *pushback; + unsigned int ignored; + void *input; + char *name; + unsigned long line; + unsigned long saved_line; + ISC_LINK(struct inputsource) link; +} inputsource; + +#define LEX_MAGIC ISC_MAGIC('L', 'e', 'x', '!') +#define VALID_LEX(l) ISC_MAGIC_VALID(l, LEX_MAGIC) + +struct isc_lex { + /* Unlocked. */ + unsigned int magic; + isc_mem_t *mctx; + size_t max_token; + char *data; + unsigned int comments; + bool comment_ok; + bool last_was_eol; + unsigned int brace_count; + unsigned int paren_count; + unsigned int saved_paren_count; + isc_lexspecials_t specials; + LIST(struct inputsource) sources; +}; + +static isc_result_t +grow_data(isc_lex_t *lex, size_t *remainingp, char **currp, char **prevp) { + char *tmp; + + tmp = isc_mem_get(lex->mctx, lex->max_token * 2 + 1); + memmove(tmp, lex->data, lex->max_token + 1); + *currp = tmp + (*currp - lex->data); + if (*prevp != NULL) { + *prevp = tmp + (*prevp - lex->data); + } + isc_mem_put(lex->mctx, lex->data, lex->max_token + 1); + lex->data = tmp; + *remainingp += lex->max_token; + lex->max_token *= 2; + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_lex_create(isc_mem_t *mctx, size_t max_token, isc_lex_t **lexp) { + isc_lex_t *lex; + + /* + * Create a lexer. + */ + REQUIRE(lexp != NULL && *lexp == NULL); + + if (max_token == 0U) { + max_token = 1; + } + + lex = isc_mem_get(mctx, sizeof(*lex)); + lex->data = isc_mem_get(mctx, max_token + 1); + lex->mctx = mctx; + lex->max_token = max_token; + lex->comments = 0; + lex->comment_ok = true; + lex->last_was_eol = true; + lex->brace_count = 0; + lex->paren_count = 0; + lex->saved_paren_count = 0; + memset(lex->specials, 0, 256); + INIT_LIST(lex->sources); + lex->magic = LEX_MAGIC; + + *lexp = lex; + + return (ISC_R_SUCCESS); +} + +void +isc_lex_destroy(isc_lex_t **lexp) { + isc_lex_t *lex; + + /* + * Destroy the lexer. + */ + + REQUIRE(lexp != NULL); + lex = *lexp; + *lexp = NULL; + REQUIRE(VALID_LEX(lex)); + + while (!EMPTY(lex->sources)) { + RUNTIME_CHECK(isc_lex_close(lex) == ISC_R_SUCCESS); + } + if (lex->data != NULL) { + isc_mem_put(lex->mctx, lex->data, lex->max_token + 1); + } + lex->magic = 0; + isc_mem_put(lex->mctx, lex, sizeof(*lex)); +} + +unsigned int +isc_lex_getcomments(isc_lex_t *lex) { + /* + * Return the current lexer commenting styles. + */ + + REQUIRE(VALID_LEX(lex)); + + return (lex->comments); +} + +void +isc_lex_setcomments(isc_lex_t *lex, unsigned int comments) { + /* + * Set allowed lexer commenting styles. + */ + + REQUIRE(VALID_LEX(lex)); + + lex->comments = comments; +} + +void +isc_lex_getspecials(isc_lex_t *lex, isc_lexspecials_t specials) { + /* + * Put the current list of specials into 'specials'. + */ + + REQUIRE(VALID_LEX(lex)); + + memmove(specials, lex->specials, 256); +} + +void +isc_lex_setspecials(isc_lex_t *lex, isc_lexspecials_t specials) { + /* + * The characters in 'specials' are returned as tokens. Along with + * whitespace, they delimit strings and numbers. + */ + + REQUIRE(VALID_LEX(lex)); + + memmove(lex->specials, specials, 256); +} + +static isc_result_t +new_source(isc_lex_t *lex, bool is_file, bool need_close, void *input, + const char *name) { + inputsource *source; + + source = isc_mem_get(lex->mctx, sizeof(*source)); + source->result = ISC_R_SUCCESS; + source->is_file = is_file; + source->need_close = need_close; + source->at_eof = false; + source->last_was_eol = lex->last_was_eol; + source->input = input; + source->name = isc_mem_strdup(lex->mctx, name); + source->pushback = NULL; + isc_buffer_allocate(lex->mctx, &source->pushback, + (unsigned int)lex->max_token); + source->ignored = 0; + source->line = 1; + ISC_LIST_INITANDPREPEND(lex->sources, source, link); + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_lex_openfile(isc_lex_t *lex, const char *filename) { + isc_result_t result; + FILE *stream = NULL; + + /* + * Open 'filename' and make it the current input source for 'lex'. + */ + + REQUIRE(VALID_LEX(lex)); + + result = isc_stdio_open(filename, "r", &stream); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = new_source(lex, true, true, stream, filename); + if (result != ISC_R_SUCCESS) { + (void)fclose(stream); + } + return (result); +} + +isc_result_t +isc_lex_openstream(isc_lex_t *lex, FILE *stream) { + char name[128]; + + /* + * Make 'stream' the current input source for 'lex'. + */ + + REQUIRE(VALID_LEX(lex)); + + snprintf(name, sizeof(name), "stream-%p", stream); + + return (new_source(lex, true, false, stream, name)); +} + +isc_result_t +isc_lex_openbuffer(isc_lex_t *lex, isc_buffer_t *buffer) { + char name[128]; + + /* + * Make 'buffer' the current input source for 'lex'. + */ + + REQUIRE(VALID_LEX(lex)); + + snprintf(name, sizeof(name), "buffer-%p", buffer); + + return (new_source(lex, false, false, buffer, name)); +} + +isc_result_t +isc_lex_close(isc_lex_t *lex) { + inputsource *source; + + /* + * Close the most recently opened object (i.e. file or buffer). + */ + + REQUIRE(VALID_LEX(lex)); + + source = HEAD(lex->sources); + if (source == NULL) { + return (ISC_R_NOMORE); + } + + ISC_LIST_UNLINK(lex->sources, source, link); + lex->last_was_eol = source->last_was_eol; + if (source->is_file) { + if (source->need_close) { + (void)fclose((FILE *)(source->input)); + } + } + isc_mem_free(lex->mctx, source->name); + isc_buffer_free(&source->pushback); + isc_mem_put(lex->mctx, source, sizeof(*source)); + + return (ISC_R_SUCCESS); +} + +typedef enum { + lexstate_start, + lexstate_crlf, + lexstate_string, + lexstate_number, + lexstate_maybecomment, + lexstate_ccomment, + lexstate_ccommentend, + lexstate_eatline, + lexstate_qstring, + lexstate_btext, + lexstate_vpair, + lexstate_vpairstart, + lexstate_qvpair, +} lexstate; + +#define IWSEOL (ISC_LEXOPT_INITIALWS | ISC_LEXOPT_EOL) + +static void +pushback(inputsource *source, int c) { + REQUIRE(source->pushback->current > 0); + if (c == EOF) { + source->at_eof = false; + return; + } + source->pushback->current--; + if (c == '\n') { + source->line--; + } +} + +static isc_result_t +pushandgrow(isc_lex_t *lex, inputsource *source, int c) { + if (isc_buffer_availablelength(source->pushback) == 0) { + isc_buffer_t *tbuf = NULL; + unsigned int oldlen; + isc_region_t used; + isc_result_t result; + + oldlen = isc_buffer_length(source->pushback); + isc_buffer_allocate(lex->mctx, &tbuf, oldlen * 2); + isc_buffer_usedregion(source->pushback, &used); + result = isc_buffer_copyregion(tbuf, &used); + INSIST(result == ISC_R_SUCCESS); + tbuf->current = source->pushback->current; + isc_buffer_free(&source->pushback); + source->pushback = tbuf; + } + isc_buffer_putuint8(source->pushback, (uint8_t)c); + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_lex_gettoken(isc_lex_t *lex, unsigned int options, isc_token_t *tokenp) { + inputsource *source; + int c; + bool done = false; + bool no_comments = false; + bool escaped = false; + lexstate state = lexstate_start; + lexstate saved_state = lexstate_start; + isc_buffer_t *buffer; + FILE *stream; + char *curr, *prev; + size_t remaining; + uint32_t as_ulong; + unsigned int saved_options; + isc_result_t result; + + /* + * Get the next token. + */ + + REQUIRE(VALID_LEX(lex)); + source = HEAD(lex->sources); + REQUIRE(tokenp != NULL); + + if (source == NULL) { + if ((options & ISC_LEXOPT_NOMORE) != 0) { + tokenp->type = isc_tokentype_nomore; + return (ISC_R_SUCCESS); + } + return (ISC_R_NOMORE); + } + + if (source->result != ISC_R_SUCCESS) { + return (source->result); + } + + lex->saved_paren_count = lex->paren_count; + source->saved_line = source->line; + + if (isc_buffer_remaininglength(source->pushback) == 0 && source->at_eof) + { + if ((options & ISC_LEXOPT_DNSMULTILINE) != 0 && + lex->paren_count != 0) + { + lex->paren_count = 0; + return (ISC_R_UNBALANCED); + } + if ((options & ISC_LEXOPT_BTEXT) != 0 && lex->brace_count != 0) + { + lex->brace_count = 0; + return (ISC_R_UNBALANCED); + } + if ((options & ISC_LEXOPT_EOF) != 0) { + tokenp->type = isc_tokentype_eof; + return (ISC_R_SUCCESS); + } + return (ISC_R_EOF); + } + + isc_buffer_compact(source->pushback); + + saved_options = options; + if ((options & ISC_LEXOPT_DNSMULTILINE) != 0 && lex->paren_count > 0) { + options &= ~IWSEOL; + } + + curr = lex->data; + *curr = '\0'; + + prev = NULL; + remaining = lex->max_token; + +#ifdef HAVE_FLOCKFILE + if (source->is_file) { + flockfile(source->input); + } +#endif /* ifdef HAVE_FLOCKFILE */ + + do { + if (isc_buffer_remaininglength(source->pushback) == 0) { + if (source->is_file) { + stream = source->input; + +#if defined(HAVE_FLOCKFILE) && defined(HAVE_GETC_UNLOCKED) + c = getc_unlocked(stream); +#else /* if defined(HAVE_FLOCKFILE) && defined(HAVE_GETC_UNLOCKED) */ + c = getc(stream); +#endif /* if defined(HAVE_FLOCKFILE) && defined(HAVE_GETC_UNLOCKED) */ + if (c == EOF) { + if (ferror(stream)) { + source->result = ISC_R_IOERROR; + result = source->result; + goto done; + } + source->at_eof = true; + } + } else { + buffer = source->input; + + if (buffer->current == buffer->used) { + c = EOF; + source->at_eof = true; + } else { + c = *((unsigned char *)buffer->base + + buffer->current); + buffer->current++; + } + } + if (c != EOF) { + source->result = pushandgrow(lex, source, c); + if (source->result != ISC_R_SUCCESS) { + result = source->result; + goto done; + } + } + } + + if (!source->at_eof) { + if (state == lexstate_start) { + /* Token has not started yet. */ + source->ignored = isc_buffer_consumedlength( + source->pushback); + } + c = isc_buffer_getuint8(source->pushback); + } else { + c = EOF; + } + + if (c == '\n') { + source->line++; + } + + if (lex->comment_ok && !no_comments) { + if (!escaped && c == ';' && + ((lex->comments & ISC_LEXCOMMENT_DNSMASTERFILE) != + 0)) + { + saved_state = state; + state = lexstate_eatline; + no_comments = true; + continue; + } else if (c == '/' && + (lex->comments & + (ISC_LEXCOMMENT_C | + ISC_LEXCOMMENT_CPLUSPLUS)) != 0) + { + saved_state = state; + state = lexstate_maybecomment; + no_comments = true; + continue; + } else if (c == '#' && ((lex->comments & + ISC_LEXCOMMENT_SHELL) != 0)) + { + saved_state = state; + state = lexstate_eatline; + no_comments = true; + continue; + } + } + + no_read: + /* INSIST(c == EOF || (c >= 0 && c <= 255)); */ + switch (state) { + case lexstate_start: + if (c == EOF) { + lex->last_was_eol = false; + if ((options & ISC_LEXOPT_DNSMULTILINE) != 0 && + lex->paren_count != 0) + { + lex->paren_count = 0; + result = ISC_R_UNBALANCED; + goto done; + } + if ((options & ISC_LEXOPT_BTEXT) != 0 && + lex->brace_count != 0) + { + lex->brace_count = 0; + result = ISC_R_UNBALANCED; + goto done; + } + if ((options & ISC_LEXOPT_EOF) == 0) { + result = ISC_R_EOF; + goto done; + } + tokenp->type = isc_tokentype_eof; + done = true; + } else if (c == ' ' || c == '\t') { + if (lex->last_was_eol && + (options & ISC_LEXOPT_INITIALWS) != 0) + { + lex->last_was_eol = false; + tokenp->type = isc_tokentype_initialws; + tokenp->value.as_char = c; + done = true; + } + } else if (c == '\n') { + if ((options & ISC_LEXOPT_EOL) != 0) { + tokenp->type = isc_tokentype_eol; + done = true; + } + lex->last_was_eol = true; + } else if (c == '\r') { + if ((options & ISC_LEXOPT_EOL) != 0) { + state = lexstate_crlf; + } + } else if (c == '"' && + (options & ISC_LEXOPT_QSTRING) != 0) + { + lex->last_was_eol = false; + no_comments = true; + state = lexstate_qstring; + } else if (lex->specials[c]) { + lex->last_was_eol = false; + if ((c == '(' || c == ')') && + (options & ISC_LEXOPT_DNSMULTILINE) != 0) + { + if (c == '(') { + if (lex->paren_count == 0) { + options &= ~IWSEOL; + } + lex->paren_count++; + } else { + if (lex->paren_count == 0) { + result = + ISC_R_UNBALANCED; + goto done; + } + lex->paren_count--; + if (lex->paren_count == 0) { + options = saved_options; + } + } + continue; + } else if (c == '{' && + (options & ISC_LEXOPT_BTEXT) != 0) + { + if (lex->brace_count != 0) { + result = ISC_R_UNBALANCED; + goto done; + } + lex->brace_count++; + options &= ~IWSEOL; + state = lexstate_btext; + no_comments = true; + continue; + } + tokenp->type = isc_tokentype_special; + tokenp->value.as_char = c; + done = true; + } else if (isdigit((unsigned char)c) && + (options & ISC_LEXOPT_NUMBER) != 0) + { + lex->last_was_eol = false; + if ((options & ISC_LEXOPT_OCTAL) != 0 && + (c == '8' || c == '9')) + { + state = lexstate_string; + } else { + state = lexstate_number; + } + goto no_read; + } else { + lex->last_was_eol = false; + state = lexstate_string; + goto no_read; + } + break; + case lexstate_crlf: + if (c != '\n') { + pushback(source, c); + } + tokenp->type = isc_tokentype_eol; + done = true; + lex->last_was_eol = true; + break; + case lexstate_number: + if (c == EOF || !isdigit((unsigned char)c)) { + if (c == ' ' || c == '\t' || c == '\r' || + c == '\n' || c == EOF || lex->specials[c]) + { + int base; + if ((options & ISC_LEXOPT_OCTAL) != 0) { + base = 8; + } else if ((options & + ISC_LEXOPT_CNUMBER) != 0) + { + base = 0; + } else { + base = 10; + } + pushback(source, c); + + result = isc_parse_uint32( + &as_ulong, lex->data, base); + if (result == ISC_R_SUCCESS) { + tokenp->type = + isc_tokentype_number; + tokenp->value.as_ulong = + as_ulong; + } else if (result == ISC_R_BADNUMBER) { + isc_tokenvalue_t *v; + + tokenp->type = + isc_tokentype_string; + v = &(tokenp->value); + v->as_textregion.base = + lex->data; + v->as_textregion.length = + (unsigned int)(lex->max_token - + remaining); + } else { + goto done; + } + done = true; + continue; + } else if ((options & ISC_LEXOPT_CNUMBER) == + 0 || + ((c != 'x' && c != 'X') || + (curr != &lex->data[1]) || + (lex->data[0] != '0'))) + { + /* Above test supports hex numbers */ + state = lexstate_string; + } + } else if ((options & ISC_LEXOPT_OCTAL) != 0 && + (c == '8' || c == '9')) + { + state = lexstate_string; + } + if (remaining == 0U) { + result = grow_data(lex, &remaining, &curr, + &prev); + if (result != ISC_R_SUCCESS) { + goto done; + } + } + INSIST(remaining > 0U); + *curr++ = c; + *curr = '\0'; + remaining--; + break; + case lexstate_string: + if (!escaped && c == '=' && + (options & ISC_LEXOPT_VPAIR) != 0) + { + if (remaining == 0U) { + result = grow_data(lex, &remaining, + &curr, &prev); + if (result != ISC_R_SUCCESS) { + goto done; + } + } + INSIST(remaining > 0U); + *curr++ = c; + *curr = '\0'; + remaining--; + state = lexstate_vpairstart; + break; + } + FALLTHROUGH; + case lexstate_vpairstart: + if (state == lexstate_vpairstart) { + if (c == '"' && + (options & ISC_LEXOPT_QVPAIR) != 0) + { + no_comments = true; + state = lexstate_qvpair; + break; + } + state = lexstate_vpair; + } + FALLTHROUGH; + case lexstate_vpair: + /* + * EOF needs to be checked before lex->specials[c] + * as lex->specials[EOF] is not a good idea. + */ + if (c == '\r' || c == '\n' || c == EOF || + (!escaped && + (c == ' ' || c == '\t' || lex->specials[c]))) + { + pushback(source, c); + if (source->result != ISC_R_SUCCESS) { + result = source->result; + goto done; + } + if (escaped && c == EOF) { + result = ISC_R_UNEXPECTEDEND; + goto done; + } + tokenp->type = (state == lexstate_string) + ? isc_tokentype_string + : isc_tokentype_vpair; + tokenp->value.as_textregion.base = lex->data; + tokenp->value.as_textregion.length = + (unsigned int)(lex->max_token - + remaining); + done = true; + continue; + } + if ((options & ISC_LEXOPT_ESCAPE) != 0) { + escaped = (!escaped && c == '\\') ? true + : false; + } + if (remaining == 0U) { + result = grow_data(lex, &remaining, &curr, + &prev); + if (result != ISC_R_SUCCESS) { + goto done; + } + } + INSIST(remaining > 0U); + *curr++ = c; + *curr = '\0'; + remaining--; + break; + case lexstate_maybecomment: + if (c == '*' && (lex->comments & ISC_LEXCOMMENT_C) != 0) + { + state = lexstate_ccomment; + continue; + } else if (c == '/' && (lex->comments & + ISC_LEXCOMMENT_CPLUSPLUS) != 0) + { + state = lexstate_eatline; + continue; + } + pushback(source, c); + c = '/'; + no_comments = false; + state = saved_state; + goto no_read; + case lexstate_ccomment: + if (c == EOF) { + result = ISC_R_UNEXPECTEDEND; + goto done; + } + if (c == '*') { + state = lexstate_ccommentend; + } + break; + case lexstate_ccommentend: + if (c == EOF) { + result = ISC_R_UNEXPECTEDEND; + goto done; + } + if (c == '/') { + /* + * C-style comments become a single space. + * We do this to ensure that a comment will + * act as a delimiter for strings and + * numbers. + */ + c = ' '; + no_comments = false; + state = saved_state; + goto no_read; + } else if (c != '*') { + state = lexstate_ccomment; + } + break; + case lexstate_eatline: + if ((c == '\n') || (c == EOF)) { + no_comments = false; + state = saved_state; + goto no_read; + } + break; + case lexstate_qstring: + case lexstate_qvpair: + if (c == EOF) { + result = ISC_R_UNEXPECTEDEND; + goto done; + } + if (c == '"') { + if (escaped) { + escaped = false; + /* + * Overwrite the preceding backslash. + */ + INSIST(prev != NULL); + *prev = '"'; + } else { + tokenp->type = + (state == lexstate_qstring) + ? isc_tokentype_qstring + : isc_tokentype_qvpair; + tokenp->value.as_textregion.base = + lex->data; + tokenp->value.as_textregion.length = + (unsigned int)(lex->max_token - + remaining); + no_comments = false; + done = true; + } + } else { + if (c == '\n' && !escaped && + (options & ISC_LEXOPT_QSTRINGMULTILINE) == + 0) + { + pushback(source, c); + result = ISC_R_UNBALANCEDQUOTES; + goto done; + } + if (c == '\\' && !escaped) { + escaped = true; + } else { + escaped = false; + } + if (remaining == 0U) { + result = grow_data(lex, &remaining, + &curr, &prev); + if (result != ISC_R_SUCCESS) { + goto done; + } + } + INSIST(remaining > 0U); + prev = curr; + *curr++ = c; + *curr = '\0'; + remaining--; + } + break; + case lexstate_btext: + if (c == EOF) { + result = ISC_R_UNEXPECTEDEND; + goto done; + } + if (c == '{') { + if (escaped) { + escaped = false; + } else { + lex->brace_count++; + } + } else if (c == '}') { + if (escaped) { + escaped = false; + } else { + INSIST(lex->brace_count > 0); + lex->brace_count--; + } + + if (lex->brace_count == 0) { + tokenp->type = isc_tokentype_btext; + tokenp->value.as_textregion.base = + lex->data; + tokenp->value.as_textregion.length = + (unsigned int)(lex->max_token - + remaining); + no_comments = false; + done = true; + break; + } + } + + if (c == '\\' && !escaped) { + escaped = true; + } else { + escaped = false; + } + + if (remaining == 0U) { + result = grow_data(lex, &remaining, &curr, + &prev); + if (result != ISC_R_SUCCESS) { + goto done; + } + } + INSIST(remaining > 0U); + prev = curr; + *curr++ = c; + *curr = '\0'; + remaining--; + break; + default: + FATAL_ERROR(__FILE__, __LINE__, "Unexpected state %d", + state); + } + } while (!done); + + result = ISC_R_SUCCESS; +done: +#ifdef HAVE_FLOCKFILE + if (source->is_file) { + funlockfile(source->input); + } +#endif /* ifdef HAVE_FLOCKFILE */ + return (result); +} + +isc_result_t +isc_lex_getmastertoken(isc_lex_t *lex, isc_token_t *token, + isc_tokentype_t expect, bool eol) { + unsigned int options = ISC_LEXOPT_EOL | ISC_LEXOPT_EOF | + ISC_LEXOPT_DNSMULTILINE | ISC_LEXOPT_ESCAPE; + isc_result_t result; + + if (expect == isc_tokentype_vpair) { + options |= ISC_LEXOPT_VPAIR; + } else if (expect == isc_tokentype_qvpair) { + options |= ISC_LEXOPT_VPAIR; + options |= ISC_LEXOPT_QVPAIR; + } else if (expect == isc_tokentype_qstring) { + options |= ISC_LEXOPT_QSTRING; + } else if (expect == isc_tokentype_number) { + options |= ISC_LEXOPT_NUMBER; + } + result = isc_lex_gettoken(lex, options, token); + if (result == ISC_R_RANGE) { + isc_lex_ungettoken(lex, token); + } + if (result != ISC_R_SUCCESS) { + return (result); + } + + if (eol && ((token->type == isc_tokentype_eol) || + (token->type == isc_tokentype_eof))) + { + return (ISC_R_SUCCESS); + } + if (token->type == isc_tokentype_string && + (expect == isc_tokentype_qstring || expect == isc_tokentype_qvpair)) + { + return (ISC_R_SUCCESS); + } + if (token->type == isc_tokentype_vpair && + expect == isc_tokentype_qvpair) + { + return (ISC_R_SUCCESS); + } + if (token->type != expect) { + isc_lex_ungettoken(lex, token); + if (token->type == isc_tokentype_eol || + token->type == isc_tokentype_eof) + { + return (ISC_R_UNEXPECTEDEND); + } + if (expect == isc_tokentype_number) { + return (ISC_R_BADNUMBER); + } + return (ISC_R_UNEXPECTEDTOKEN); + } + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_lex_getoctaltoken(isc_lex_t *lex, isc_token_t *token, bool eol) { + unsigned int options = ISC_LEXOPT_EOL | ISC_LEXOPT_EOF | + ISC_LEXOPT_DNSMULTILINE | ISC_LEXOPT_ESCAPE | + ISC_LEXOPT_NUMBER | ISC_LEXOPT_OCTAL; + isc_result_t result; + + result = isc_lex_gettoken(lex, options, token); + if (result == ISC_R_RANGE) { + isc_lex_ungettoken(lex, token); + } + if (result != ISC_R_SUCCESS) { + return (result); + } + + if (eol && ((token->type == isc_tokentype_eol) || + (token->type == isc_tokentype_eof))) + { + return (ISC_R_SUCCESS); + } + if (token->type != isc_tokentype_number) { + isc_lex_ungettoken(lex, token); + if (token->type == isc_tokentype_eol || + token->type == isc_tokentype_eof) + { + return (ISC_R_UNEXPECTEDEND); + } + return (ISC_R_BADNUMBER); + } + return (ISC_R_SUCCESS); +} + +void +isc_lex_ungettoken(isc_lex_t *lex, isc_token_t *tokenp) { + inputsource *source; + /* + * Unget the current token. + */ + + REQUIRE(VALID_LEX(lex)); + source = HEAD(lex->sources); + REQUIRE(source != NULL); + REQUIRE(tokenp != NULL); + REQUIRE(isc_buffer_consumedlength(source->pushback) != 0 || + tokenp->type == isc_tokentype_eof); + + UNUSED(tokenp); + + isc_buffer_first(source->pushback); + lex->paren_count = lex->saved_paren_count; + source->line = source->saved_line; + source->at_eof = false; +} + +void +isc_lex_getlasttokentext(isc_lex_t *lex, isc_token_t *tokenp, isc_region_t *r) { + inputsource *source; + + REQUIRE(VALID_LEX(lex)); + source = HEAD(lex->sources); + REQUIRE(source != NULL); + REQUIRE(tokenp != NULL); + REQUIRE(isc_buffer_consumedlength(source->pushback) != 0 || + tokenp->type == isc_tokentype_eof); + + UNUSED(tokenp); + + INSIST(source->ignored <= isc_buffer_consumedlength(source->pushback)); + r->base = (unsigned char *)isc_buffer_base(source->pushback) + + source->ignored; + r->length = isc_buffer_consumedlength(source->pushback) - + source->ignored; +} + +char * +isc_lex_getsourcename(isc_lex_t *lex) { + inputsource *source; + + REQUIRE(VALID_LEX(lex)); + source = HEAD(lex->sources); + + if (source == NULL) { + return (NULL); + } + + return (source->name); +} + +unsigned long +isc_lex_getsourceline(isc_lex_t *lex) { + inputsource *source; + + REQUIRE(VALID_LEX(lex)); + source = HEAD(lex->sources); + + if (source == NULL) { + return (0); + } + + return (source->line); +} + +isc_result_t +isc_lex_setsourcename(isc_lex_t *lex, const char *name) { + inputsource *source; + char *newname; + + REQUIRE(VALID_LEX(lex)); + source = HEAD(lex->sources); + + if (source == NULL) { + return (ISC_R_NOTFOUND); + } + newname = isc_mem_strdup(lex->mctx, name); + isc_mem_free(lex->mctx, source->name); + source->name = newname; + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_lex_setsourceline(isc_lex_t *lex, unsigned long line) { + inputsource *source; + + REQUIRE(VALID_LEX(lex)); + source = HEAD(lex->sources); + + if (source == NULL) { + return (ISC_R_NOTFOUND); + } + + source->line = line; + return (ISC_R_SUCCESS); +} + +bool +isc_lex_isfile(isc_lex_t *lex) { + inputsource *source; + + REQUIRE(VALID_LEX(lex)); + + source = HEAD(lex->sources); + + if (source == NULL) { + return (false); + } + + return (source->is_file); +} diff --git a/lib/isc/lfsr.c b/lib/isc/lfsr.c new file mode 100644 index 0000000..633b4a9 --- /dev/null +++ b/lib/isc/lfsr.c @@ -0,0 +1,153 @@ +/* + * 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. + */ + +/*! \file */ + +#include <inttypes.h> +#include <stddef.h> +#include <stdlib.h> + +#include <isc/assertions.h> +#include <isc/lfsr.h> +#include <isc/util.h> + +#define VALID_LFSR(x) (x != NULL) + +void +isc_lfsr_init(isc_lfsr_t *lfsr, uint32_t state, unsigned int bits, uint32_t tap, + unsigned int count, isc_lfsrreseed_t reseed, void *arg) { + REQUIRE(VALID_LFSR(lfsr)); + REQUIRE(8 <= bits && bits <= 32); + REQUIRE(tap != 0); + + lfsr->state = state; + lfsr->bits = bits; + lfsr->tap = tap; + lfsr->count = count; + lfsr->reseed = reseed; + lfsr->arg = arg; + + if (count == 0 && reseed != NULL) { + reseed(lfsr, arg); + } + if (lfsr->state == 0) { + lfsr->state = 0xffffffffU >> (32 - lfsr->bits); + } +} + +/*! + * Return the next state of the lfsr. + */ +static uint32_t +lfsr_generate(isc_lfsr_t *lfsr) { + /* + * If the previous state is zero, we must fill it with something + * here, or we will begin to generate an extremely predictable output. + * + * First, give the reseed function a crack at it. If the state is + * still 0, set it to all ones. + */ + if (lfsr->state == 0) { + if (lfsr->reseed != NULL) { + lfsr->reseed(lfsr, lfsr->arg); + } + if (lfsr->state == 0) { + lfsr->state = 0xffffffffU >> (32 - lfsr->bits); + } + } + + if (lfsr->state & 0x01) { + lfsr->state = (lfsr->state >> 1) ^ lfsr->tap; + return (1); + } else { + lfsr->state >>= 1; + return (0); + } +} + +void +isc_lfsr_generate(isc_lfsr_t *lfsr, void *data, unsigned int count) { + unsigned char *p; + unsigned int bit; + unsigned int byte; + + REQUIRE(VALID_LFSR(lfsr)); + REQUIRE(data != NULL); + REQUIRE(count > 0); + + p = data; + byte = count; + + while (byte--) { + *p = 0; + for (bit = 0; bit < 7; bit++) { + *p |= lfsr_generate(lfsr); + *p <<= 1; + } + *p |= lfsr_generate(lfsr); + p++; + } + + if (lfsr->count != 0 && lfsr->reseed != NULL) { + if (lfsr->count <= count * 8) { + lfsr->reseed(lfsr, lfsr->arg); + } else { + lfsr->count -= (count * 8); + } + } +} + +static uint32_t +lfsr_skipgenerate(isc_lfsr_t *lfsr, unsigned int skip) { + while (skip--) { + (void)lfsr_generate(lfsr); + } + + (void)lfsr_generate(lfsr); + + return (lfsr->state); +} + +/* + * Skip "skip" states in "lfsr". + */ +void +isc_lfsr_skip(isc_lfsr_t *lfsr, unsigned int skip) { + REQUIRE(VALID_LFSR(lfsr)); + + while (skip--) { + (void)lfsr_generate(lfsr); + } +} + +/* + * Skip states in lfsr1 and lfsr2 using the other's current state. + * Return the final state of lfsr1 ^ lfsr2. + */ +uint32_t +isc_lfsr_generate32(isc_lfsr_t *lfsr1, isc_lfsr_t *lfsr2) { + uint32_t state1, state2; + uint32_t skip1, skip2; + + REQUIRE(VALID_LFSR(lfsr1)); + REQUIRE(VALID_LFSR(lfsr2)); + + skip1 = lfsr1->state & 0x01; + skip2 = lfsr2->state & 0x01; + + /* cross-skip. */ + state1 = lfsr_skipgenerate(lfsr1, skip2); + state2 = lfsr_skipgenerate(lfsr2, skip1); + + return (state1 ^ state2); +} diff --git a/lib/isc/lib.c b/lib/isc/lib.c new file mode 100644 index 0000000..848ed58 --- /dev/null +++ b/lib/isc/lib.c @@ -0,0 +1,85 @@ +/* + * 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. + */ + +/*! \file */ + +#include <isc/bind9.h> +#include <isc/iterated_hash.h> +#include <isc/lib.h> +#include <isc/mem.h> +#include <isc/util.h> + +#include "config.h" +#include "mem_p.h" +#include "tls_p.h" +#include "trampoline_p.h" + +#ifndef ISC_CONSTRUCTOR +#error Either __attribute__((constructor|destructor))__ or DllMain support needed to compile BIND 9. +#endif + +/*** + *** Functions + ***/ + +void +isc_lib_register(void) { + isc_bind9 = false; +} + +#ifdef WIN32 +int +isc_lib_ntservice(int(WINAPI *mainfunc)(int argc, char *argv[]), int argc, + char *argv[]) { + isc__trampoline_t *trampoline = isc__trampoline_get(NULL, NULL); + int r; + + isc__trampoline_attach(trampoline); + + r = mainfunc(argc, argv); + + isc__trampoline_detach(trampoline); + + return (r); +} +#endif /* ifdef WIN32 */ + +void +isc__initialize(void) ISC_CONSTRUCTOR; +void +isc__shutdown(void) ISC_DESTRUCTOR; + +void +isc__initialize(void) { + isc__mem_initialize(); + isc__tls_initialize(); + isc__trampoline_initialize(); +} + +void +isc__shutdown(void) { + isc__trampoline_shutdown(); + isc__tls_shutdown(); + isc__mem_shutdown(); +} + +/* + * This is a workaround for situation when libisc is statically linked. Under + * normal situation, the linker throws out all symbols from compilation unit + * when no symbols are used in the final binary. This empty function must be + * called at least once from different compilation unit (mem.c in this case). + */ +void +isc_enable_constructors() { + /* do nothing */ +} diff --git a/lib/isc/lib_p.h b/lib/isc/lib_p.h new file mode 100644 index 0000000..56bbbe3 --- /dev/null +++ b/lib/isc/lib_p.h @@ -0,0 +1,20 @@ +/* + * 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. + */ + +#pragma once + +void +isc__initialize(void); + +void +isc__shutdown(void); diff --git a/lib/isc/log.c b/lib/isc/log.c new file mode 100644 index 0000000..b15d66a --- /dev/null +++ b/lib/isc/log.c @@ -0,0 +1,1896 @@ +/* + * 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. + */ + +/*! \file */ + +#include <errno.h> +#include <inttypes.h> +#include <limits.h> +#include <stdbool.h> +#include <stdlib.h> +#include <sys/types.h> /* dev_t FreeBSD 2.1 */ +#include <time.h> + +#include <isc/atomic.h> +#include <isc/dir.h> +#include <isc/file.h> +#include <isc/log.h> +#include <isc/magic.h> +#include <isc/mem.h> +#include <isc/platform.h> +#include <isc/print.h> +#include <isc/rwlock.h> +#include <isc/stat.h> +#include <isc/stdio.h> +#include <isc/string.h> +#include <isc/time.h> +#include <isc/util.h> + +#define LCTX_MAGIC ISC_MAGIC('L', 'c', 't', 'x') +#define VALID_CONTEXT(lctx) ISC_MAGIC_VALID(lctx, LCTX_MAGIC) + +#define LCFG_MAGIC ISC_MAGIC('L', 'c', 'f', 'g') +#define VALID_CONFIG(lcfg) ISC_MAGIC_VALID(lcfg, LCFG_MAGIC) + +#define RDLOCK(lp) RWLOCK(lp, isc_rwlocktype_read); +#define WRLOCK(lp) RWLOCK(lp, isc_rwlocktype_write); +#define RDUNLOCK(lp) RWUNLOCK(lp, isc_rwlocktype_read); +#define WRUNLOCK(lp) RWUNLOCK(lp, isc_rwlocktype_write); + +/* + * XXXDCL make dynamic? + */ +#define LOG_BUFFER_SIZE (8 * 1024) + +/*! + * This is the structure that holds each named channel. A simple linked + * list chains all of the channels together, so an individual channel is + * found by doing strcmp()s with the names down the list. Their should + * be no performance penalty from this as it is expected that the number + * of named channels will be no more than a dozen or so, and name lookups + * from the head of the list are only done when isc_log_usechannel() is + * called, which should also be very infrequent. + */ +typedef struct isc_logchannel isc_logchannel_t; + +struct isc_logchannel { + char *name; + unsigned int type; + int level; + unsigned int flags; + isc_logdestination_t destination; + ISC_LINK(isc_logchannel_t) link; +}; + +/*! + * The logchannellist structure associates categories and modules with + * channels. First the appropriate channellist is found based on the + * category, and then each structure in the linked list is checked for + * a matching module. It is expected that the number of channels + * associated with any given category will be very short, no more than + * three or four in the more unusual cases. + */ +typedef struct isc_logchannellist isc_logchannellist_t; + +struct isc_logchannellist { + const isc_logmodule_t *module; + isc_logchannel_t *channel; + ISC_LINK(isc_logchannellist_t) link; +}; + +/*! + * This structure is used to remember messages for pruning via + * isc_log_[v]write1(). + */ +typedef struct isc_logmessage isc_logmessage_t; + +struct isc_logmessage { + char *text; + isc_time_t time; + ISC_LINK(isc_logmessage_t) link; +}; + +/*! + * The isc_logconfig structure is used to store the configurable information + * about where messages are actually supposed to be sent -- the information + * that could changed based on some configuration file, as opposed to the + * the category/module specification of isc_log_[v]write[1] that is compiled + * into a program, or the debug_level which is dynamic state information. + */ +struct isc_logconfig { + unsigned int magic; + isc_log_t *lctx; + ISC_LIST(isc_logchannel_t) channels; + ISC_LIST(isc_logchannellist_t) * channellists; + unsigned int channellist_count; + unsigned int duplicate_interval; + int_fast32_t highest_level; + char *tag; + bool dynamic; +}; + +/*! + * This isc_log structure provides the context for the isc_log functions. + * The log context locks itself in isc_log_doit, the internal backend to + * isc_log_write. The locking is necessary both to provide exclusive access + * to the buffer into which the message is formatted and to guard against + * competing threads trying to write to the same syslog resource. (On + * some systems, such as BSD/OS, stdio is thread safe but syslog is not.) + * Unfortunately, the lock cannot guard against a _different_ logging + * context in the same program competing for syslog's attention. Thus + * There Can Be Only One, but this is not enforced. + * XXXDCL enforce it? + * + * Note that the category and module information is not locked. + * This is because in the usual case, only one isc_log_t is ever created + * in a program, and the category/module registration happens only once. + * XXXDCL it might be wise to add more locking overall. + */ +struct isc_log { + /* Not locked. */ + unsigned int magic; + isc_mem_t *mctx; + isc_logcategory_t *categories; + unsigned int category_count; + isc_logmodule_t *modules; + unsigned int module_count; + atomic_int_fast32_t debug_level; + isc_rwlock_t lcfg_rwl; + /* Locked by isc_log lcfg_rwl */ + isc_logconfig_t *logconfig; + isc_mutex_t lock; + /* Locked by isc_log lock. */ + char buffer[LOG_BUFFER_SIZE]; + ISC_LIST(isc_logmessage_t) messages; + atomic_bool dynamic; + atomic_int_fast32_t highest_level; +}; + +/*! + * Used when ISC_LOG_PRINTLEVEL is enabled for a channel. + */ +static const char *log_level_strings[] = { "debug", "info", "notice", + "warning", "error", "critical" }; + +/*! + * Used to convert ISC_LOG_* priorities into syslog priorities. + * XXXDCL This will need modification for NT. + */ +static const int syslog_map[] = { LOG_DEBUG, LOG_INFO, LOG_NOTICE, + LOG_WARNING, LOG_ERR, LOG_CRIT }; + +/*! + * When adding new categories, a corresponding ISC_LOGCATEGORY_foo + * definition needs to be added to <isc/log.h>. + * + * The default category is provided so that the internal default can + * be overridden. Since the default is always looked up as the first + * channellist in the log context, it must come first in isc_categories[]. + */ +LIBISC_EXTERNAL_DATA isc_logcategory_t isc_categories[] = { { "default", + 0 }, /* "default + must come + first. */ + { "general", 0 }, + { NULL, 0 } }; + +/*! + * See above comment for categories on LIBISC_EXTERNAL_DATA, and apply it to + * modules. + */ +LIBISC_EXTERNAL_DATA isc_logmodule_t isc_modules[] = { + { "socket", 0 }, { "time", 0 }, { "interface", 0 }, { "timer", 0 }, + { "file", 0 }, { "netmgr", 0 }, { "other", 0 }, { NULL, 0 } +}; + +/*! + * This essentially constant structure must be filled in at run time, + * because its channel member is pointed to a channel that is created + * dynamically with isc_log_createchannel. + */ +static isc_logchannellist_t default_channel; + +/*! + * libisc logs to this context. + */ +LIBISC_EXTERNAL_DATA isc_log_t *isc_lctx = NULL; + +/*! + * Forward declarations. + */ +static void +assignchannel(isc_logconfig_t *lcfg, unsigned int category_id, + const isc_logmodule_t *module, isc_logchannel_t *channel); + +static void +sync_channellist(isc_logconfig_t *lcfg); + +static void +sync_highest_level(isc_log_t *lctx, isc_logconfig_t *lcfg); + +static isc_result_t +greatest_version(isc_logfile_t *file, int versions, int *greatest); + +static void +isc_log_doit(isc_log_t *lctx, isc_logcategory_t *category, + isc_logmodule_t *module, int level, bool write_once, + const char *format, va_list args) ISC_FORMAT_PRINTF(6, 0); + +/*@{*/ +/*! + * Convenience macros. + */ + +#define FACILITY(channel) (channel->destination.facility) +#define FILE_NAME(channel) (channel->destination.file.name) +#define FILE_STREAM(channel) (channel->destination.file.stream) +#define FILE_VERSIONS(channel) (channel->destination.file.versions) +#define FILE_SUFFIX(channel) (channel->destination.file.suffix) +#define FILE_MAXSIZE(channel) (channel->destination.file.maximum_size) +#define FILE_MAXREACHED(channel) (channel->destination.file.maximum_reached) + +/*@}*/ +/**** +**** Public interfaces. +****/ + +/* + * Establish a new logging context, with default channels. + */ +void +isc_log_create(isc_mem_t *mctx, isc_log_t **lctxp, isc_logconfig_t **lcfgp) { + isc_log_t *lctx; + isc_logconfig_t *lcfg = NULL; + + REQUIRE(mctx != NULL); + REQUIRE(lctxp != NULL && *lctxp == NULL); + REQUIRE(lcfgp == NULL || *lcfgp == NULL); + + lctx = isc_mem_get(mctx, sizeof(*lctx)); + lctx->mctx = NULL; + isc_mem_attach(mctx, &lctx->mctx); + lctx->categories = NULL; + lctx->category_count = 0; + lctx->modules = NULL; + lctx->module_count = 0; + atomic_init(&lctx->debug_level, 0); + + ISC_LIST_INIT(lctx->messages); + + isc_mutex_init(&lctx->lock); + isc_rwlock_init(&lctx->lcfg_rwl, 0, 0); + + /* + * Normally setting the magic number is the last step done + * in a creation function, but a valid log context is needed + * by isc_log_registercategories and isc_logconfig_create. + * If either fails, the lctx is destroyed and not returned + * to the caller. + */ + lctx->magic = LCTX_MAGIC; + + isc_log_registercategories(lctx, isc_categories); + isc_log_registermodules(lctx, isc_modules); + isc_logconfig_create(lctx, &lcfg); + + sync_channellist(lcfg); + + lctx->logconfig = lcfg; + + atomic_init(&lctx->highest_level, lcfg->highest_level); + atomic_init(&lctx->dynamic, lcfg->dynamic); + + *lctxp = lctx; + if (lcfgp != NULL) { + *lcfgp = lcfg; + } +} + +void +isc_logconfig_create(isc_log_t *lctx, isc_logconfig_t **lcfgp) { + isc_logconfig_t *lcfg; + isc_logdestination_t destination; + int level = ISC_LOG_INFO; + + REQUIRE(lcfgp != NULL && *lcfgp == NULL); + REQUIRE(VALID_CONTEXT(lctx)); + + lcfg = isc_mem_get(lctx->mctx, sizeof(*lcfg)); + + lcfg->lctx = lctx; + lcfg->channellists = NULL; + lcfg->channellist_count = 0; + lcfg->duplicate_interval = 0; + lcfg->highest_level = level; + lcfg->tag = NULL; + lcfg->dynamic = false; + ISC_LIST_INIT(lcfg->channels); + lcfg->magic = LCFG_MAGIC; + + /* + * Create the default channels: + * default_syslog, default_stderr, default_debug and null. + */ + destination.facility = LOG_DAEMON; + isc_log_createchannel(lcfg, "default_syslog", ISC_LOG_TOSYSLOG, level, + &destination, 0); + + destination.file.stream = stderr; + destination.file.name = NULL; + destination.file.versions = ISC_LOG_ROLLNEVER; + destination.file.suffix = isc_log_rollsuffix_increment; + destination.file.maximum_size = 0; + isc_log_createchannel(lcfg, "default_stderr", ISC_LOG_TOFILEDESC, level, + &destination, ISC_LOG_PRINTTIME); + + /* + * Set the default category's channel to default_stderr, + * which is at the head of the channels list because it was + * just created. + */ + default_channel.channel = ISC_LIST_HEAD(lcfg->channels); + + destination.file.stream = stderr; + destination.file.name = NULL; + destination.file.versions = ISC_LOG_ROLLNEVER; + destination.file.suffix = isc_log_rollsuffix_increment; + destination.file.maximum_size = 0; + isc_log_createchannel(lcfg, "default_debug", ISC_LOG_TOFILEDESC, + ISC_LOG_DYNAMIC, &destination, ISC_LOG_PRINTTIME); + + isc_log_createchannel(lcfg, "null", ISC_LOG_TONULL, ISC_LOG_DYNAMIC, + NULL, 0); + + *lcfgp = lcfg; +} + +void +isc_logconfig_use(isc_log_t *lctx, isc_logconfig_t *lcfg) { + isc_logconfig_t *old_cfg; + + REQUIRE(VALID_CONTEXT(lctx)); + REQUIRE(VALID_CONFIG(lcfg)); + REQUIRE(lcfg->lctx == lctx); + + /* + * Ensure that lcfg->channellist_count == lctx->category_count. + * They won't be equal if isc_log_usechannel has not been called + * since any call to isc_log_registercategories. + */ + sync_channellist(lcfg); + + WRLOCK(&lctx->lcfg_rwl); + old_cfg = lctx->logconfig; + lctx->logconfig = lcfg; + sync_highest_level(lctx, lcfg); + WRUNLOCK(&lctx->lcfg_rwl); + + isc_logconfig_destroy(&old_cfg); +} + +void +isc_log_destroy(isc_log_t **lctxp) { + isc_log_t *lctx; + isc_logconfig_t *lcfg; + isc_mem_t *mctx; + isc_logmessage_t *message; + + REQUIRE(lctxp != NULL && VALID_CONTEXT(*lctxp)); + + lctx = *lctxp; + *lctxp = NULL; + mctx = lctx->mctx; + + /* Stop the logging as a first thing */ + atomic_store_release(&lctx->debug_level, 0); + atomic_store_release(&lctx->highest_level, 0); + atomic_store_release(&lctx->dynamic, false); + + WRLOCK(&lctx->lcfg_rwl); + lcfg = lctx->logconfig; + lctx->logconfig = NULL; + WRUNLOCK(&lctx->lcfg_rwl); + + if (lcfg != NULL) { + isc_logconfig_destroy(&lcfg); + } + + isc_rwlock_destroy(&lctx->lcfg_rwl); + isc_mutex_destroy(&lctx->lock); + + while ((message = ISC_LIST_HEAD(lctx->messages)) != NULL) { + ISC_LIST_UNLINK(lctx->messages, message, link); + + isc_mem_put(mctx, message, + sizeof(*message) + strlen(message->text) + 1); + } + + lctx->buffer[0] = '\0'; + lctx->categories = NULL; + lctx->category_count = 0; + lctx->modules = NULL; + lctx->module_count = 0; + lctx->mctx = NULL; + lctx->magic = 0; + + isc_mem_putanddetach(&mctx, lctx, sizeof(*lctx)); +} + +void +isc_logconfig_destroy(isc_logconfig_t **lcfgp) { + isc_logconfig_t *lcfg; + isc_mem_t *mctx; + isc_logchannel_t *channel; + char *filename; + unsigned int i; + + REQUIRE(lcfgp != NULL && VALID_CONFIG(*lcfgp)); + + lcfg = *lcfgp; + *lcfgp = NULL; + + /* + * This function cannot be called with a logconfig that is in + * use by a log context. + */ + REQUIRE(lcfg->lctx != NULL); + + RDLOCK(&lcfg->lctx->lcfg_rwl); + REQUIRE(lcfg->lctx->logconfig != lcfg); + RDUNLOCK(&lcfg->lctx->lcfg_rwl); + + mctx = lcfg->lctx->mctx; + + while ((channel = ISC_LIST_HEAD(lcfg->channels)) != NULL) { + ISC_LIST_UNLINK(lcfg->channels, channel, link); + + if (channel->type == ISC_LOG_TOFILE) { + /* + * The filename for the channel may have ultimately + * started its life in user-land as a const string, + * but in isc_log_createchannel it gets copied + * into writable memory and is not longer truly const. + */ + DE_CONST(FILE_NAME(channel), filename); + isc_mem_free(mctx, filename); + + if (FILE_STREAM(channel) != NULL) { + (void)fclose(FILE_STREAM(channel)); + } + } + + isc_mem_free(mctx, channel->name); + isc_mem_put(mctx, channel, sizeof(*channel)); + } + + for (i = 0; i < lcfg->channellist_count; i++) { + isc_logchannellist_t *item; + while ((item = ISC_LIST_HEAD(lcfg->channellists[i])) != NULL) { + ISC_LIST_UNLINK(lcfg->channellists[i], item, link); + isc_mem_put(mctx, item, sizeof(*item)); + } + } + + if (lcfg->channellist_count > 0) { + isc_mem_put(mctx, lcfg->channellists, + lcfg->channellist_count * + sizeof(ISC_LIST(isc_logchannellist_t))); + } + + lcfg->dynamic = false; + if (lcfg->tag != NULL) { + isc_mem_free(lcfg->lctx->mctx, lcfg->tag); + } + lcfg->tag = NULL; + lcfg->highest_level = 0; + lcfg->duplicate_interval = 0; + lcfg->magic = 0; + + isc_mem_put(mctx, lcfg, sizeof(*lcfg)); +} + +void +isc_log_registercategories(isc_log_t *lctx, isc_logcategory_t categories[]) { + isc_logcategory_t *catp; + + REQUIRE(VALID_CONTEXT(lctx)); + REQUIRE(categories != NULL && categories[0].name != NULL); + + /* + * XXXDCL This somewhat sleazy situation of using the last pointer + * in one category array to point to the next array exists because + * this registration function returns void and I didn't want to have + * change everything that used it by making it return an isc_result_t. + * It would need to do that if it had to allocate memory to store + * pointers to each array passed in. + */ + if (lctx->categories == NULL) { + lctx->categories = categories; + } else { + /* + * Adjust the last (NULL) pointer of the already registered + * categories to point to the incoming array. + */ + for (catp = lctx->categories; catp->name != NULL;) { + if (catp->id == UINT_MAX) { + /* + * The name pointer points to the next array. + * Ick. + */ + DE_CONST(catp->name, catp); + } else { + catp++; + } + } + + catp->name = (void *)categories; + catp->id = UINT_MAX; + } + + /* + * Update the id number of the category with its new global id. + */ + for (catp = categories; catp->name != NULL; catp++) { + catp->id = lctx->category_count++; + } +} + +isc_logcategory_t * +isc_log_categorybyname(isc_log_t *lctx, const char *name) { + isc_logcategory_t *catp; + + REQUIRE(VALID_CONTEXT(lctx)); + REQUIRE(name != NULL); + + for (catp = lctx->categories; catp->name != NULL;) { + if (catp->id == UINT_MAX) { + /* + * catp is neither modified nor returned to the + * caller, so removing its const qualifier is ok. + */ + DE_CONST(catp->name, catp); + } else { + if (strcmp(catp->name, name) == 0) { + return (catp); + } + catp++; + } + } + + return (NULL); +} + +void +isc_log_registermodules(isc_log_t *lctx, isc_logmodule_t modules[]) { + isc_logmodule_t *modp; + + REQUIRE(VALID_CONTEXT(lctx)); + REQUIRE(modules != NULL && modules[0].name != NULL); + + /* + * XXXDCL This somewhat sleazy situation of using the last pointer + * in one category array to point to the next array exists because + * this registration function returns void and I didn't want to have + * change everything that used it by making it return an isc_result_t. + * It would need to do that if it had to allocate memory to store + * pointers to each array passed in. + */ + if (lctx->modules == NULL) { + lctx->modules = modules; + } else { + /* + * Adjust the last (NULL) pointer of the already registered + * modules to point to the incoming array. + */ + for (modp = lctx->modules; modp->name != NULL;) { + if (modp->id == UINT_MAX) { + /* + * The name pointer points to the next array. + * Ick. + */ + DE_CONST(modp->name, modp); + } else { + modp++; + } + } + + modp->name = (void *)modules; + modp->id = UINT_MAX; + } + + /* + * Update the id number of the module with its new global id. + */ + for (modp = modules; modp->name != NULL; modp++) { + modp->id = lctx->module_count++; + } +} + +isc_logmodule_t * +isc_log_modulebyname(isc_log_t *lctx, const char *name) { + isc_logmodule_t *modp; + + REQUIRE(VALID_CONTEXT(lctx)); + REQUIRE(name != NULL); + + for (modp = lctx->modules; modp->name != NULL;) { + if (modp->id == UINT_MAX) { + /* + * modp is neither modified nor returned to the + * caller, so removing its const qualifier is ok. + */ + DE_CONST(modp->name, modp); + } else { + if (strcmp(modp->name, name) == 0) { + return (modp); + } + modp++; + } + } + + return (NULL); +} + +void +isc_log_createchannel(isc_logconfig_t *lcfg, const char *name, + unsigned int type, int level, + const isc_logdestination_t *destination, + unsigned int flags) { + isc_logchannel_t *channel; + isc_mem_t *mctx; + unsigned int permitted = ISC_LOG_PRINTALL | ISC_LOG_DEBUGONLY | + ISC_LOG_BUFFERED | ISC_LOG_ISO8601 | + ISC_LOG_UTC; + + REQUIRE(VALID_CONFIG(lcfg)); + REQUIRE(name != NULL); + REQUIRE(type == ISC_LOG_TOSYSLOG || type == ISC_LOG_TOFILE || + type == ISC_LOG_TOFILEDESC || type == ISC_LOG_TONULL); + REQUIRE(destination != NULL || type == ISC_LOG_TONULL); + REQUIRE(level >= ISC_LOG_CRITICAL); + REQUIRE((flags & ~permitted) == 0); + + /* XXXDCL find duplicate names? */ + + mctx = lcfg->lctx->mctx; + + channel = isc_mem_get(mctx, sizeof(*channel)); + + channel->name = isc_mem_strdup(mctx, name); + + channel->type = type; + channel->level = level; + channel->flags = flags; + ISC_LINK_INIT(channel, link); + + switch (type) { + case ISC_LOG_TOSYSLOG: + FACILITY(channel) = destination->facility; + break; + + case ISC_LOG_TOFILE: + /* + * The file name is copied because greatest_version wants + * to scribble on it, so it needs to be definitely in + * writable memory. + */ + FILE_NAME(channel) = isc_mem_strdup(mctx, + destination->file.name); + FILE_STREAM(channel) = NULL; + FILE_VERSIONS(channel) = destination->file.versions; + FILE_SUFFIX(channel) = destination->file.suffix; + FILE_MAXSIZE(channel) = destination->file.maximum_size; + FILE_MAXREACHED(channel) = false; + break; + + case ISC_LOG_TOFILEDESC: + FILE_NAME(channel) = NULL; + FILE_STREAM(channel) = destination->file.stream; + FILE_MAXSIZE(channel) = 0; + FILE_VERSIONS(channel) = ISC_LOG_ROLLNEVER; + FILE_SUFFIX(channel) = isc_log_rollsuffix_increment; + break; + + case ISC_LOG_TONULL: + /* Nothing. */ + break; + + default: + UNREACHABLE(); + } + + ISC_LIST_PREPEND(lcfg->channels, channel, link); + + /* + * If default_stderr was redefined, make the default category + * point to the new default_stderr. + */ + if (strcmp(name, "default_stderr") == 0) { + default_channel.channel = channel; + } +} + +isc_result_t +isc_log_usechannel(isc_logconfig_t *lcfg, const char *name, + const isc_logcategory_t *category, + const isc_logmodule_t *module) { + isc_log_t *lctx; + isc_logchannel_t *channel; + + REQUIRE(VALID_CONFIG(lcfg)); + REQUIRE(name != NULL); + + lctx = lcfg->lctx; + + REQUIRE(category == NULL || category->id < lctx->category_count); + REQUIRE(module == NULL || module->id < lctx->module_count); + + for (channel = ISC_LIST_HEAD(lcfg->channels); channel != NULL; + channel = ISC_LIST_NEXT(channel, link)) + { + if (strcmp(name, channel->name) == 0) { + break; + } + } + + if (channel == NULL) { + return (ISC_R_NOTFOUND); + } + + if (category != NULL) { + assignchannel(lcfg, category->id, module, channel); + } else { + /* + * Assign to all categories. Note that this includes + * the default channel. + */ + for (size_t i = 0; i < lctx->category_count; i++) { + assignchannel(lcfg, i, module, channel); + } + } + + /* + * Update the highest logging level, if the current lcfg is in use. + */ + if (lcfg->lctx->logconfig == lcfg) { + sync_highest_level(lctx, lcfg); + } + + return (ISC_R_SUCCESS); +} + +void +isc_log_write(isc_log_t *lctx, isc_logcategory_t *category, + isc_logmodule_t *module, int level, const char *format, ...) { + va_list args; + + /* + * Contract checking is done in isc_log_doit(). + */ + + va_start(args, format); + isc_log_doit(lctx, category, module, level, false, format, args); + va_end(args); +} + +void +isc_log_vwrite(isc_log_t *lctx, isc_logcategory_t *category, + isc_logmodule_t *module, int level, const char *format, + va_list args) { + /* + * Contract checking is done in isc_log_doit(). + */ + isc_log_doit(lctx, category, module, level, false, format, args); +} + +void +isc_log_write1(isc_log_t *lctx, isc_logcategory_t *category, + isc_logmodule_t *module, int level, const char *format, ...) { + va_list args; + + /* + * Contract checking is done in isc_log_doit(). + */ + + va_start(args, format); + isc_log_doit(lctx, category, module, level, true, format, args); + va_end(args); +} + +void +isc_log_vwrite1(isc_log_t *lctx, isc_logcategory_t *category, + isc_logmodule_t *module, int level, const char *format, + va_list args) { + /* + * Contract checking is done in isc_log_doit(). + */ + isc_log_doit(lctx, category, module, level, true, format, args); +} + +void +isc_log_setcontext(isc_log_t *lctx) { + isc_lctx = lctx; +} + +void +isc_log_setdebuglevel(isc_log_t *lctx, unsigned int level) { + REQUIRE(VALID_CONTEXT(lctx)); + + atomic_store_release(&lctx->debug_level, level); + /* + * Close ISC_LOG_DEBUGONLY channels if level is zero. + */ + if (level == 0) { + RDLOCK(&lctx->lcfg_rwl); + isc_logconfig_t *lcfg = lctx->logconfig; + if (lcfg != NULL) { + LOCK(&lctx->lock); + for (isc_logchannel_t *channel = + ISC_LIST_HEAD(lcfg->channels); + channel != NULL; + channel = ISC_LIST_NEXT(channel, link)) + { + if (channel->type == ISC_LOG_TOFILE && + (channel->flags & ISC_LOG_DEBUGONLY) != 0 && + FILE_STREAM(channel) != NULL) + { + (void)fclose(FILE_STREAM(channel)); + FILE_STREAM(channel) = NULL; + } + } + UNLOCK(&lctx->lock); + } + RDUNLOCK(&lctx->lcfg_rwl); + } +} + +unsigned int +isc_log_getdebuglevel(isc_log_t *lctx) { + REQUIRE(VALID_CONTEXT(lctx)); + + return (atomic_load_acquire(&lctx->debug_level)); +} + +void +isc_log_setduplicateinterval(isc_logconfig_t *lcfg, unsigned int interval) { + REQUIRE(VALID_CONFIG(lcfg)); + + lcfg->duplicate_interval = interval; +} + +unsigned int +isc_log_getduplicateinterval(isc_logconfig_t *lcfg) { + REQUIRE(VALID_CONTEXT(lcfg)); + + return (lcfg->duplicate_interval); +} + +void +isc_log_settag(isc_logconfig_t *lcfg, const char *tag) { + REQUIRE(VALID_CONFIG(lcfg)); + + if (tag != NULL && *tag != '\0') { + if (lcfg->tag != NULL) { + isc_mem_free(lcfg->lctx->mctx, lcfg->tag); + } + lcfg->tag = isc_mem_strdup(lcfg->lctx->mctx, tag); + } else { + if (lcfg->tag != NULL) { + isc_mem_free(lcfg->lctx->mctx, lcfg->tag); + } + lcfg->tag = NULL; + } +} + +char * +isc_log_gettag(isc_logconfig_t *lcfg) { + REQUIRE(VALID_CONFIG(lcfg)); + + return (lcfg->tag); +} + +/* XXXDCL NT -- This interface will assuredly be changing. */ +void +isc_log_opensyslog(const char *tag, int options, int facility) { + (void)openlog(tag, options, facility); +} + +void +isc_log_closefilelogs(isc_log_t *lctx) { + REQUIRE(VALID_CONTEXT(lctx)); + + RDLOCK(&lctx->lcfg_rwl); + isc_logconfig_t *lcfg = lctx->logconfig; + if (lcfg != NULL) { + LOCK(&lctx->lock); + for (isc_logchannel_t *channel = ISC_LIST_HEAD(lcfg->channels); + channel != NULL; channel = ISC_LIST_NEXT(channel, link)) + { + if (channel->type == ISC_LOG_TOFILE && + FILE_STREAM(channel) != NULL) + { + (void)fclose(FILE_STREAM(channel)); + FILE_STREAM(channel) = NULL; + } + } + UNLOCK(&lctx->lock); + } + RDUNLOCK(&lctx->lcfg_rwl); +} + +/**** +**** Internal functions +****/ + +static void +assignchannel(isc_logconfig_t *lcfg, unsigned int category_id, + const isc_logmodule_t *module, isc_logchannel_t *channel) { + isc_logchannellist_t *new_item; + isc_log_t *lctx; + + REQUIRE(VALID_CONFIG(lcfg)); + + lctx = lcfg->lctx; + + REQUIRE(category_id < lctx->category_count); + REQUIRE(module == NULL || module->id < lctx->module_count); + REQUIRE(channel != NULL); + + /* + * Ensure lcfg->channellist_count == lctx->category_count. + */ + sync_channellist(lcfg); + + new_item = isc_mem_get(lctx->mctx, sizeof(*new_item)); + + new_item->channel = channel; + new_item->module = module; + ISC_LIST_INITANDPREPEND(lcfg->channellists[category_id], new_item, + link); + + /* + * Remember the highest logging level set by any channel in the + * logging config, so isc_log_doit() can quickly return if the + * message is too high to be logged by any channel. + */ + if (channel->type != ISC_LOG_TONULL) { + if (lcfg->highest_level < channel->level) { + lcfg->highest_level = channel->level; + } + if (channel->level == ISC_LOG_DYNAMIC) { + lcfg->dynamic = true; + } + } +} + +/* + * This would ideally be part of isc_log_registercategories(), except then + * that function would have to return isc_result_t instead of void. + */ +static void +sync_channellist(isc_logconfig_t *lcfg) { + unsigned int bytes; + isc_log_t *lctx; + void *lists; + + REQUIRE(VALID_CONFIG(lcfg)); + + lctx = lcfg->lctx; + + REQUIRE(lctx->category_count != 0); + + if (lctx->category_count == lcfg->channellist_count) { + return; + } + + bytes = lctx->category_count * sizeof(ISC_LIST(isc_logchannellist_t)); + + lists = isc_mem_get(lctx->mctx, bytes); + + memset(lists, 0, bytes); + + if (lcfg->channellist_count != 0) { + bytes = lcfg->channellist_count * + sizeof(ISC_LIST(isc_logchannellist_t)); + memmove(lists, lcfg->channellists, bytes); + isc_mem_put(lctx->mctx, lcfg->channellists, bytes); + } + + lcfg->channellists = lists; + lcfg->channellist_count = lctx->category_count; +} + +static void +sync_highest_level(isc_log_t *lctx, isc_logconfig_t *lcfg) { + atomic_store(&lctx->highest_level, lcfg->highest_level); + atomic_store(&lctx->dynamic, lcfg->dynamic); +} + +static isc_result_t +greatest_version(isc_logfile_t *file, int versions, int *greatestp) { + char *bname, *digit_end; + const char *dirname; + int version, greatest = -1; + size_t bnamelen; + isc_dir_t dir; + isc_result_t result; + char sep = '/'; +#ifdef _WIN32 + char *bname2; +#endif /* ifdef _WIN32 */ + + /* + * It is safe to DE_CONST the file.name because it was copied + * with isc_mem_strdup(). + */ + bname = strrchr(file->name, sep); +#ifdef _WIN32 + bname2 = strrchr(file->name, '\\'); + if ((bname != NULL && bname2 != NULL && bname2 > bname) || + (bname == NULL && bname2 != NULL)) + { + bname = bname2; + sep = '\\'; + } +#endif /* ifdef _WIN32 */ + if (bname != NULL) { + *bname++ = '\0'; + dirname = file->name; + } else { + DE_CONST(file->name, bname); + dirname = "."; + } + bnamelen = strlen(bname); + + isc_dir_init(&dir); + result = isc_dir_open(&dir, dirname); + + /* + * Replace the file separator if it was taken out. + */ + if (bname != file->name) { + *(bname - 1) = sep; + } + + /* + * Return if the directory open failed. + */ + if (result != ISC_R_SUCCESS) { + return (result); + } + + while (isc_dir_read(&dir) == ISC_R_SUCCESS) { + if (dir.entry.length > bnamelen && + strncmp(dir.entry.name, bname, bnamelen) == 0 && + dir.entry.name[bnamelen] == '.') + { + version = strtol(&dir.entry.name[bnamelen + 1], + &digit_end, 10); + /* + * Remove any backup files that exceed versions. + */ + if (*digit_end == '\0' && version >= versions) { + result = isc_file_remove(dir.entry.name); + if (result != ISC_R_SUCCESS && + result != ISC_R_FILENOTFOUND) + { + syslog(LOG_ERR, + "unable to remove " + "log file '%s': %s", + dir.entry.name, + isc_result_totext(result)); + } + } else if (*digit_end == '\0' && version > greatest) { + greatest = version; + } + } + } + isc_dir_close(&dir); + + *greatestp = greatest; + + return (ISC_R_SUCCESS); +} + +static void +insert_sort(int64_t to_keep[], int64_t versions, int64_t version) { + int i = 0; + while (i < versions && version < to_keep[i]) { + i++; + } + if (i == versions) { + return; + } + if (i < versions - 1) { + memmove(&to_keep[i + 1], &to_keep[i], + sizeof(to_keep[0]) * (versions - i - 1)); + } + to_keep[i] = version; +} + +static int64_t +last_to_keep(int64_t versions, isc_dir_t *dirp, char *bname, size_t bnamelen) { + int64_t to_keep[ISC_LOG_MAX_VERSIONS] = { 0 }; + int64_t version = 0; + + if (versions <= 0) { + return (INT64_MAX); + } + + if (versions > ISC_LOG_MAX_VERSIONS) { + versions = ISC_LOG_MAX_VERSIONS; + } + /* + * First we fill 'to_keep' structure using insertion sort + */ + memset(to_keep, 0, sizeof(to_keep)); + while (isc_dir_read(dirp) == ISC_R_SUCCESS) { + char *digit_end = NULL; + char *ename = NULL; + + if (dirp->entry.length <= bnamelen || + strncmp(dirp->entry.name, bname, bnamelen) != 0 || + dirp->entry.name[bnamelen] != '.') + { + continue; + } + + ename = &dirp->entry.name[bnamelen + 1]; + version = strtoull(ename, &digit_end, 10); + if (*digit_end == '\0') { + insert_sort(to_keep, versions, version); + } + } + + isc_dir_reset(dirp); + + /* + * to_keep[versions - 1] is the last one we want to keep + */ + return (to_keep[versions - 1]); +} + +static isc_result_t +remove_old_tsversions(isc_logfile_t *file, int versions) { + isc_result_t result; + char *bname = NULL, *digit_end = NULL; + const char *dirname = NULL; + int64_t version, last = INT64_MAX; + size_t bnamelen; + isc_dir_t dir; + char sep = '/'; +#ifdef _WIN32 + char *bname2; +#endif /* ifdef _WIN32 */ + /* + * It is safe to DE_CONST the file.name because it was copied + * with isc_mem_strdup(). + */ + bname = strrchr(file->name, sep); +#ifdef _WIN32 + bname2 = strrchr(file->name, '\\'); + if ((bname != NULL && bname2 != NULL && bname2 > bname) || + (bname == NULL && bname2 != NULL)) + { + bname = bname2; + sep = '\\'; + } +#endif /* ifdef _WIN32 */ + if (bname != NULL) { + *bname++ = '\0'; + dirname = file->name; + } else { + DE_CONST(file->name, bname); + dirname = "."; + } + bnamelen = strlen(bname); + + isc_dir_init(&dir); + result = isc_dir_open(&dir, dirname); + + /* + * Replace the file separator if it was taken out. + */ + if (bname != file->name) { + *(bname - 1) = sep; + } + + /* + * Return if the directory open failed. + */ + if (result != ISC_R_SUCCESS) { + return (result); + } + + last = last_to_keep(versions, &dir, bname, bnamelen); + + /* + * Then we remove all files that we don't want to_keep + */ + while (isc_dir_read(&dir) == ISC_R_SUCCESS) { + if (dir.entry.length > bnamelen && + strncmp(dir.entry.name, bname, bnamelen) == 0 && + dir.entry.name[bnamelen] == '.') + { + char *ename = &dir.entry.name[bnamelen + 1]; + version = strtoull(ename, &digit_end, 10); + /* + * Remove any backup files that exceed versions. + */ + if (*digit_end == '\0' && version < last) { + result = isc_file_remove(dir.entry.name); + if (result != ISC_R_SUCCESS && + result != ISC_R_FILENOTFOUND) + { + syslog(LOG_ERR, + "unable to remove " + "log file '%s': %s", + dir.entry.name, + isc_result_totext(result)); + } + } + } + } + + isc_dir_close(&dir); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +roll_increment(isc_logfile_t *file) { + int i, n, greatest; + char current[PATH_MAX + 1]; + char newpath[PATH_MAX + 1]; + const char *path; + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(file != NULL); + REQUIRE(file->versions != 0); + + path = file->name; + + if (file->versions == ISC_LOG_ROLLINFINITE) { + /* + * Find the first missing entry in the log file sequence. + */ + for (greatest = 0; greatest < INT_MAX; greatest++) { + n = snprintf(current, sizeof(current), "%s.%u", path, + (unsigned)greatest); + if (n >= (int)sizeof(current) || n < 0 || + !isc_file_exists(current)) + { + break; + } + } + } else { + /* + * Get the largest existing version and remove any + * version greater than the permitted version. + */ + result = greatest_version(file, file->versions, &greatest); + if (result != ISC_R_SUCCESS) { + return (result); + } + + /* + * Increment if greatest is not the actual maximum value. + */ + if (greatest < file->versions - 1) { + greatest++; + } + } + + for (i = greatest; i > 0; i--) { + result = ISC_R_SUCCESS; + n = snprintf(current, sizeof(current), "%s.%u", path, + (unsigned)(i - 1)); + if (n >= (int)sizeof(current) || n < 0) { + result = ISC_R_NOSPACE; + } + if (result == ISC_R_SUCCESS) { + n = snprintf(newpath, sizeof(newpath), "%s.%u", path, + (unsigned)i); + if (n >= (int)sizeof(newpath) || n < 0) { + result = ISC_R_NOSPACE; + } + } + if (result == ISC_R_SUCCESS) { + result = isc_file_rename(current, newpath); + } + if (result != ISC_R_SUCCESS && result != ISC_R_FILENOTFOUND) { + syslog(LOG_ERR, + "unable to rename log file '%s.%u' to " + "'%s.%u': %s", + path, i - 1, path, i, isc_result_totext(result)); + } + } + + n = snprintf(newpath, sizeof(newpath), "%s.0", path); + if (n >= (int)sizeof(newpath) || n < 0) { + result = ISC_R_NOSPACE; + } else { + result = isc_file_rename(path, newpath); + } + if (result != ISC_R_SUCCESS && result != ISC_R_FILENOTFOUND) { + syslog(LOG_ERR, "unable to rename log file '%s' to '%s.0': %s", + path, path, isc_result_totext(result)); + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +roll_timestamp(isc_logfile_t *file) { + int n; + char newts[PATH_MAX + 1]; + char newpath[PATH_MAX + 1]; + const char *path; + isc_time_t now; + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(file != NULL); + REQUIRE(file->versions != 0); + + path = file->name; + + /* + * First find all the logfiles and remove the oldest ones + * Save one fewer than file->versions because we'll be renaming + * the existing file to a timestamped version after this. + */ + if (file->versions != ISC_LOG_ROLLINFINITE) { + remove_old_tsversions(file, file->versions - 1); + } + + /* Then just rename the current logfile */ + isc_time_now(&now); + isc_time_formatshorttimestamp(&now, newts, PATH_MAX + 1); + n = snprintf(newpath, sizeof(newpath), "%s.%s", path, newts); + if (n >= (int)sizeof(newpath) || n < 0) { + result = ISC_R_NOSPACE; + } else { + result = isc_file_rename(path, newpath); + } + if (result != ISC_R_SUCCESS && result != ISC_R_FILENOTFOUND) { + syslog(LOG_ERR, "unable to rename log file '%s' to '%s.0': %s", + path, path, isc_result_totext(result)); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_logfile_roll(isc_logfile_t *file) { + isc_result_t result; + + REQUIRE(file != NULL); + + /* + * Do nothing (not even excess version trimming) if ISC_LOG_ROLLNEVER + * is specified. Apparently complete external control over the log + * files is desired. + */ + if (file->versions == ISC_LOG_ROLLNEVER) { + return (ISC_R_SUCCESS); + } else if (file->versions == 0) { + result = isc_file_remove(file->name); + if (result != ISC_R_SUCCESS && result != ISC_R_FILENOTFOUND) { + syslog(LOG_ERR, "unable to remove log file '%s': %s", + file->name, isc_result_totext(result)); + } + return (ISC_R_SUCCESS); + } + + switch (file->suffix) { + case isc_log_rollsuffix_increment: + return (roll_increment(file)); + case isc_log_rollsuffix_timestamp: + return (roll_timestamp(file)); + default: + return (ISC_R_UNEXPECTED); + } +} + +static isc_result_t +isc_log_open(isc_logchannel_t *channel) { + struct stat statbuf; + bool regular_file; + bool roll = false; + isc_result_t result = ISC_R_SUCCESS; + const char *path; + + REQUIRE(channel->type == ISC_LOG_TOFILE); + REQUIRE(FILE_STREAM(channel) == NULL); + + path = FILE_NAME(channel); + + REQUIRE(path != NULL && *path != '\0'); + + /* + * Determine type of file; only regular files will be + * version renamed, and only if the base file exists + * and either has no size limit or has reached its size limit. + */ + if (stat(path, &statbuf) == 0) { + regular_file = S_ISREG(statbuf.st_mode) ? true : false; + /* XXXDCL if not regular_file complain? */ + if ((FILE_MAXSIZE(channel) == 0 && + FILE_VERSIONS(channel) != ISC_LOG_ROLLNEVER) || + (FILE_MAXSIZE(channel) > 0 && + statbuf.st_size >= FILE_MAXSIZE(channel))) + { + roll = regular_file; + } + } else if (errno == ENOENT) { + regular_file = true; + POST(regular_file); + } else { + result = ISC_R_INVALIDFILE; + } + + /* + * Version control. + */ + if (result == ISC_R_SUCCESS && roll) { + if (FILE_VERSIONS(channel) == ISC_LOG_ROLLNEVER) { + return (ISC_R_MAXSIZE); + } + result = isc_logfile_roll(&channel->destination.file); + if (result != ISC_R_SUCCESS) { + if ((channel->flags & ISC_LOG_OPENERR) == 0) { + syslog(LOG_ERR, + "isc_log_open: isc_logfile_roll '%s' " + "failed: %s", + FILE_NAME(channel), + isc_result_totext(result)); + channel->flags |= ISC_LOG_OPENERR; + } + return (result); + } + } + + result = isc_stdio_open(path, "a", &FILE_STREAM(channel)); + + return (result); +} + +ISC_NO_SANITIZE_THREAD bool +isc_log_wouldlog(isc_log_t *lctx, int level) { + /* + * Try to avoid locking the mutex for messages which can't + * possibly be logged to any channels -- primarily debugging + * messages that the debug level is not high enough to print. + * + * If the level is (mathematically) less than or equal to the + * highest_level, or if there is a dynamic channel and the level is + * less than or equal to the debug level, the main loop must be + * entered to see if the message should really be output. + */ + if (lctx == NULL) { + return (false); + } + + int highest_level = atomic_load_acquire(&lctx->highest_level); + if (level <= highest_level) { + return (true); + } + if (atomic_load_acquire(&lctx->dynamic)) { + int debug_level = atomic_load_acquire(&lctx->debug_level); + if (level <= debug_level) { + return (true); + } + } + + return (false); +} + +static void +isc_log_doit(isc_log_t *lctx, isc_logcategory_t *category, + isc_logmodule_t *module, int level, bool write_once, + const char *format, va_list args) { + int syslog_level; + const char *time_string; + char local_time[64]; + char iso8601z_string[64]; + char iso8601l_string[64]; + char level_string[24] = { 0 }; + struct stat statbuf; + bool matched = false; + bool printtime, iso8601, utc, printtag, printcolon; + bool printcategory, printmodule, printlevel, buffered; + isc_logchannel_t *channel; + isc_logchannellist_t *category_channels; + isc_result_t result; + + REQUIRE(lctx == NULL || VALID_CONTEXT(lctx)); + REQUIRE(category != NULL); + REQUIRE(module != NULL); + REQUIRE(level != ISC_LOG_DYNAMIC); + REQUIRE(format != NULL); + + /* + * Programs can use libraries that use this logging code without + * wanting to do any logging, thus the log context is allowed to + * be non-existent. + */ + if (lctx == NULL) { + return; + } + + REQUIRE(category->id < lctx->category_count); + REQUIRE(module->id < lctx->module_count); + + if (!isc_log_wouldlog(lctx, level)) { + return; + } + + local_time[0] = '\0'; + iso8601l_string[0] = '\0'; + iso8601z_string[0] = '\0'; + + RDLOCK(&lctx->lcfg_rwl); + LOCK(&lctx->lock); + + lctx->buffer[0] = '\0'; + + isc_logconfig_t *lcfg = lctx->logconfig; + + category_channels = ISC_LIST_HEAD(lcfg->channellists[category->id]); + + /* + * XXXDCL add duplicate filtering? (To not write multiple times + * to the same source via various channels). + */ + do { + /* + * If the channel list end was reached and a match was + * made, everything is finished. + */ + if (category_channels == NULL && matched) { + break; + } + + if (category_channels == NULL && !matched && + category_channels != ISC_LIST_HEAD(lcfg->channellists[0])) + { + /* + * No category/module pair was explicitly + * configured. Try the category named "default". + */ + category_channels = + ISC_LIST_HEAD(lcfg->channellists[0]); + } + + if (category_channels == NULL && !matched) { + /* + * No matching module was explicitly configured + * for the category named "default". Use the + * internal default channel. + */ + category_channels = &default_channel; + } + + if (category_channels->module != NULL && + category_channels->module != module) + { + category_channels = ISC_LIST_NEXT(category_channels, + link); + continue; + } + + matched = true; + + channel = category_channels->channel; + category_channels = ISC_LIST_NEXT(category_channels, link); + + int_fast32_t dlevel = atomic_load_acquire(&lctx->debug_level); + if (((channel->flags & ISC_LOG_DEBUGONLY) != 0) && dlevel == 0) + { + continue; + } + + if (channel->level == ISC_LOG_DYNAMIC) { + if (dlevel < level) { + continue; + } + } else if (channel->level < level) { + continue; + } + + if ((channel->flags & ISC_LOG_PRINTTIME) != 0 && + local_time[0] == '\0') + { + isc_time_t isctime; + + TIME_NOW(&isctime); + + isc_time_formattimestamp(&isctime, local_time, + sizeof(local_time)); + isc_time_formatISO8601ms(&isctime, iso8601z_string, + sizeof(iso8601z_string)); + isc_time_formatISO8601Lms(&isctime, iso8601l_string, + sizeof(iso8601l_string)); + } + + if ((channel->flags & ISC_LOG_PRINTLEVEL) != 0 && + level_string[0] == '\0') + { + if (level < ISC_LOG_CRITICAL) { + snprintf(level_string, sizeof(level_string), + "level %d: ", level); + } else if (level > ISC_LOG_DYNAMIC) { + snprintf(level_string, sizeof(level_string), + "%s %d: ", log_level_strings[0], + level); + } else { + snprintf(level_string, sizeof(level_string), + "%s: ", log_level_strings[-level]); + } + } + + /* + * Only format the message once. + */ + if (lctx->buffer[0] == '\0') { + (void)vsnprintf(lctx->buffer, sizeof(lctx->buffer), + format, args); + + /* + * Check for duplicates. + */ + if (write_once) { + isc_logmessage_t *message, *next; + isc_time_t oldest; + isc_interval_t interval; + size_t size; + + isc_interval_set(&interval, + lcfg->duplicate_interval, 0); + + /* + * 'oldest' is the age of the oldest + * messages which fall within the + * duplicate_interval range. + */ + TIME_NOW(&oldest); + if (isc_time_subtract(&oldest, &interval, + &oldest) != ISC_R_SUCCESS) + { + /* + * Can't effectively do the + * checking without having a + * valid time. + */ + message = NULL; + } else { + message = ISC_LIST_HEAD(lctx->messages); + } + + while (message != NULL) { + if (isc_time_compare(&message->time, + &oldest) < 0) + { + /* + * This message is older + * than the + * duplicate_interval, + * so it should be + * dropped from the + * history. + * + * Setting the interval + * to be to be longer + * will obviously not + * cause the expired + * message to spring + * back into existence. + */ + next = ISC_LIST_NEXT(message, + link); + + ISC_LIST_UNLINK(lctx->messages, + message, link); + + isc_mem_put( + lctx->mctx, message, + sizeof(*message) + 1 + + strlen(message->text)); + + message = next; + continue; + } + + /* + * This message is in the + * duplicate filtering interval + * ... + */ + if (strcmp(lctx->buffer, + message->text) == 0) + { + /* + * ... and it is a + * duplicate. Unlock the + * mutex and get the + * hell out of Dodge. + */ + goto unlock; + } + + message = ISC_LIST_NEXT(message, link); + } + + /* + * It wasn't in the duplicate interval, + * so add it to the message list. + */ + size = sizeof(isc_logmessage_t) + + strlen(lctx->buffer) + 1; + message = isc_mem_get(lctx->mctx, size); + message->text = (char *)(message + 1); + size -= sizeof(isc_logmessage_t); + strlcpy(message->text, lctx->buffer, size); + TIME_NOW(&message->time); + ISC_LINK_INIT(message, link); + ISC_LIST_APPEND(lctx->messages, message, link); + } + } + + utc = ((channel->flags & ISC_LOG_UTC) != 0); + iso8601 = ((channel->flags & ISC_LOG_ISO8601) != 0); + printtime = ((channel->flags & ISC_LOG_PRINTTIME) != 0); + printtag = ((channel->flags & + (ISC_LOG_PRINTTAG | ISC_LOG_PRINTPREFIX)) != 0 && + lcfg->tag != NULL); + printcolon = ((channel->flags & ISC_LOG_PRINTTAG) != 0 && + lcfg->tag != NULL); + printcategory = ((channel->flags & ISC_LOG_PRINTCATEGORY) != 0); + printmodule = ((channel->flags & ISC_LOG_PRINTMODULE) != 0); + printlevel = ((channel->flags & ISC_LOG_PRINTLEVEL) != 0); + buffered = ((channel->flags & ISC_LOG_BUFFERED) != 0); + + if (printtime) { + if (iso8601) { + if (utc) { + time_string = iso8601z_string; + } else { + time_string = iso8601l_string; + } + } else { + time_string = local_time; + } + } else { + time_string = ""; + } + + switch (channel->type) { + case ISC_LOG_TOFILE: + if (FILE_MAXREACHED(channel)) { + /* + * If the file can be rolled, OR + * If the file no longer exists, OR + * If the file is less than the maximum + * size, (such as if it had been renamed + * and a new one touched, or it was + * truncated in place) + * ... then close it to trigger + * reopening. + */ + if (FILE_VERSIONS(channel) != + ISC_LOG_ROLLNEVER || + (stat(FILE_NAME(channel), &statbuf) != 0 && + errno == ENOENT) || + statbuf.st_size < FILE_MAXSIZE(channel)) + { + (void)fclose(FILE_STREAM(channel)); + FILE_STREAM(channel) = NULL; + FILE_MAXREACHED(channel) = false; + } else { + /* + * Eh, skip it. + */ + break; + } + } + + if (FILE_STREAM(channel) == NULL) { + result = isc_log_open(channel); + if (result != ISC_R_SUCCESS && + result != ISC_R_MAXSIZE && + (channel->flags & ISC_LOG_OPENERR) == 0) + { + syslog(LOG_ERR, + "isc_log_open '%s' " + "failed: %s", + FILE_NAME(channel), + isc_result_totext(result)); + channel->flags |= ISC_LOG_OPENERR; + } + if (result != ISC_R_SUCCESS) { + break; + } + channel->flags &= ~ISC_LOG_OPENERR; + } + FALLTHROUGH; + + case ISC_LOG_TOFILEDESC: + fprintf(FILE_STREAM(channel), "%s%s%s%s%s%s%s%s%s%s\n", + printtime ? time_string : "", + printtime ? " " : "", printtag ? lcfg->tag : "", + printcolon ? ": " : "", + printcategory ? category->name : "", + printcategory ? ": " : "", + printmodule ? (module != NULL ? module->name + : "no_module") + : "", + printmodule ? ": " : "", + printlevel ? level_string : "", lctx->buffer); + + if (!buffered) { + fflush(FILE_STREAM(channel)); + } + + /* + * If the file now exceeds its maximum size + * threshold, note it so that it will not be + * logged to any more. + */ + if (FILE_MAXSIZE(channel) > 0) { + INSIST(channel->type == ISC_LOG_TOFILE); + + /* XXXDCL NT fstat/fileno */ + /* XXXDCL complain if fstat fails? */ + if (fstat(fileno(FILE_STREAM(channel)), + &statbuf) >= 0 && + statbuf.st_size > FILE_MAXSIZE(channel)) + { + FILE_MAXREACHED(channel) = true; + } + } + + break; + + case ISC_LOG_TOSYSLOG: + if (level > 0) { + syslog_level = LOG_DEBUG; + } else if (level < ISC_LOG_CRITICAL) { + syslog_level = LOG_CRIT; + } else { + syslog_level = syslog_map[-level]; + } + + (void)syslog( + FACILITY(channel) | syslog_level, + "%s%s%s%s%s%s%s%s%s%s", + printtime ? time_string : "", + printtime ? " " : "", printtag ? lcfg->tag : "", + printcolon ? ": " : "", + printcategory ? category->name : "", + printcategory ? ": " : "", + printmodule ? (module != NULL ? module->name + : "no_module") + : "", + printmodule ? ": " : "", + printlevel ? level_string : "", lctx->buffer); + break; + + case ISC_LOG_TONULL: + break; + } + } while (1); + +unlock: + UNLOCK(&lctx->lock); + RDUNLOCK(&lctx->lcfg_rwl); +} diff --git a/lib/isc/managers.c b/lib/isc/managers.c new file mode 100644 index 0000000..ba4b21d --- /dev/null +++ b/lib/isc/managers.c @@ -0,0 +1,95 @@ +/* + * 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. + */ + +#include <isc/managers.h> +#include <isc/util.h> + +#include "netmgr_p.h" +#include "task_p.h" + +isc_result_t +isc_managers_create(isc_mem_t *mctx, size_t workers, size_t quantum, + isc_nm_t **netmgrp, isc_taskmgr_t **taskmgrp) { + isc_result_t result; + isc_taskmgr_t *taskmgr = NULL; + isc_nm_t *netmgr = NULL; + + REQUIRE(netmgrp != NULL && *netmgrp == NULL); + isc__netmgr_create(mctx, workers, &netmgr); + *netmgrp = netmgr; + INSIST(netmgr != NULL); + + REQUIRE(taskmgrp == NULL || *taskmgrp == NULL); + if (taskmgrp != NULL) { + INSIST(netmgr != NULL); + result = isc__taskmgr_create(mctx, quantum, netmgr, &taskmgr); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_managers_create() failed: %s", + isc_result_totext(result)); + goto fail; + } + *taskmgrp = taskmgr; + } + + return (ISC_R_SUCCESS); +fail: + isc_managers_destroy(netmgrp, taskmgrp); + + return (result); +} + +void +isc_managers_destroy(isc_nm_t **netmgrp, isc_taskmgr_t **taskmgrp) { + /* + * If we have a taskmgr to clean up, then we must also have a netmgr. + */ + REQUIRE(taskmgrp != NULL || netmgrp == NULL); + + /* + * The sequence of operations here is important: + * + * 1. Initiate shutdown of the taskmgr, sending shutdown events to + * all tasks that are not already shutting down. + */ + if (taskmgrp != NULL) { + INSIST(*taskmgrp != NULL); + isc__taskmgr_shutdown(*taskmgrp); + } + + /* + * 2. Initiate shutdown of the network manager, freeing clients + * and other resources and preventing new connections, but do + * not stop processing of existing events. + */ + if (netmgrp != NULL) { + INSIST(*netmgrp != NULL); + isc__netmgr_shutdown(*netmgrp); + } + + /* + * 3. Finish destruction of the task manager when all tasks + * have completed. + */ + if (taskmgrp != NULL) { + isc__taskmgr_destroy(taskmgrp); + } + + /* + * 4. Finish destruction of the netmgr, and wait until all + * references have been released. + */ + if (netmgrp != NULL) { + isc__netmgr_destroy(netmgrp); + } +} diff --git a/lib/isc/md.c b/lib/isc/md.c new file mode 100644 index 0000000..0c6a70d --- /dev/null +++ b/lib/isc/md.c @@ -0,0 +1,175 @@ +/* + * 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. + */ + +#include <stdio.h> + +#include <openssl/err.h> +#include <openssl/evp.h> +#include <openssl/opensslv.h> + +#include <isc/md.h> +#include <isc/util.h> + +#include "openssl_shim.h" + +isc_md_t * +isc_md_new(void) { + isc_md_t *md = EVP_MD_CTX_new(); + RUNTIME_CHECK(md != NULL); + return (md); +} + +void +isc_md_free(isc_md_t *md) { + if (ISC_UNLIKELY(md == NULL)) { + return; + } + + EVP_MD_CTX_free(md); +} + +isc_result_t +isc_md_init(isc_md_t *md, const isc_md_type_t *md_type) { + REQUIRE(md != NULL); + + if (md_type == NULL) { + return (ISC_R_NOTIMPLEMENTED); + } + + if (EVP_DigestInit_ex(md, md_type, NULL) != 1) { + return (ISC_R_CRYPTOFAILURE); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_md_reset(isc_md_t *md) { + REQUIRE(md != NULL); + + if (EVP_MD_CTX_reset(md) != 1) { + return (ISC_R_CRYPTOFAILURE); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_md_update(isc_md_t *md, const unsigned char *buf, const size_t len) { + REQUIRE(md != NULL); + + if (ISC_UNLIKELY(buf == NULL || len == 0)) { + return (ISC_R_SUCCESS); + } + + if (EVP_DigestUpdate(md, buf, len) != 1) { + return (ISC_R_CRYPTOFAILURE); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_md_final(isc_md_t *md, unsigned char *digest, unsigned int *digestlen) { + REQUIRE(md != NULL); + REQUIRE(digest != NULL); + + if (EVP_DigestFinal_ex(md, digest, digestlen) != 1) { + return (ISC_R_CRYPTOFAILURE); + } + + return (ISC_R_SUCCESS); +} + +const isc_md_type_t * +isc_md_get_md_type(isc_md_t *md) { + REQUIRE(md != NULL); + + return (EVP_MD_CTX_md(md)); +} + +size_t +isc_md_get_size(isc_md_t *md) { + REQUIRE(md != NULL); + + return (EVP_MD_CTX_size(md)); +} + +size_t +isc_md_get_block_size(isc_md_t *md) { + REQUIRE(md != NULL); + + return (EVP_MD_CTX_block_size(md)); +} + +size_t +isc_md_type_get_size(const isc_md_type_t *md_type) { + STATIC_ASSERT(ISC_MAX_MD_SIZE >= EVP_MAX_MD_SIZE, + "Change ISC_MAX_MD_SIZE to be greater than or equal to " + "EVP_MAX_MD_SIZE"); + if (md_type != NULL) { + return ((size_t)EVP_MD_size(md_type)); + } + + return (ISC_MAX_MD_SIZE); +} + +size_t +isc_md_type_get_block_size(const isc_md_type_t *md_type) { + STATIC_ASSERT(ISC_MAX_MD_SIZE >= EVP_MAX_MD_SIZE, + "Change ISC_MAX_MD_SIZE to be greater than or equal to " + "EVP_MAX_MD_SIZE"); + if (md_type != NULL) { + return ((size_t)EVP_MD_block_size(md_type)); + } + + return (ISC_MAX_MD_SIZE); +} + +isc_result_t +isc_md(const isc_md_type_t *md_type, const unsigned char *buf, const size_t len, + unsigned char *digest, unsigned int *digestlen) { + isc_md_t *md; + isc_result_t res; + + md = isc_md_new(); + + res = isc_md_init(md, md_type); + if (res != ISC_R_SUCCESS) { + goto end; + } + + res = isc_md_update(md, buf, len); + if (res != ISC_R_SUCCESS) { + goto end; + } + + res = isc_md_final(md, digest, digestlen); + if (res != ISC_R_SUCCESS) { + goto end; + } +end: + isc_md_free(md); + + return (res); +} + +#define md_register_algorithm(alg) \ + const isc_md_type_t *isc__md_##alg(void) { return (EVP_##alg()); } + +md_register_algorithm(md5); +md_register_algorithm(sha1); +md_register_algorithm(sha224); +md_register_algorithm(sha256); +md_register_algorithm(sha384); +md_register_algorithm(sha512); diff --git a/lib/isc/mem.c b/lib/isc/mem.c new file mode 100644 index 0000000..21b2d86 --- /dev/null +++ b/lib/isc/mem.c @@ -0,0 +1,2450 @@ +/* + * 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. + */ + +/*! \file */ + +#include <errno.h> +#include <inttypes.h> +#include <limits.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> + +#include <isc/bind9.h> +#include <isc/hash.h> +#include <isc/lib.h> +#include <isc/magic.h> +#include <isc/mem.h> +#include <isc/mutex.h> +#include <isc/once.h> +#include <isc/print.h> +#include <isc/refcount.h> +#include <isc/strerr.h> +#include <isc/string.h> +#include <isc/util.h> + +#ifdef HAVE_LIBXML2 +#include <libxml/xmlwriter.h> +#define ISC_XMLCHAR (const xmlChar *) +#endif /* HAVE_LIBXML2 */ + +#ifdef HAVE_JSON_C +#include <json_object.h> +#endif /* HAVE_JSON_C */ + +#include "mem_p.h" + +#define MCTXLOCK(m) LOCK(&m->lock) +#define MCTXUNLOCK(m) UNLOCK(&m->lock) + +#ifndef ISC_MEM_DEBUGGING +#define ISC_MEM_DEBUGGING 0 +#endif /* ifndef ISC_MEM_DEBUGGING */ +LIBISC_EXTERNAL_DATA unsigned int isc_mem_debugging = ISC_MEM_DEBUGGING; +LIBISC_EXTERNAL_DATA unsigned int isc_mem_defaultflags = ISC_MEMFLAG_DEFAULT; + +/* + * Constants. + */ + +#define DEF_MAX_SIZE 1100 +#define DEF_MEM_TARGET 4096 +#define ALIGNMENT_SIZE \ + 8U /*%< must be a power of 2, also update lib/dns/rbt.c */ +#define NUM_BASIC_BLOCKS 64 /*%< must be > 1 */ +#define TABLE_INCREMENT 1024 +#define DEBUG_TABLE_COUNT 512U + +/* + * Types. + */ +typedef struct isc__mem isc__mem_t; +typedef struct isc__mempool isc__mempool_t; + +#if ISC_MEM_TRACKLINES +typedef struct debuglink debuglink_t; +struct debuglink { + ISC_LINK(debuglink_t) link; + const void *ptr; + size_t size; + const char *file; + unsigned int line; +}; + +typedef ISC_LIST(debuglink_t) debuglist_t; + +#define FLARG_PASS , file, line +#define FLARG , const char *file, unsigned int line +#else /* if ISC_MEM_TRACKLINES */ +#define FLARG_PASS +#define FLARG +#endif /* if ISC_MEM_TRACKLINES */ + +typedef struct element element; +struct element { + element *next; +}; + +typedef struct { + /*! + * This structure must be ALIGNMENT_SIZE bytes. + */ + union { + size_t size; + isc__mem_t *ctx; + char bytes[ALIGNMENT_SIZE]; + } u; +} size_info; + +struct stats { + unsigned long gets; + unsigned long totalgets; + unsigned long blocks; + unsigned long freefrags; +}; + +#define MEM_MAGIC ISC_MAGIC('M', 'e', 'm', 'C') +#define VALID_CONTEXT(c) ISC_MAGIC_VALID(c, MEM_MAGIC) + +/* List of all active memory contexts. */ + +static ISC_LIST(isc__mem_t) contexts; + +static isc_once_t init_once = ISC_ONCE_INIT; +static isc_once_t shut_once = ISC_ONCE_INIT; +static isc_mutex_t contextslock; + +/*% + * Total size of lost memory due to a bug of external library. + * Locked by the global lock. + */ +static uint64_t totallost; + +/*% + * Memory allocation and free function definitions. + * isc__memalloc_t must deal with memory allocation failure + * and must never return NULL. + */ +typedef void *(*isc__memalloc_t)(size_t); +typedef void (*isc__memfree_t)(void *); + +struct isc__mem { + isc_mem_t common; + unsigned int flags; + isc_mutex_t lock; + isc__memalloc_t memalloc; + isc__memfree_t memfree; + size_t max_size; + bool checkfree; + struct stats *stats; + isc_refcount_t references; + char name[16]; + void *tag; + size_t total; + size_t inuse; + size_t maxinuse; + size_t malloced; + size_t maxmalloced; + size_t hi_water; + size_t lo_water; + bool hi_called; + bool is_overmem; + isc_mem_water_t water; + void *water_arg; + ISC_LIST(isc__mempool_t) pools; + unsigned int poolcnt; + + /* ISC_MEMFLAG_INTERNAL */ + size_t mem_target; + element **freelists; + element *basic_blocks; + unsigned char **basic_table; + unsigned int basic_table_count; + unsigned int basic_table_size; + unsigned char *lowest; + unsigned char *highest; + +#if ISC_MEM_TRACKLINES + debuglist_t *debuglist; + size_t debuglistcnt; +#endif /* if ISC_MEM_TRACKLINES */ + + ISC_LINK(isc__mem_t) link; +}; + +#define MEMPOOL_MAGIC ISC_MAGIC('M', 'E', 'M', 'p') +#define VALID_MEMPOOL(c) ISC_MAGIC_VALID(c, MEMPOOL_MAGIC) + +struct isc__mempool { + /* always unlocked */ + isc_mempool_t common; /*%< common header of mempool's */ + isc__mem_t *mctx; /*%< our memory context */ + ISC_LINK(isc__mempool_t) link; /*%< next pool in this mem context */ + element *items; /*%< low water item list */ + size_t size; /*%< size of each item on this pool */ + unsigned int maxalloc; /*%< max number of items allowed */ + unsigned int allocated; /*%< # of items currently given out */ + unsigned int freecount; /*%< # of items on reserved list */ + unsigned int freemax; /*%< # of items allowed on free list */ + unsigned int fillcount; /*%< # of items to fetch on each fill */ + /*%< Stats only. */ + unsigned int gets; /*%< # of requests to this pool */ + /*%< Debugging only. */ +#if ISC_MEMPOOL_NAMES + char name[16]; /*%< printed name in stats reports */ +#endif /* if ISC_MEMPOOL_NAMES */ +}; + +/* + * Private Inline-able. + */ + +#if !ISC_MEM_TRACKLINES +#define ADD_TRACE(a, b, c, d, e) +#define DELETE_TRACE(a, b, c, d, e) +#define ISC_MEMFUNC_SCOPE +#else /* if !ISC_MEM_TRACKLINES */ +#define TRACE_OR_RECORD (ISC_MEM_DEBUGTRACE | ISC_MEM_DEBUGRECORD) +#define ADD_TRACE(a, b, c, d, e) \ + do { \ + if (ISC_UNLIKELY((isc_mem_debugging & TRACE_OR_RECORD) != 0 && \ + b != NULL)) \ + add_trace_entry(a, b, c, d, e); \ + } while (0) +#define DELETE_TRACE(a, b, c, d, e) \ + do { \ + if (ISC_UNLIKELY((isc_mem_debugging & TRACE_OR_RECORD) != 0 && \ + b != NULL)) \ + delete_trace_entry(a, b, c, d, e); \ + } while (0) + +static void +print_active(isc__mem_t *ctx, FILE *out); + +#endif /* ISC_MEM_TRACKLINES */ + +static void * +isc___mem_get(isc_mem_t *ctx, size_t size FLARG); +static void +isc___mem_put(isc_mem_t *ctx, void *ptr, size_t size FLARG); +static void +isc___mem_putanddetach(isc_mem_t **ctxp, void *ptr, size_t size FLARG); +static void * +isc___mem_allocate(isc_mem_t *ctx, size_t size FLARG); +static void * +isc___mem_reallocate(isc_mem_t *ctx, void *ptr, size_t size FLARG); +static char * +isc___mem_strdup(isc_mem_t *mctx, const char *s FLARG); +static char * +isc___mem_strndup(isc_mem_t *mctx0, const char *s, size_t size FLARG); +static void +isc___mem_free(isc_mem_t *ctx, void *ptr FLARG); + +static isc_memmethods_t memmethods = { + isc___mem_get, isc___mem_put, isc___mem_putanddetach, + isc___mem_allocate, isc___mem_reallocate, isc___mem_strdup, + isc___mem_strndup, isc___mem_free, +}; + +#if ISC_MEM_TRACKLINES +/*! + * mctx must be locked. + */ +static void +add_trace_entry(isc__mem_t *mctx, const void *ptr, size_t size FLARG) { + debuglink_t *dl; + uint32_t hash; + uint32_t idx; + + if ((isc_mem_debugging & ISC_MEM_DEBUGTRACE) != 0) { + fprintf(stderr, "add %p size %zu file %s line %u mctx %p\n", + ptr, size, file, line, mctx); + } + + if (mctx->debuglist == NULL) { + return; + } + +#ifdef __COVERITY__ + /* + * Use simple conversion from pointer to hash to avoid + * tainting 'ptr' due to byte swap in isc_hash_function. + */ + hash = (uintptr_t)ptr >> 3; +#else + hash = isc_hash_function(&ptr, sizeof(ptr), true); +#endif + idx = hash % DEBUG_TABLE_COUNT; + + dl = malloc(sizeof(debuglink_t)); + INSIST(dl != NULL); + mctx->malloced += sizeof(debuglink_t); + if (mctx->malloced > mctx->maxmalloced) { + mctx->maxmalloced = mctx->malloced; + } + + ISC_LINK_INIT(dl, link); + dl->ptr = ptr; + dl->size = size; + dl->file = file; + dl->line = line; + + ISC_LIST_PREPEND(mctx->debuglist[idx], dl, link); + mctx->debuglistcnt++; +} + +static void +delete_trace_entry(isc__mem_t *mctx, const void *ptr, size_t size, + const char *file, unsigned int line) { + debuglink_t *dl; + uint32_t hash; + uint32_t idx; + + if ((isc_mem_debugging & ISC_MEM_DEBUGTRACE) != 0) { + fprintf(stderr, "del %p size %zu file %s line %u mctx %p\n", + ptr, size, file, line, mctx); + } + + if (mctx->debuglist == NULL) { + return; + } + +#ifdef __COVERITY__ + /* + * Use simple conversion from pointer to hash to avoid + * tainting 'ptr' due to byte swap in isc_hash_function. + */ + hash = (uintptr_t)ptr >> 3; +#else + hash = isc_hash_function(&ptr, sizeof(ptr), true); +#endif + idx = hash % DEBUG_TABLE_COUNT; + + dl = ISC_LIST_HEAD(mctx->debuglist[idx]); + while (ISC_LIKELY(dl != NULL)) { + if (ISC_UNLIKELY(dl->ptr == ptr)) { + ISC_LIST_UNLINK(mctx->debuglist[idx], dl, link); + mctx->malloced -= sizeof(*dl); + free(dl); + return; + } + dl = ISC_LIST_NEXT(dl, link); + } + + /* + * If we get here, we didn't find the item on the list. We're + * screwed. + */ + UNREACHABLE(); +} +#endif /* ISC_MEM_TRACKLINES */ + +static size_t +rmsize(size_t size) { + /* + * round down to ALIGNMENT_SIZE + */ + return (size & (~(ALIGNMENT_SIZE - 1))); +} + +static size_t +quantize(size_t size) { + /*! + * Round up the result in order to get a size big + * enough to satisfy the request and be aligned on ALIGNMENT_SIZE + * byte boundaries. + */ + + if (size == 0U) { + return (ALIGNMENT_SIZE); + } + return ((size + ALIGNMENT_SIZE - 1) & (~(ALIGNMENT_SIZE - 1))); +} + +static void +more_basic_blocks(isc__mem_t *ctx) { + void *tmp; + unsigned char *curr, *next; + unsigned char *first, *last; + unsigned char **table; + unsigned int table_size; + + /* Require: we hold the context lock. */ + + INSIST(ctx->basic_table_count <= ctx->basic_table_size); + if (ctx->basic_table_count == ctx->basic_table_size) { + table_size = ctx->basic_table_size + TABLE_INCREMENT; + table = (ctx->memalloc)(table_size * sizeof(unsigned char *)); + ctx->malloced += table_size * sizeof(unsigned char *); + if (ctx->malloced > ctx->maxmalloced) { + ctx->maxmalloced = ctx->malloced; + } + if (ctx->basic_table_size != 0) { + memmove(table, ctx->basic_table, + ctx->basic_table_size * + sizeof(unsigned char *)); + (ctx->memfree)(ctx->basic_table); + ctx->malloced -= ctx->basic_table_size * + sizeof(unsigned char *); + } + ctx->basic_table = table; + ctx->basic_table_size = table_size; + } + + tmp = (ctx->memalloc)(NUM_BASIC_BLOCKS * ctx->mem_target); + ctx->total += NUM_BASIC_BLOCKS * ctx->mem_target; + ctx->basic_table[ctx->basic_table_count] = tmp; + ctx->basic_table_count++; + ctx->malloced += NUM_BASIC_BLOCKS * ctx->mem_target; + if (ctx->malloced > ctx->maxmalloced) { + ctx->maxmalloced = ctx->malloced; + } + + curr = tmp; + next = curr + ctx->mem_target; + for (int i = 0; i < (NUM_BASIC_BLOCKS - 1); i++) { + ((element *)curr)->next = (element *)next; + curr = next; + next += ctx->mem_target; + } + /* + * curr is now pointing at the last block in the + * array. + */ + ((element *)curr)->next = NULL; + first = tmp; + last = first + NUM_BASIC_BLOCKS * ctx->mem_target - 1; + if (first < ctx->lowest || ctx->lowest == NULL) { + ctx->lowest = first; + } + if (last > ctx->highest) { + ctx->highest = last; + } + ctx->basic_blocks = tmp; +} + +static void +more_frags(isc__mem_t *ctx, size_t new_size) { + int frags; + size_t total_size; + void *tmp; + unsigned char *curr, *next; + + /*! + * Try to get more fragments by chopping up a basic block. + */ + + if (ctx->basic_blocks == NULL) { + more_basic_blocks(ctx); + } + INSIST(ctx->basic_blocks != NULL); + + total_size = ctx->mem_target; + tmp = ctx->basic_blocks; + ctx->basic_blocks = ctx->basic_blocks->next; + frags = (int)(total_size / new_size); + ctx->stats[new_size].blocks++; + ctx->stats[new_size].freefrags += frags; + /* + * Set up a linked-list of blocks of size + * "new_size". + */ + curr = tmp; + next = curr + new_size; + total_size -= new_size; + for (int i = 0; i < (frags - 1); i++) { + ((element *)curr)->next = (element *)next; + curr = next; + next += new_size; + total_size -= new_size; + } + /* + * Add the remaining fragment of the basic block to a free list. + */ + total_size = rmsize(total_size); + if (total_size > 0U) { + ((element *)next)->next = ctx->freelists[total_size]; + ctx->freelists[total_size] = (element *)next; + ctx->stats[total_size].freefrags++; + } + /* + * curr is now pointing at the last block in the + * array. + */ + ((element *)curr)->next = NULL; + ctx->freelists[new_size] = tmp; +} + +static void * +mem_getunlocked(isc__mem_t *ctx, size_t size) { + size_t new_size = quantize(size); + void *ret; + + if (new_size >= ctx->max_size) { + /* + * memget() was called on something beyond our upper limit. + */ + ret = (ctx->memalloc)(size); + ctx->total += size; + ctx->inuse += size; + ctx->stats[ctx->max_size].gets++; + ctx->stats[ctx->max_size].totalgets++; + ctx->malloced += size; + if (ctx->malloced > ctx->maxmalloced) { + ctx->maxmalloced = ctx->malloced; + } + /* + * If we don't set new_size to size, then the + * ISC_MEMFLAG_FILL code might write over bytes we don't + * own. + */ + new_size = size; + goto done; + } + /* + * If there are no blocks in the free list for this size, get a chunk + * of memory and then break it up into "new_size"-sized blocks, adding + * them to the free list. + */ + if (ctx->freelists[new_size] == NULL) { + more_frags(ctx, new_size); + } + INSIST(ctx->freelists[new_size] != NULL); + + /* + * The free list uses the "rounded-up" size "new_size". + */ + + ret = ctx->freelists[new_size]; + ctx->freelists[new_size] = ctx->freelists[new_size]->next; + + /* + * The stats[] uses the _actual_ "size" requested by the + * caller, with the caveat (in the code above) that "size" >= the + * max. size (max_size) ends up getting recorded as a call to + * max_size. + */ + ctx->stats[size].gets++; + ctx->stats[size].totalgets++; + ctx->stats[new_size].freefrags--; + ctx->inuse += new_size; + +done: + if (ISC_UNLIKELY((ctx->flags & ISC_MEMFLAG_FILL) != 0) && + ISC_LIKELY(ret != NULL)) + { + memset(ret, 0xbe, new_size); /* Mnemonic for "beef". */ + } + + return (ret); +} + +#if ISC_MEM_CHECKOVERRUN +static void +check_overrun(void *mem, size_t size, size_t new_size) { + unsigned char *cp; + + cp = (unsigned char *)mem; + cp += size; + while (size < new_size) { + INSIST(*cp == 0xbe); + cp++; + size++; + } +} +#endif /* if ISC_MEM_CHECKOVERRUN */ + +/* coverity[+free : arg-1] */ +static void +mem_putunlocked(isc__mem_t *ctx, void *mem, size_t size) { + size_t new_size = quantize(size); + + if (new_size >= ctx->max_size) { + /* + * memput() called on something beyond our upper limit. + */ + if (ISC_UNLIKELY((ctx->flags & ISC_MEMFLAG_FILL) != 0)) { + memset(mem, 0xde, size); /* Mnemonic for "dead". */ + } + + (ctx->memfree)(mem); + INSIST(ctx->stats[ctx->max_size].gets != 0U); + ctx->stats[ctx->max_size].gets--; + INSIST(size <= ctx->inuse); + ctx->inuse -= size; + ctx->malloced -= size; + return; + } + + if (ISC_UNLIKELY((ctx->flags & ISC_MEMFLAG_FILL) != 0)) { +#if ISC_MEM_CHECKOVERRUN + check_overrun(mem, size, new_size); +#endif /* if ISC_MEM_CHECKOVERRUN */ + memset(mem, 0xde, new_size); /* Mnemonic for "dead". */ + } + + /* + * The free list uses the "rounded-up" size "new_size". + */ + ((element *)mem)->next = ctx->freelists[new_size]; + ctx->freelists[new_size] = (element *)mem; + + /* + * The stats[] uses the _actual_ "size" requested by the + * caller, with the caveat (in the code above) that "size" >= the + * max. size (max_size) ends up getting recorded as a call to + * max_size. + */ + INSIST(ctx->stats[size].gets != 0U); + ctx->stats[size].gets--; + ctx->stats[new_size].freefrags++; + ctx->inuse -= new_size; +} + +/*! + * Perform a malloc, doing memory filling and overrun detection as necessary. + */ +static void * +mem_get(isc__mem_t *ctx, size_t size) { + char *ret; + +#if ISC_MEM_CHECKOVERRUN + size += 1; +#endif /* if ISC_MEM_CHECKOVERRUN */ + ret = (ctx->memalloc)(size); + + if (ISC_UNLIKELY((ctx->flags & ISC_MEMFLAG_FILL) != 0)) { + if (ISC_LIKELY(ret != NULL)) { + memset(ret, 0xbe, size); /* Mnemonic for "beef". */ + } + } +#if ISC_MEM_CHECKOVERRUN + else + { + if (ISC_LIKELY(ret != NULL)) { + ret[size - 1] = 0xbe; + } + } +#endif /* if ISC_MEM_CHECKOVERRUN */ + + return (ret); +} + +/*! + * Perform a free, doing memory filling and overrun detection as necessary. + */ +/* coverity[+free : arg-1] */ +static void +mem_put(isc__mem_t *ctx, void *mem, size_t size) { +#if ISC_MEM_CHECKOVERRUN + INSIST(((unsigned char *)mem)[size] == 0xbe); + size += 1; +#endif /* if ISC_MEM_CHECKOVERRUN */ + if (ISC_UNLIKELY((ctx->flags & ISC_MEMFLAG_FILL) != 0)) { + memset(mem, 0xde, size); /* Mnemonic for "dead". */ + } + (ctx->memfree)(mem); +} + +/*! + * Update internal counters after a memory get. + */ +static void +mem_getstats(isc__mem_t *ctx, size_t size) { + ctx->total += size; + ctx->inuse += size; + + if (size > ctx->max_size) { + ctx->stats[ctx->max_size].gets++; + ctx->stats[ctx->max_size].totalgets++; + } else { + ctx->stats[size].gets++; + ctx->stats[size].totalgets++; + } + +#if ISC_MEM_CHECKOVERRUN + size += 1; +#endif /* if ISC_MEM_CHECKOVERRUN */ + ctx->malloced += size; + if (ctx->malloced > ctx->maxmalloced) { + ctx->maxmalloced = ctx->malloced; + } +} + +/*! + * Update internal counters after a memory put. + */ +static void +mem_putstats(isc__mem_t *ctx, void *ptr, size_t size) { + UNUSED(ptr); + + INSIST(ctx->inuse >= size); + ctx->inuse -= size; + + if (size > ctx->max_size) { + INSIST(ctx->stats[ctx->max_size].gets > 0U); + ctx->stats[ctx->max_size].gets--; + } else { + INSIST(ctx->stats[size].gets > 0U); + ctx->stats[size].gets--; + } +#if ISC_MEM_CHECKOVERRUN + size += 1; +#endif /* if ISC_MEM_CHECKOVERRUN */ + ctx->malloced -= size; +} + +/* + * Private. + */ + +static void * +default_memalloc(size_t size) { + void *ptr; + + ptr = malloc(size); + + /* + * If the space cannot be allocated, a null pointer is returned. If the + * size of the space requested is zero, the behavior is + * implementation-defined: either a null pointer is returned, or the + * behavior is as if the size were some nonzero value, except that the + * returned pointer shall not be used to access an object. + * [ISO9899 § 7.22.3] + * + * [ISO9899] + * ISO/IEC WG 9899:2011: Programming languages - C. + * International Organization for Standardization, Geneva, + * Switzerland. + * http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1570.pdf + */ + + if (ptr == NULL && size != 0) { + char strbuf[ISC_STRERRORSIZE]; + strerror_r(errno, strbuf, sizeof(strbuf)); + isc_error_fatal(__FILE__, __LINE__, "malloc failed: %s", + strbuf); + } + + return (ptr); +} + +static void +default_memfree(void *ptr) { + free(ptr); +} + +static void +mem_initialize(void) { + isc_mutex_init(&contextslock); + ISC_LIST_INIT(contexts); + totallost = 0; +} + +void +isc__mem_initialize(void) { + RUNTIME_CHECK(isc_once_do(&init_once, mem_initialize) == ISC_R_SUCCESS); +} + +static void +mem_shutdown(void) { + isc__mem_checkdestroyed(); + + isc_mutex_destroy(&contextslock); +} + +void +isc__mem_shutdown(void) { + RUNTIME_CHECK(isc_once_do(&shut_once, mem_shutdown) == ISC_R_SUCCESS); +} + +static void +mem_create(isc_mem_t **ctxp, unsigned int flags) { + REQUIRE(ctxp != NULL && *ctxp == NULL); +#if __SANITIZE_ADDRESS__ + REQUIRE((flags & ISC_MEMFLAG_INTERNAL) == 0); +#endif + + isc__mem_t *ctx; + + isc_enable_constructors(); + + STATIC_ASSERT((ALIGNMENT_SIZE & (ALIGNMENT_SIZE - 1)) == 0, + "wrong alignment size"); + + ctx = (default_memalloc)(sizeof(*ctx)); + + isc_mutex_init(&ctx->lock); + + ctx->max_size = DEF_MAX_SIZE; + ctx->flags = flags; + isc_refcount_init(&ctx->references, 1); + memset(ctx->name, 0, sizeof(ctx->name)); + ctx->tag = NULL; + ctx->total = 0; + ctx->inuse = 0; + ctx->maxinuse = 0; + ctx->malloced = sizeof(*ctx); + ctx->maxmalloced = sizeof(*ctx); + ctx->hi_water = 0; + ctx->lo_water = 0; + ctx->hi_called = false; + ctx->is_overmem = false; + ctx->water = NULL; + ctx->water_arg = NULL; + ctx->common.impmagic = MEM_MAGIC; + ctx->common.magic = ISCAPI_MCTX_MAGIC; + ctx->common.methods = (isc_memmethods_t *)&memmethods; + ctx->memalloc = default_memalloc; + ctx->memfree = default_memfree; + ctx->stats = NULL; + ctx->checkfree = true; +#if ISC_MEM_TRACKLINES + ctx->debuglist = NULL; + ctx->debuglistcnt = 0; +#endif /* if ISC_MEM_TRACKLINES */ + ISC_LIST_INIT(ctx->pools); + ctx->poolcnt = 0; + ctx->freelists = NULL; + ctx->basic_blocks = NULL; + ctx->basic_table = NULL; + ctx->basic_table_count = 0; + ctx->basic_table_size = 0; + ctx->lowest = NULL; + ctx->highest = NULL; + + ctx->stats = + (ctx->memalloc)((ctx->max_size + 1) * sizeof(struct stats)); + + memset(ctx->stats, 0, (ctx->max_size + 1) * sizeof(struct stats)); + ctx->malloced += (ctx->max_size + 1) * sizeof(struct stats); + ctx->maxmalloced += (ctx->max_size + 1) * sizeof(struct stats); + + if ((flags & ISC_MEMFLAG_INTERNAL) != 0) { + ctx->mem_target = DEF_MEM_TARGET; + ctx->freelists = + (ctx->memalloc)(ctx->max_size * sizeof(element *)); + memset(ctx->freelists, 0, ctx->max_size * sizeof(element *)); + ctx->malloced += ctx->max_size * sizeof(element *); + ctx->maxmalloced += ctx->max_size * sizeof(element *); + } + +#if ISC_MEM_TRACKLINES + if (ISC_UNLIKELY((isc_mem_debugging & ISC_MEM_DEBUGRECORD) != 0)) { + unsigned int i; + + ctx->debuglist = (ctx->memalloc)( + (DEBUG_TABLE_COUNT * sizeof(debuglist_t))); + for (i = 0; i < DEBUG_TABLE_COUNT; i++) { + ISC_LIST_INIT(ctx->debuglist[i]); + } + ctx->malloced += DEBUG_TABLE_COUNT * sizeof(debuglist_t); + ctx->maxmalloced += DEBUG_TABLE_COUNT * sizeof(debuglist_t); + } +#endif /* if ISC_MEM_TRACKLINES */ + + LOCK(&contextslock); + ISC_LIST_INITANDAPPEND(contexts, ctx, link); + UNLOCK(&contextslock); + + *ctxp = (isc_mem_t *)ctx; +} + +/* + * Public. + */ + +static void +destroy(isc__mem_t *ctx) { + unsigned int i; + + LOCK(&contextslock); + ISC_LIST_UNLINK(contexts, ctx, link); + totallost += ctx->inuse; + UNLOCK(&contextslock); + + ctx->common.impmagic = 0; + ctx->common.magic = 0; + + INSIST(ISC_LIST_EMPTY(ctx->pools)); + +#if ISC_MEM_TRACKLINES + if (ISC_UNLIKELY(ctx->debuglist != NULL)) { + debuglink_t *dl; + for (i = 0; i < DEBUG_TABLE_COUNT; i++) { + for (dl = ISC_LIST_HEAD(ctx->debuglist[i]); dl != NULL; + dl = ISC_LIST_HEAD(ctx->debuglist[i])) + { + if (ctx->checkfree && dl->ptr != NULL) { + print_active(ctx, stderr); + } + INSIST(!ctx->checkfree || dl->ptr == NULL); + + ISC_LIST_UNLINK(ctx->debuglist[i], dl, link); + free(dl); + ctx->malloced -= sizeof(*dl); + } + } + + (ctx->memfree)(ctx->debuglist); + ctx->malloced -= DEBUG_TABLE_COUNT * sizeof(debuglist_t); + } +#endif /* if ISC_MEM_TRACKLINES */ + + if (ctx->checkfree) { + for (i = 0; i <= ctx->max_size; i++) { + if (ctx->stats[i].gets != 0U) { + fprintf(stderr, + "Failing assertion due to probable " + "leaked memory in context %p (\"%s\") " + "(stats[%u].gets == %lu).\n", + ctx, ctx->name, i, ctx->stats[i].gets); +#if ISC_MEM_TRACKLINES + print_active(ctx, stderr); +#endif /* if ISC_MEM_TRACKLINES */ + INSIST(ctx->stats[i].gets == 0U); + } + } + } + + (ctx->memfree)(ctx->stats); + ctx->malloced -= (ctx->max_size + 1) * sizeof(struct stats); + + if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { + for (i = 0; i < ctx->basic_table_count; i++) { + (ctx->memfree)(ctx->basic_table[i]); + ctx->malloced -= NUM_BASIC_BLOCKS * ctx->mem_target; + } + (ctx->memfree)(ctx->freelists); + ctx->malloced -= ctx->max_size * sizeof(element *); + if (ctx->basic_table != NULL) { + (ctx->memfree)(ctx->basic_table); + ctx->malloced -= ctx->basic_table_size * + sizeof(unsigned char *); + } + } + + isc_mutex_destroy(&ctx->lock); + + ctx->malloced -= sizeof(*ctx); + if (ctx->checkfree) { + INSIST(ctx->malloced == 0); + } + (ctx->memfree)(ctx); +} + +void +isc_mem_attach(isc_mem_t *source0, isc_mem_t **targetp) { + REQUIRE(VALID_CONTEXT(source0)); + REQUIRE(targetp != NULL && *targetp == NULL); + + isc__mem_t *source = (isc__mem_t *)source0; + + isc_refcount_increment(&source->references); + + *targetp = (isc_mem_t *)source; +} + +void +isc_mem_detach(isc_mem_t **ctxp) { + REQUIRE(ctxp != NULL && VALID_CONTEXT(*ctxp)); + + isc__mem_t *ctx = (isc__mem_t *)*ctxp; + *ctxp = NULL; + + if (isc_refcount_decrement(&ctx->references) == 1) { + isc_refcount_destroy(&ctx->references); + destroy(ctx); + } +} + +/* + * isc_mem_putanddetach() is the equivalent of: + * + * mctx = NULL; + * isc_mem_attach(ptr->mctx, &mctx); + * isc_mem_detach(&ptr->mctx); + * isc_mem_put(mctx, ptr, sizeof(*ptr); + * isc_mem_detach(&mctx); + */ + +void +isc___mem_putanddetach(isc_mem_t **ctxp, void *ptr, size_t size FLARG) { + REQUIRE(ctxp != NULL && VALID_CONTEXT(*ctxp)); + REQUIRE(ptr != NULL); + + isc__mem_t *ctx = (isc__mem_t *)*ctxp; + *ctxp = NULL; + + if (ISC_UNLIKELY((isc_mem_debugging & + (ISC_MEM_DEBUGSIZE | ISC_MEM_DEBUGCTX)) != 0)) + { + if ((isc_mem_debugging & ISC_MEM_DEBUGSIZE) != 0) { + size_info *si = &(((size_info *)ptr)[-1]); + size_t oldsize = si->u.size - ALIGNMENT_SIZE; + if ((isc_mem_debugging & ISC_MEM_DEBUGCTX) != 0) { + oldsize -= ALIGNMENT_SIZE; + } + INSIST(oldsize == size); + } + isc__mem_free((isc_mem_t *)ctx, ptr FLARG_PASS); + + goto destroy; + } + + MCTXLOCK(ctx); + + DELETE_TRACE(ctx, ptr, size, file, line); + + if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { + mem_putunlocked(ctx, ptr, size); + } else { + mem_putstats(ctx, ptr, size); + mem_put(ctx, ptr, size); + } + MCTXUNLOCK(ctx); + +destroy: + if (isc_refcount_decrement(&ctx->references) == 1) { + isc_refcount_destroy(&ctx->references); + destroy(ctx); + } +} + +void +isc_mem_destroy(isc_mem_t **ctxp) { + /* + * This routine provides legacy support for callers who use mctxs + * without attaching/detaching. + */ + + REQUIRE(ctxp != NULL && VALID_CONTEXT(*ctxp)); + + isc__mem_t *ctx = (isc__mem_t *)*ctxp; + +#if ISC_MEM_TRACKLINES + if (isc_refcount_decrement(&ctx->references) > 1) { + print_active(ctx, stderr); + } +#else /* if ISC_MEM_TRACKLINES */ + isc_refcount_decrementz(&ctx->references); +#endif /* if ISC_MEM_TRACKLINES */ + isc_refcount_destroy(&ctx->references); + destroy(ctx); + + *ctxp = NULL; +} + +void * +isc___mem_get(isc_mem_t *ctx0, size_t size FLARG) { + REQUIRE(VALID_CONTEXT(ctx0)); + + isc__mem_t *ctx = (isc__mem_t *)ctx0; + void *ptr; + bool call_water = false; + + if (ISC_UNLIKELY((isc_mem_debugging & + (ISC_MEM_DEBUGSIZE | ISC_MEM_DEBUGCTX)) != 0)) + { + return (isc__mem_allocate(ctx0, size FLARG_PASS)); + } + + if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { + MCTXLOCK(ctx); + ptr = mem_getunlocked(ctx, size); + } else { + ptr = mem_get(ctx, size); + MCTXLOCK(ctx); + if (ptr != NULL) { + mem_getstats(ctx, size); + } + } + + ADD_TRACE(ctx, ptr, size, file, line); + + if (ctx->hi_water != 0U && ctx->inuse > ctx->hi_water) { + ctx->is_overmem = true; + if (!ctx->hi_called) { + call_water = true; + } + } + if (ctx->inuse > ctx->maxinuse) { + ctx->maxinuse = ctx->inuse; + if (ctx->hi_water != 0U && ctx->inuse > ctx->hi_water && + (isc_mem_debugging & ISC_MEM_DEBUGUSAGE) != 0) + { + fprintf(stderr, "maxinuse = %lu\n", + (unsigned long)ctx->inuse); + } + } + MCTXUNLOCK(ctx); + + if (call_water && (ctx->water != NULL)) { + (ctx->water)(ctx->water_arg, ISC_MEM_HIWATER); + } + + return (ptr); +} + +void +isc___mem_put(isc_mem_t *ctx0, void *ptr, size_t size FLARG) { + REQUIRE(VALID_CONTEXT(ctx0)); + REQUIRE(ptr != NULL); + + isc__mem_t *ctx = (isc__mem_t *)ctx0; + bool call_water = false; + size_info *si; + size_t oldsize; + + if (ISC_UNLIKELY((isc_mem_debugging & + (ISC_MEM_DEBUGSIZE | ISC_MEM_DEBUGCTX)) != 0)) + { + if ((isc_mem_debugging & ISC_MEM_DEBUGSIZE) != 0) { + si = &(((size_info *)ptr)[-1]); + oldsize = si->u.size - ALIGNMENT_SIZE; + if ((isc_mem_debugging & ISC_MEM_DEBUGCTX) != 0) { + oldsize -= ALIGNMENT_SIZE; + } + INSIST(oldsize == size); + } + isc__mem_free((isc_mem_t *)ctx, ptr FLARG_PASS); + return; + } + + MCTXLOCK(ctx); + + DELETE_TRACE(ctx, ptr, size, file, line); + + if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { + mem_putunlocked(ctx, ptr, size); + } else { + mem_putstats(ctx, ptr, size); + mem_put(ctx, ptr, size); + } + + /* + * The check against ctx->lo_water == 0 is for the condition + * when the context was pushed over hi_water but then had + * isc_mem_setwater() called with 0 for hi_water and lo_water. + */ + if ((ctx->inuse < ctx->lo_water) || (ctx->lo_water == 0U)) { + ctx->is_overmem = false; + if (ctx->hi_called) { + call_water = true; + } + } + + MCTXUNLOCK(ctx); + + if (call_water && (ctx->water != NULL)) { + (ctx->water)(ctx->water_arg, ISC_MEM_LOWATER); + } +} + +void +isc_mem_waterack(isc_mem_t *ctx0, int flag) { + REQUIRE(VALID_CONTEXT(ctx0)); + + isc__mem_t *ctx = (isc__mem_t *)ctx0; + + MCTXLOCK(ctx); + if (flag == ISC_MEM_LOWATER) { + ctx->hi_called = false; + } else if (flag == ISC_MEM_HIWATER) { + ctx->hi_called = true; + } + MCTXUNLOCK(ctx); +} + +#if ISC_MEM_TRACKLINES +static void +print_active(isc__mem_t *mctx, FILE *out) { + if (mctx->debuglist != NULL) { + debuglink_t *dl; + unsigned int i; + bool found; + + fputs("Dump of all outstanding memory allocations:\n", out); + found = false; + for (i = 0; i < DEBUG_TABLE_COUNT; i++) { + dl = ISC_LIST_HEAD(mctx->debuglist[i]); + + if (dl != NULL) { + found = true; + } + + while (dl != NULL) { + if (dl->ptr != NULL) { + fprintf(out, + "\tptr %p size %zu file %s " + "line %u\n", + dl->ptr, dl->size, dl->file, + dl->line); + } + dl = ISC_LIST_NEXT(dl, link); + } + } + + if (!found) { + fputs("\tNone.\n", out); + } + } +} +#endif /* if ISC_MEM_TRACKLINES */ + +/* + * Print the stats[] on the stream "out" with suitable formatting. + */ +void +isc_mem_stats(isc_mem_t *ctx0, FILE *out) { + REQUIRE(VALID_CONTEXT(ctx0)); + + isc__mem_t *ctx = (isc__mem_t *)ctx0; + size_t i; + const struct stats *s; + const isc__mempool_t *pool; + + MCTXLOCK(ctx); + + for (i = 0; i <= ctx->max_size; i++) { + s = &ctx->stats[i]; + + if (s->totalgets == 0U && s->gets == 0U) { + continue; + } + fprintf(out, "%s%5lu: %11lu gets, %11lu rem", + (i == ctx->max_size) ? ">=" : " ", (unsigned long)i, + s->totalgets, s->gets); + if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0 && + (s->blocks != 0U || s->freefrags != 0U)) + { + fprintf(out, " (%lu bl, %lu ff)", s->blocks, + s->freefrags); + } + fputc('\n', out); + } + + /* + * Note that since a pool can be locked now, these stats might be + * somewhat off if the pool is in active use at the time the stats + * are dumped. The link fields are protected by the isc_mem_t's + * lock, however, so walking this list and extracting integers from + * stats fields is always safe. + */ + pool = ISC_LIST_HEAD(ctx->pools); + if (pool != NULL) { + fputs("[Pool statistics]\n", out); + fprintf(out, "%15s %10s %10s %10s %10s %10s %10s %10s %1s\n", + "name", "size", "maxalloc", "allocated", "freecount", + "freemax", "fillcount", "gets", "L"); + } + while (pool != NULL) { + fprintf(out, "%15s %10lu %10u %10u %10u %10u %10u %10u %s\n", +#if ISC_MEMPOOL_NAMES + pool->name, +#else /* if ISC_MEMPOOL_NAMES */ + "(not tracked)", +#endif /* if ISC_MEMPOOL_NAMES */ + (unsigned long)pool->size, pool->maxalloc, + pool->allocated, pool->freecount, pool->freemax, + pool->fillcount, pool->gets, "N"); + pool = ISC_LIST_NEXT(pool, link); + } + +#if ISC_MEM_TRACKLINES + print_active(ctx, out); +#endif /* if ISC_MEM_TRACKLINES */ + + MCTXUNLOCK(ctx); +} + +/* + * Replacements for malloc() and free() -- they implicitly remember the + * size of the object allocated (with some additional overhead). + */ + +static void * +mem_allocateunlocked(isc_mem_t *ctx0, size_t size) { + isc__mem_t *ctx = (isc__mem_t *)ctx0; + size_info *si; + + size += ALIGNMENT_SIZE; + if (ISC_UNLIKELY((isc_mem_debugging & ISC_MEM_DEBUGCTX) != 0)) { + size += ALIGNMENT_SIZE; + } + + if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { + si = mem_getunlocked(ctx, size); + } else { + si = mem_get(ctx, size); + } + + if (ISC_UNLIKELY((isc_mem_debugging & ISC_MEM_DEBUGCTX) != 0)) { + si->u.ctx = ctx; + si++; + } + si->u.size = size; + return (&si[1]); +} + +void * +isc___mem_allocate(isc_mem_t *ctx0, size_t size FLARG) { + REQUIRE(VALID_CONTEXT(ctx0)); + + isc__mem_t *ctx = (isc__mem_t *)ctx0; + size_info *si; + bool call_water = false; + + MCTXLOCK(ctx); + si = mem_allocateunlocked((isc_mem_t *)ctx, size); + if (((ctx->flags & ISC_MEMFLAG_INTERNAL) == 0)) { + mem_getstats(ctx, si[-1].u.size); + } + + ADD_TRACE(ctx, si, si[-1].u.size, file, line); + if (ctx->hi_water != 0U && ctx->inuse > ctx->hi_water && + !ctx->is_overmem) + { + ctx->is_overmem = true; + } + + if (ctx->hi_water != 0U && !ctx->hi_called && + ctx->inuse > ctx->hi_water) + { + ctx->hi_called = true; + call_water = true; + } + if (ctx->inuse > ctx->maxinuse) { + ctx->maxinuse = ctx->inuse; + if (ISC_UNLIKELY(ctx->hi_water != 0U && + ctx->inuse > ctx->hi_water && + (isc_mem_debugging & ISC_MEM_DEBUGUSAGE) != 0)) + { + fprintf(stderr, "maxinuse = %lu\n", + (unsigned long)ctx->inuse); + } + } + MCTXUNLOCK(ctx); + + if (call_water) { + (ctx->water)(ctx->water_arg, ISC_MEM_HIWATER); + } + + return (si); +} + +void * +isc___mem_reallocate(isc_mem_t *ctx0, void *ptr, size_t size FLARG) { + REQUIRE(VALID_CONTEXT(ctx0)); + + void *new_ptr = NULL; + size_t oldsize, copysize; + + /* + * This function emulates the realloc(3) standard library function: + * - if size > 0, allocate new memory; and if ptr is non NULL, copy + * as much of the old contents to the new buffer and free the old one. + * Note that when allocation fails the original pointer is intact; + * the caller must free it. + * - if size is 0 and ptr is non NULL, simply free the given ptr. + * - this function returns: + * pointer to the newly allocated memory, or + * NULL if allocation fails or doesn't happen. + */ + if (size > 0U) { + new_ptr = isc__mem_allocate(ctx0, size FLARG_PASS); + if (new_ptr != NULL && ptr != NULL) { + oldsize = (((size_info *)ptr)[-1]).u.size; + INSIST(oldsize >= ALIGNMENT_SIZE); + oldsize -= ALIGNMENT_SIZE; + if (ISC_UNLIKELY((isc_mem_debugging & + ISC_MEM_DEBUGCTX) != 0)) + { + INSIST(oldsize >= ALIGNMENT_SIZE); + oldsize -= ALIGNMENT_SIZE; + } + copysize = (oldsize > size) ? size : oldsize; + memmove(new_ptr, ptr, copysize); + isc__mem_free(ctx0, ptr FLARG_PASS); + } + } else if (ptr != NULL) { + isc__mem_free(ctx0, ptr FLARG_PASS); + } + + return (new_ptr); +} + +void +isc___mem_free(isc_mem_t *ctx0, void *ptr FLARG) { + REQUIRE(VALID_CONTEXT(ctx0)); + REQUIRE(ptr != NULL); + + isc__mem_t *ctx = (isc__mem_t *)ctx0; + size_info *si; + size_t size; + bool call_water = false; + + if (ISC_UNLIKELY((isc_mem_debugging & ISC_MEM_DEBUGCTX) != 0)) { + si = &(((size_info *)ptr)[-2]); + REQUIRE(si->u.ctx == ctx); + size = si[1].u.size; + } else { + si = &(((size_info *)ptr)[-1]); + size = si->u.size; + } + + MCTXLOCK(ctx); + + DELETE_TRACE(ctx, ptr, size, file, line); + + if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { + mem_putunlocked(ctx, si, size); + } else { + mem_putstats(ctx, si, size); + mem_put(ctx, si, size); + } + + /* + * The check against ctx->lo_water == 0 is for the condition + * when the context was pushed over hi_water but then had + * isc_mem_setwater() called with 0 for hi_water and lo_water. + */ + if (ctx->is_overmem && + (ctx->inuse < ctx->lo_water || ctx->lo_water == 0U)) + { + ctx->is_overmem = false; + } + + if (ctx->hi_called && + (ctx->inuse < ctx->lo_water || ctx->lo_water == 0U)) + { + ctx->hi_called = false; + + if (ctx->water != NULL) { + call_water = true; + } + } + MCTXUNLOCK(ctx); + + if (call_water) { + (ctx->water)(ctx->water_arg, ISC_MEM_LOWATER); + } +} + +/* + * Other useful things. + */ + +char * +isc___mem_strdup(isc_mem_t *mctx0, const char *s FLARG) { + REQUIRE(VALID_CONTEXT(mctx0)); + REQUIRE(s != NULL); + + isc__mem_t *mctx = (isc__mem_t *)mctx0; + size_t len; + char *ns; + + len = strlen(s) + 1; + + ns = isc__mem_allocate((isc_mem_t *)mctx, len FLARG_PASS); + + if (ns != NULL) { + strlcpy(ns, s, len); + } + + return (ns); +} + +char * +isc___mem_strndup(isc_mem_t *mctx0, const char *s, size_t size FLARG) { + REQUIRE(VALID_CONTEXT(mctx0)); + REQUIRE(s != NULL); + + isc__mem_t *mctx = (isc__mem_t *)mctx0; + size_t len; + char *ns; + + len = strlen(s) + 1; + if (len > size) { + len = size; + } + + ns = isc__mem_allocate((isc_mem_t *)mctx, len FLARG_PASS); + + if (ns != NULL) { + strlcpy(ns, s, len); + } + + return (ns); +} + +void +isc_mem_setdestroycheck(isc_mem_t *ctx0, bool flag) { + REQUIRE(VALID_CONTEXT(ctx0)); + + isc__mem_t *ctx = (isc__mem_t *)ctx0; + + MCTXLOCK(ctx); + + ctx->checkfree = flag; + + MCTXUNLOCK(ctx); +} + +size_t +isc_mem_inuse(isc_mem_t *ctx0) { + REQUIRE(VALID_CONTEXT(ctx0)); + + isc__mem_t *ctx = (isc__mem_t *)ctx0; + size_t inuse; + + MCTXLOCK(ctx); + + inuse = ctx->inuse; + + MCTXUNLOCK(ctx); + + return (inuse); +} + +size_t +isc_mem_maxinuse(isc_mem_t *ctx0) { + REQUIRE(VALID_CONTEXT(ctx0)); + + isc__mem_t *ctx = (isc__mem_t *)ctx0; + size_t maxinuse; + + MCTXLOCK(ctx); + + maxinuse = ctx->maxinuse; + + MCTXUNLOCK(ctx); + + return (maxinuse); +} + +size_t +isc_mem_total(isc_mem_t *ctx0) { + REQUIRE(VALID_CONTEXT(ctx0)); + + isc__mem_t *ctx = (isc__mem_t *)ctx0; + size_t total; + + MCTXLOCK(ctx); + + total = ctx->total; + + MCTXUNLOCK(ctx); + + return (total); +} + +void +isc_mem_setwater(isc_mem_t *ctx0, isc_mem_water_t water, void *water_arg, + size_t hiwater, size_t lowater) { + REQUIRE(VALID_CONTEXT(ctx0)); + REQUIRE(hiwater >= lowater); + + isc__mem_t *ctx = (isc__mem_t *)ctx0; + bool callwater = false; + isc_mem_water_t oldwater; + void *oldwater_arg; + + MCTXLOCK(ctx); + oldwater = ctx->water; + oldwater_arg = ctx->water_arg; + if (water == NULL) { + callwater = ctx->hi_called; + ctx->water = NULL; + ctx->water_arg = NULL; + ctx->hi_water = 0; + ctx->lo_water = 0; + } else { + if (ctx->hi_called && + (ctx->water != water || ctx->water_arg != water_arg || + ctx->inuse < lowater || lowater == 0U)) + { + callwater = true; + } + ctx->water = water; + ctx->water_arg = water_arg; + ctx->hi_water = hiwater; + ctx->lo_water = lowater; + } + MCTXUNLOCK(ctx); + + if (callwater && oldwater != NULL) { + (oldwater)(oldwater_arg, ISC_MEM_LOWATER); + } +} + +ISC_NO_SANITIZE_THREAD bool +isc_mem_isovermem(isc_mem_t *ctx0) { + REQUIRE(VALID_CONTEXT(ctx0)); + + isc__mem_t *ctx = (isc__mem_t *)ctx0; + + /* + * We don't bother to lock the context because 100% accuracy isn't + * necessary (and even if we locked the context the returned value + * could be different from the actual state when it's used anyway) + */ + return (ctx->is_overmem); +} + +void +isc_mem_setname(isc_mem_t *ctx0, const char *name, void *tag) { + REQUIRE(VALID_CONTEXT(ctx0)); + + isc__mem_t *ctx = (isc__mem_t *)ctx0; + + LOCK(&ctx->lock); + strlcpy(ctx->name, name, sizeof(ctx->name)); + ctx->tag = tag; + UNLOCK(&ctx->lock); +} + +const char * +isc_mem_getname(isc_mem_t *ctx0) { + REQUIRE(VALID_CONTEXT(ctx0)); + + isc__mem_t *ctx = (isc__mem_t *)ctx0; + + if (ctx->name[0] == 0) { + return (""); + } + + return (ctx->name); +} + +void * +isc_mem_gettag(isc_mem_t *ctx0) { + REQUIRE(VALID_CONTEXT(ctx0)); + + isc__mem_t *ctx = (isc__mem_t *)ctx0; + + return (ctx->tag); +} + +/* + * Memory pool stuff + */ + +void +isc_mempool_create(isc_mem_t *mctx0, size_t size, isc_mempool_t **mpctxp) { + REQUIRE(VALID_CONTEXT(mctx0)); + REQUIRE(size > 0U); + REQUIRE(mpctxp != NULL && *mpctxp == NULL); + + isc__mem_t *mctx = (isc__mem_t *)mctx0; + isc__mempool_t *mpctx; + + /* + * Allocate space for this pool, initialize values, and if all works + * well, attach to the memory context. + */ + mpctx = isc_mem_get((isc_mem_t *)mctx, sizeof(isc__mempool_t)); + + mpctx->common.impmagic = MEMPOOL_MAGIC; + mpctx->common.magic = ISCAPI_MPOOL_MAGIC; + mpctx->mctx = NULL; + isc_mem_attach((isc_mem_t *)mctx, (isc_mem_t **)&mpctx->mctx); + /* + * Mempools are stored as a linked list of element. + */ + if (size < sizeof(element)) { + size = sizeof(element); + } + mpctx->size = size; + mpctx->maxalloc = UINT_MAX; + mpctx->allocated = 0; + mpctx->freecount = 0; + mpctx->freemax = 1; + mpctx->fillcount = 1; + mpctx->gets = 0; +#if ISC_MEMPOOL_NAMES + mpctx->name[0] = 0; +#endif /* if ISC_MEMPOOL_NAMES */ + mpctx->items = NULL; + + *mpctxp = (isc_mempool_t *)mpctx; + + MCTXLOCK(mctx); + ISC_LIST_INITANDAPPEND(mctx->pools, mpctx, link); + mctx->poolcnt++; + MCTXUNLOCK(mctx); +} + +void +isc_mempool_setname(isc_mempool_t *mpctx0, const char *name) { + REQUIRE(VALID_MEMPOOL(mpctx0)); + REQUIRE(name != NULL); + + isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; + +#if ISC_MEMPOOL_NAMES + strlcpy(mpctx->name, name, sizeof(mpctx->name)); +#else /* if ISC_MEMPOOL_NAMES */ + UNUSED(mpctx); + UNUSED(name); +#endif /* if ISC_MEMPOOL_NAMES */ +} + +void +isc_mempool_destroy(isc_mempool_t **mpctxp) { + REQUIRE(mpctxp != NULL); + REQUIRE(VALID_MEMPOOL(*mpctxp)); + + isc__mempool_t *mpctx; + isc__mem_t *mctx; + element *item; + + mpctx = (isc__mempool_t *)*mpctxp; +#if ISC_MEMPOOL_NAMES + if (mpctx->allocated > 0) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_mempool_destroy(): mempool %s " + "leaked memory", + mpctx->name); + } +#endif /* if ISC_MEMPOOL_NAMES */ + REQUIRE(mpctx->allocated == 0); + + mctx = mpctx->mctx; + + /* + * Return any items on the free list + */ + MCTXLOCK(mctx); + while (mpctx->items != NULL) { + INSIST(mpctx->freecount > 0); + mpctx->freecount--; + item = mpctx->items; + mpctx->items = item->next; + mem_putstats(mctx, item, mpctx->size); + mem_put(mctx, item, mpctx->size); + } + MCTXUNLOCK(mctx); + + /* + * Remove our linked list entry from the memory context. + */ + MCTXLOCK(mctx); + ISC_LIST_UNLINK(mctx->pools, mpctx, link); + mctx->poolcnt--; + MCTXUNLOCK(mctx); + + mpctx->common.impmagic = 0; + mpctx->common.magic = 0; + + isc_mem_putanddetach((isc_mem_t **)&mpctx->mctx, mpctx, + sizeof(isc__mempool_t)); + + *mpctxp = NULL; +} + +#if __SANITIZE_ADDRESS__ +void * +isc__mempool_get(isc_mempool_t *mpctx0 FLARG) { + void *item = NULL; + + REQUIRE(VALID_MEMPOOL(mpctx0)); + + isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; + isc_mem_t *mctx = (isc_mem_t *)mpctx->mctx; + + /* + * Don't let the caller go over quota + */ + if (ISC_UNLIKELY(mpctx->allocated >= mpctx->maxalloc)) { + goto out; + } + + item = isc__mem_get(mctx, mpctx->size FLARG_PASS); + mpctx->gets++; + mpctx->allocated++; + +out: + return (item); +} + +void +isc__mempool_put(isc_mempool_t *mpctx0, void *mem FLARG) { + REQUIRE(VALID_MEMPOOL(mpctx0)); + + isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; + isc_mem_t *mctx = (isc_mem_t *)mpctx->mctx; + + REQUIRE(mem != NULL); + + INSIST(mpctx->allocated > 0); + mpctx->allocated--; + + isc__mem_put(mctx, mem, mpctx->size FLARG_PASS); +} + +#else /* __SANITIZE_ADDRESS__ */ +void * +isc__mempool_get(isc_mempool_t *mpctx0 FLARG) { + REQUIRE(VALID_MEMPOOL(mpctx0)); + + isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; + element *item; + isc__mem_t *mctx; + unsigned int i; + + mctx = mpctx->mctx; + + /* + * Don't let the caller go over quota + */ + if (ISC_UNLIKELY(mpctx->allocated >= mpctx->maxalloc)) { + item = NULL; + goto out; + } + + if (ISC_UNLIKELY(mpctx->items == NULL)) { + /* + * We need to dip into the well. Lock the memory context + * here and fill up our free list. + */ + MCTXLOCK(mctx); + for (i = 0; i < mpctx->fillcount; i++) { + item = mem_get(mctx, mpctx->size); + mem_getstats(mctx, mpctx->size); + item->next = mpctx->items; + mpctx->items = item; + mpctx->freecount++; + } + MCTXUNLOCK(mctx); + } + + /* + * If we didn't get any items, return NULL. + */ + item = mpctx->items; + if (ISC_UNLIKELY(item == NULL)) { + goto out; + } + + mpctx->items = item->next; + INSIST(mpctx->freecount > 0); + mpctx->freecount--; + mpctx->gets++; + mpctx->allocated++; + +out: +#if ISC_MEM_TRACKLINES + if (ISC_UNLIKELY(((isc_mem_debugging & TRACE_OR_RECORD) != 0) && + item != NULL)) + { + MCTXLOCK(mctx); + ADD_TRACE(mctx, item, mpctx->size, file, line); + MCTXUNLOCK(mctx); + } +#endif /* ISC_MEM_TRACKLINES */ + + return (item); +} + +/* coverity[+free : arg-1] */ +void +isc__mempool_put(isc_mempool_t *mpctx0, void *mem FLARG) { + REQUIRE(VALID_MEMPOOL(mpctx0)); + REQUIRE(mem != NULL); + + isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; + isc__mem_t *mctx = mpctx->mctx; + element *item; + + INSIST(mpctx->allocated > 0); + mpctx->allocated--; + +#if ISC_MEM_TRACKLINES + if (ISC_UNLIKELY((isc_mem_debugging & TRACE_OR_RECORD) != 0)) { + MCTXLOCK(mctx); + DELETE_TRACE(mctx, mem, mpctx->size, file, line); + MCTXUNLOCK(mctx); + } +#endif /* ISC_MEM_TRACKLINES */ + + /* + * If our free list is full, return this to the mctx directly. + */ + if (mpctx->freecount >= mpctx->freemax) { + MCTXLOCK(mctx); + mem_putstats(mctx, mem, mpctx->size); + mem_put(mctx, mem, mpctx->size); + MCTXUNLOCK(mctx); + return; + } + + /* + * Otherwise, attach it to our free list and bump the counter. + */ + mpctx->freecount++; + item = (element *)mem; + item->next = mpctx->items; + mpctx->items = item; +} + +#endif /* __SANITIZE_ADDRESS__ */ + +/* + * Quotas + */ + +void +isc_mempool_setfreemax(isc_mempool_t *mpctx0, unsigned int limit) { + REQUIRE(VALID_MEMPOOL(mpctx0)); + + isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; + + mpctx->freemax = limit; +} + +unsigned int +isc_mempool_getfreemax(isc_mempool_t *mpctx0) { + REQUIRE(VALID_MEMPOOL(mpctx0)); + + isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; + + return (mpctx->freemax); +} + +unsigned int +isc_mempool_getfreecount(isc_mempool_t *mpctx0) { + REQUIRE(VALID_MEMPOOL(mpctx0)); + + isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; + + return (mpctx->freecount); +} + +void +isc_mempool_setmaxalloc(isc_mempool_t *mpctx0, unsigned int limit) { + REQUIRE(VALID_MEMPOOL(mpctx0)); + REQUIRE(limit > 0); + + isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; + + mpctx->maxalloc = limit; +} + +unsigned int +isc_mempool_getmaxalloc(isc_mempool_t *mpctx0) { + REQUIRE(VALID_MEMPOOL(mpctx0)); + + isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; + + return (mpctx->maxalloc); +} + +unsigned int +isc_mempool_getallocated(isc_mempool_t *mpctx0) { + REQUIRE(VALID_MEMPOOL(mpctx0)); + + isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; + + return (mpctx->allocated); +} + +void +isc_mempool_setfillcount(isc_mempool_t *mpctx0, unsigned int limit) { + REQUIRE(VALID_MEMPOOL(mpctx0)); + REQUIRE(limit > 0); + + isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; + + mpctx->fillcount = limit; +} + +unsigned int +isc_mempool_getfillcount(isc_mempool_t *mpctx0) { + REQUIRE(VALID_MEMPOOL(mpctx0)); + + isc__mempool_t *mpctx = (isc__mempool_t *)mpctx0; + + return (mpctx->fillcount); +} + +/* + * Requires contextslock to be held by caller. + */ +static void +print_contexts(FILE *file) { + isc__mem_t *ctx; + + for (ctx = ISC_LIST_HEAD(contexts); ctx != NULL; + ctx = ISC_LIST_NEXT(ctx, link)) + { + fprintf(file, "context: %p (%s): %" PRIuFAST32 " references\n", + ctx, ctx->name[0] == 0 ? "<unknown>" : ctx->name, + isc_refcount_current(&ctx->references)); + print_active(ctx, file); + } + fflush(file); +} + +static atomic_uintptr_t checkdestroyed = 0; + +void +isc_mem_checkdestroyed(FILE *file) { + atomic_store_release(&checkdestroyed, (uintptr_t)file); +} + +void +isc__mem_checkdestroyed(void) { + FILE *file = (FILE *)atomic_load_acquire(&checkdestroyed); + + if (file == NULL) { + return; + } + + LOCK(&contextslock); + if (!ISC_LIST_EMPTY(contexts)) { +#if ISC_MEM_TRACKLINES + if (ISC_UNLIKELY((isc_mem_debugging & TRACE_OR_RECORD) != 0)) { + print_contexts(file); + } +#endif /* if ISC_MEM_TRACKLINES */ + UNREACHABLE(); + } + UNLOCK(&contextslock); +} + +unsigned int +isc_mem_references(isc_mem_t *ctx0) { + isc__mem_t *ctx = (isc__mem_t *)ctx0; + return (isc_refcount_current(&ctx->references)); +} + +typedef struct summarystat { + uint64_t total; + uint64_t inuse; + uint64_t malloced; + uint64_t blocksize; + uint64_t contextsize; +} summarystat_t; + +#ifdef HAVE_LIBXML2 +#define TRY0(a) \ + do { \ + xmlrc = (a); \ + if (xmlrc < 0) \ + goto error; \ + } while (0) +static int +xml_renderctx(isc__mem_t *ctx, summarystat_t *summary, + xmlTextWriterPtr writer) { + REQUIRE(VALID_CONTEXT(ctx)); + + int xmlrc; + + MCTXLOCK(ctx); + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "context")); + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "id")); + TRY0(xmlTextWriterWriteFormatString(writer, "%p", ctx)); + TRY0(xmlTextWriterEndElement(writer)); /* id */ + + if (ctx->name[0] != 0) { + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "name")); + TRY0(xmlTextWriterWriteFormatString(writer, "%s", ctx->name)); + TRY0(xmlTextWriterEndElement(writer)); /* name */ + } + + summary->contextsize += sizeof(*ctx) + + (ctx->max_size + 1) * sizeof(struct stats) + + ctx->max_size * sizeof(element *) + + ctx->basic_table_count * sizeof(char *); +#if ISC_MEM_TRACKLINES + if (ctx->debuglist != NULL) { + summary->contextsize += DEBUG_TABLE_COUNT * + sizeof(debuglist_t) + + ctx->debuglistcnt * sizeof(debuglink_t); + } +#endif /* if ISC_MEM_TRACKLINES */ + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "references")); + TRY0(xmlTextWriterWriteFormatString( + writer, "%" PRIuFAST32, + isc_refcount_current(&ctx->references))); + TRY0(xmlTextWriterEndElement(writer)); /* references */ + + summary->total += ctx->total; + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "total")); + TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", + (uint64_t)ctx->total)); + TRY0(xmlTextWriterEndElement(writer)); /* total */ + + summary->inuse += ctx->inuse; + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "inuse")); + TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", + (uint64_t)ctx->inuse)); + TRY0(xmlTextWriterEndElement(writer)); /* inuse */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "maxinuse")); + TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", + (uint64_t)ctx->maxinuse)); + TRY0(xmlTextWriterEndElement(writer)); /* maxinuse */ + + summary->malloced += ctx->malloced; + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "malloced")); + TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", + (uint64_t)ctx->malloced)); + TRY0(xmlTextWriterEndElement(writer)); /* malloced */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "maxmalloced")); + TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", + (uint64_t)ctx->maxmalloced)); + TRY0(xmlTextWriterEndElement(writer)); /* maxmalloced */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "blocksize")); + if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { + summary->blocksize += ctx->basic_table_count * + NUM_BASIC_BLOCKS * ctx->mem_target; + TRY0(xmlTextWriterWriteFormatString( + writer, "%" PRIu64 "", + (uint64_t)ctx->basic_table_count * NUM_BASIC_BLOCKS * + ctx->mem_target)); + } else { + TRY0(xmlTextWriterWriteFormatString(writer, "%s", "-")); + } + TRY0(xmlTextWriterEndElement(writer)); /* blocksize */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "pools")); + TRY0(xmlTextWriterWriteFormatString(writer, "%u", ctx->poolcnt)); + TRY0(xmlTextWriterEndElement(writer)); /* pools */ + summary->contextsize += ctx->poolcnt * sizeof(isc_mempool_t); + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "hiwater")); + TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", + (uint64_t)ctx->hi_water)); + TRY0(xmlTextWriterEndElement(writer)); /* hiwater */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "lowater")); + TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", + (uint64_t)ctx->lo_water)); + TRY0(xmlTextWriterEndElement(writer)); /* lowater */ + + TRY0(xmlTextWriterEndElement(writer)); /* context */ + +error: + MCTXUNLOCK(ctx); + + return (xmlrc); +} + +int +isc_mem_renderxml(void *writer0) { + isc__mem_t *ctx; + summarystat_t summary; + uint64_t lost; + int xmlrc; + xmlTextWriterPtr writer = (xmlTextWriterPtr)writer0; + + memset(&summary, 0, sizeof(summary)); + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "contexts")); + + LOCK(&contextslock); + lost = totallost; + for (ctx = ISC_LIST_HEAD(contexts); ctx != NULL; + ctx = ISC_LIST_NEXT(ctx, link)) + { + xmlrc = xml_renderctx(ctx, &summary, writer); + if (xmlrc < 0) { + UNLOCK(&contextslock); + goto error; + } + } + UNLOCK(&contextslock); + + TRY0(xmlTextWriterEndElement(writer)); /* contexts */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "summary")); + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "TotalUse")); + TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", + summary.total)); + TRY0(xmlTextWriterEndElement(writer)); /* TotalUse */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "InUse")); + TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", + summary.inuse)); + TRY0(xmlTextWriterEndElement(writer)); /* InUse */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "Malloced")); + TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", + summary.malloced)); + TRY0(xmlTextWriterEndElement(writer)); /* InUse */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "BlockSize")); + TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", + summary.blocksize)); + TRY0(xmlTextWriterEndElement(writer)); /* BlockSize */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "ContextSize")); + TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", + summary.contextsize)); + TRY0(xmlTextWriterEndElement(writer)); /* ContextSize */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "Lost")); + TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", lost)); + TRY0(xmlTextWriterEndElement(writer)); /* Lost */ + + TRY0(xmlTextWriterEndElement(writer)); /* summary */ +error: + return (xmlrc); +} + +#endif /* HAVE_LIBXML2 */ + +#ifdef HAVE_JSON_C +#define CHECKMEM(m) RUNTIME_CHECK(m != NULL) + +static isc_result_t +json_renderctx(isc__mem_t *ctx, summarystat_t *summary, json_object *array) { + REQUIRE(VALID_CONTEXT(ctx)); + REQUIRE(summary != NULL); + REQUIRE(array != NULL); + + json_object *ctxobj, *obj; + char buf[1024]; + + MCTXLOCK(ctx); + + summary->contextsize += sizeof(*ctx) + + (ctx->max_size + 1) * sizeof(struct stats) + + ctx->max_size * sizeof(element *) + + ctx->basic_table_count * sizeof(char *); + summary->total += ctx->total; + summary->inuse += ctx->inuse; + summary->malloced += ctx->malloced; + if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { + summary->blocksize += ctx->basic_table_count * + NUM_BASIC_BLOCKS * ctx->mem_target; + } +#if ISC_MEM_TRACKLINES + if (ctx->debuglist != NULL) { + summary->contextsize += DEBUG_TABLE_COUNT * + sizeof(debuglist_t) + + ctx->debuglistcnt * sizeof(debuglink_t); + } +#endif /* if ISC_MEM_TRACKLINES */ + + ctxobj = json_object_new_object(); + CHECKMEM(ctxobj); + + snprintf(buf, sizeof(buf), "%p", ctx); + obj = json_object_new_string(buf); + CHECKMEM(obj); + json_object_object_add(ctxobj, "id", obj); + + if (ctx->name[0] != 0) { + obj = json_object_new_string(ctx->name); + CHECKMEM(obj); + json_object_object_add(ctxobj, "name", obj); + } + + obj = json_object_new_int64(isc_refcount_current(&ctx->references)); + CHECKMEM(obj); + json_object_object_add(ctxobj, "references", obj); + + obj = json_object_new_int64(ctx->total); + CHECKMEM(obj); + json_object_object_add(ctxobj, "total", obj); + + obj = json_object_new_int64(ctx->inuse); + CHECKMEM(obj); + json_object_object_add(ctxobj, "inuse", obj); + + obj = json_object_new_int64(ctx->maxinuse); + CHECKMEM(obj); + json_object_object_add(ctxobj, "maxinuse", obj); + + obj = json_object_new_int64(ctx->malloced); + CHECKMEM(obj); + json_object_object_add(ctxobj, "malloced", obj); + + obj = json_object_new_int64(ctx->maxmalloced); + CHECKMEM(obj); + json_object_object_add(ctxobj, "maxmalloced", obj); + + if ((ctx->flags & ISC_MEMFLAG_INTERNAL) != 0) { + uint64_t blocksize; + blocksize = ctx->basic_table_count * NUM_BASIC_BLOCKS * + ctx->mem_target; + obj = json_object_new_int64(blocksize); + CHECKMEM(obj); + json_object_object_add(ctxobj, "blocksize", obj); + } + + obj = json_object_new_int64(ctx->poolcnt); + CHECKMEM(obj); + json_object_object_add(ctxobj, "pools", obj); + + summary->contextsize += ctx->poolcnt * sizeof(isc_mempool_t); + + obj = json_object_new_int64(ctx->hi_water); + CHECKMEM(obj); + json_object_object_add(ctxobj, "hiwater", obj); + + obj = json_object_new_int64(ctx->lo_water); + CHECKMEM(obj); + json_object_object_add(ctxobj, "lowater", obj); + + MCTXUNLOCK(ctx); + json_object_array_add(array, ctxobj); + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_mem_renderjson(void *memobj0) { + isc_result_t result = ISC_R_SUCCESS; + isc__mem_t *ctx; + summarystat_t summary; + uint64_t lost; + json_object *ctxarray, *obj; + json_object *memobj = (json_object *)memobj0; + + memset(&summary, 0, sizeof(summary)); + + ctxarray = json_object_new_array(); + CHECKMEM(ctxarray); + + LOCK(&contextslock); + lost = totallost; + for (ctx = ISC_LIST_HEAD(contexts); ctx != NULL; + ctx = ISC_LIST_NEXT(ctx, link)) + { + result = json_renderctx(ctx, &summary, ctxarray); + if (result != ISC_R_SUCCESS) { + UNLOCK(&contextslock); + goto error; + } + } + UNLOCK(&contextslock); + + obj = json_object_new_int64(summary.total); + CHECKMEM(obj); + json_object_object_add(memobj, "TotalUse", obj); + + obj = json_object_new_int64(summary.inuse); + CHECKMEM(obj); + json_object_object_add(memobj, "InUse", obj); + + obj = json_object_new_int64(summary.malloced); + CHECKMEM(obj); + json_object_object_add(memobj, "Malloced", obj); + + obj = json_object_new_int64(summary.blocksize); + CHECKMEM(obj); + json_object_object_add(memobj, "BlockSize", obj); + + obj = json_object_new_int64(summary.contextsize); + CHECKMEM(obj); + json_object_object_add(memobj, "ContextSize", obj); + + obj = json_object_new_int64(lost); + CHECKMEM(obj); + json_object_object_add(memobj, "Lost", obj); + + json_object_object_add(memobj, "contexts", ctxarray); + return (ISC_R_SUCCESS); + +error: + if (ctxarray != NULL) { + json_object_put(ctxarray); + } + return (result); +} +#endif /* HAVE_JSON_C */ + +void +isc_mem_create(isc_mem_t **mctxp) { + mem_create(mctxp, isc_mem_defaultflags); +} + +void * +isc__mem_get(isc_mem_t *mctx, size_t size FLARG) { + REQUIRE(ISCAPI_MCTX_VALID(mctx)); + + return (mctx->methods->memget(mctx, size FLARG_PASS)); +} + +void +isc__mem_put(isc_mem_t *mctx, void *ptr, size_t size FLARG) { + REQUIRE(ISCAPI_MCTX_VALID(mctx)); + + mctx->methods->memput(mctx, ptr, size FLARG_PASS); +} + +void +isc__mem_putanddetach(isc_mem_t **mctxp, void *ptr, size_t size FLARG) { + REQUIRE(mctxp != NULL && ISCAPI_MCTX_VALID(*mctxp)); + + (*mctxp)->methods->memputanddetach(mctxp, ptr, size FLARG_PASS); +} + +void * +isc__mem_allocate(isc_mem_t *mctx, size_t size FLARG) { + REQUIRE(ISCAPI_MCTX_VALID(mctx)); + + return (mctx->methods->memallocate(mctx, size FLARG_PASS)); +} + +void * +isc__mem_reallocate(isc_mem_t *mctx, void *ptr, size_t size FLARG) { + REQUIRE(ISCAPI_MCTX_VALID(mctx)); + + return (mctx->methods->memreallocate(mctx, ptr, size FLARG_PASS)); +} + +char * +isc__mem_strdup(isc_mem_t *mctx, const char *s FLARG) { + REQUIRE(ISCAPI_MCTX_VALID(mctx)); + + return (mctx->methods->memstrdup(mctx, s FLARG_PASS)); +} + +char * +isc__mem_strndup(isc_mem_t *mctx, const char *s, size_t size FLARG) { + REQUIRE(ISCAPI_MCTX_VALID(mctx)); + + return (mctx->methods->memstrndup(mctx, s, size FLARG_PASS)); +} + +void +isc__mem_free(isc_mem_t *mctx, void *ptr FLARG) { + REQUIRE(ISCAPI_MCTX_VALID(mctx)); + + mctx->methods->memfree(mctx, ptr FLARG_PASS); +} + +void +isc__mem_printactive(isc_mem_t *ctx0, FILE *file) { +#if ISC_MEM_TRACKLINES + REQUIRE(VALID_CONTEXT(ctx0)); + REQUIRE(file != NULL); + + isc__mem_t *ctx = (isc__mem_t *)ctx0; + + print_active(ctx, file); +#else /* if ISC_MEM_TRACKLINES */ + UNUSED(ctx0); + UNUSED(file); +#endif /* if ISC_MEM_TRACKLINES */ +} diff --git a/lib/isc/mem_p.h b/lib/isc/mem_p.h new file mode 100644 index 0000000..611a025 --- /dev/null +++ b/lib/isc/mem_p.h @@ -0,0 +1,36 @@ +/* + * 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. + */ + +#pragma once + +#include <stdio.h> + +#include <isc/mem.h> + +/*! \file */ + +void +isc__mem_printactive(isc_mem_t *mctx, FILE *file); +/*%< + * For use by unit tests, prints active memory blocks for + * a single memory context. + */ + +void +isc__mem_checkdestroyed(void); + +void +isc__mem_initialize(void); + +void +isc__mem_shutdown(void); diff --git a/lib/isc/mutexblock.c b/lib/isc/mutexblock.c new file mode 100644 index 0000000..56a2985 --- /dev/null +++ b/lib/isc/mutexblock.c @@ -0,0 +1,35 @@ +/* + * 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. + */ + +/*! \file */ + +#include <isc/mutexblock.h> +#include <isc/util.h> + +void +isc_mutexblock_init(isc_mutex_t *block, unsigned int count) { + unsigned int i; + + for (i = 0; i < count; i++) { + isc_mutex_init(&block[i]); + } +} + +void +isc_mutexblock_destroy(isc_mutex_t *block, unsigned int count) { + unsigned int i; + + for (i = 0; i < count; i++) { + isc_mutex_destroy(&block[i]); + } +} diff --git a/lib/isc/netaddr.c b/lib/isc/netaddr.c new file mode 100644 index 0000000..39aaae6 --- /dev/null +++ b/lib/isc/netaddr.c @@ -0,0 +1,479 @@ +/* + * 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. + */ + +/*! \file */ + +#include <inttypes.h> +#include <stdbool.h> +#include <stdio.h> + +#include <isc/buffer.h> +#include <isc/net.h> +#include <isc/netaddr.h> +#include <isc/print.h> +#include <isc/sockaddr.h> +#include <isc/string.h> +#include <isc/util.h> + +bool +isc_netaddr_equal(const isc_netaddr_t *a, const isc_netaddr_t *b) { + REQUIRE(a != NULL && b != NULL); + + if (a->family != b->family) { + return (false); + } + + if (a->zone != b->zone) { + return (false); + } + + switch (a->family) { + case AF_INET: + if (a->type.in.s_addr != b->type.in.s_addr) { + return (false); + } + break; + case AF_INET6: + if (memcmp(&a->type.in6, &b->type.in6, sizeof(a->type.in6)) != + 0 || + a->zone != b->zone) + { + return (false); + } + break; +#ifdef ISC_PLATFORM_HAVESYSUNH + case AF_UNIX: + if (strcmp(a->type.un, b->type.un) != 0) { + return (false); + } + break; +#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */ + default: + return (false); + } + return (true); +} + +bool +isc_netaddr_eqprefix(const isc_netaddr_t *a, const isc_netaddr_t *b, + unsigned int prefixlen) { + const unsigned char *pa = NULL, *pb = NULL; + unsigned int ipabytes = 0; /* Length of whole IP address in bytes */ + unsigned int nbytes; /* Number of significant whole bytes */ + unsigned int nbits; /* Number of significant leftover bits */ + + REQUIRE(a != NULL && b != NULL); + + if (a->family != b->family) { + return (false); + } + + if (a->zone != b->zone && b->zone != 0) { + return (false); + } + + switch (a->family) { + case AF_INET: + pa = (const unsigned char *)&a->type.in; + pb = (const unsigned char *)&b->type.in; + ipabytes = 4; + break; + case AF_INET6: + pa = (const unsigned char *)&a->type.in6; + pb = (const unsigned char *)&b->type.in6; + ipabytes = 16; + break; + default: + return (false); + } + + /* + * Don't crash if we get a pattern like 10.0.0.1/9999999. + */ + if (prefixlen > ipabytes * 8) { + prefixlen = ipabytes * 8; + } + + nbytes = prefixlen / 8; + nbits = prefixlen % 8; + + if (nbytes > 0) { + if (memcmp(pa, pb, nbytes) != 0) { + return (false); + } + } + if (nbits > 0) { + unsigned int bytea, byteb, mask; + INSIST(nbytes < ipabytes); + INSIST(nbits < 8); + bytea = pa[nbytes]; + byteb = pb[nbytes]; + mask = (0xFF << (8 - nbits)) & 0xFF; + if ((bytea & mask) != (byteb & mask)) { + return (false); + } + } + return (true); +} + +isc_result_t +isc_netaddr_totext(const isc_netaddr_t *netaddr, isc_buffer_t *target) { + char abuf[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:255.255.255.255")]; + char zbuf[sizeof("%4294967295")]; + unsigned int alen; + int zlen; + const char *r; + const void *type; + + REQUIRE(netaddr != NULL); + + switch (netaddr->family) { + case AF_INET: + type = &netaddr->type.in; + break; + case AF_INET6: + type = &netaddr->type.in6; + break; +#ifdef ISC_PLATFORM_HAVESYSUNH + case AF_UNIX: + alen = strlen(netaddr->type.un); + if (alen > isc_buffer_availablelength(target)) { + return (ISC_R_NOSPACE); + } + isc_buffer_putmem(target, + (const unsigned char *)(netaddr->type.un), + alen); + return (ISC_R_SUCCESS); +#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */ + default: + return (ISC_R_FAILURE); + } + r = inet_ntop(netaddr->family, type, abuf, sizeof(abuf)); + if (r == NULL) { + return (ISC_R_FAILURE); + } + + alen = strlen(abuf); + INSIST(alen < sizeof(abuf)); + + zlen = 0; + if (netaddr->family == AF_INET6 && netaddr->zone != 0) { + zlen = snprintf(zbuf, sizeof(zbuf), "%%%u", netaddr->zone); + if (zlen < 0) { + return (ISC_R_FAILURE); + } + INSIST((unsigned int)zlen < sizeof(zbuf)); + } + + if (alen + zlen > isc_buffer_availablelength(target)) { + return (ISC_R_NOSPACE); + } + + isc_buffer_putmem(target, (unsigned char *)abuf, alen); + isc_buffer_putmem(target, (unsigned char *)zbuf, (unsigned int)zlen); + + return (ISC_R_SUCCESS); +} + +void +isc_netaddr_format(const isc_netaddr_t *na, char *array, unsigned int size) { + isc_result_t result; + isc_buffer_t buf; + + isc_buffer_init(&buf, array, size); + result = isc_netaddr_totext(na, &buf); + + if (size == 0) { + return; + } + + /* + * Null terminate. + */ + if (result == ISC_R_SUCCESS) { + if (isc_buffer_availablelength(&buf) >= 1) { + isc_buffer_putuint8(&buf, 0); + } else { + result = ISC_R_NOSPACE; + } + } + + if (result != ISC_R_SUCCESS) { + snprintf(array, size, "<unknown address, family %u>", + na->family); + array[size - 1] = '\0'; + } +} + +isc_result_t +isc_netaddr_prefixok(const isc_netaddr_t *na, unsigned int prefixlen) { + static const unsigned char zeros[16]; + unsigned int nbits, nbytes, ipbytes = 0; + const unsigned char *p; + + switch (na->family) { + case AF_INET: + p = (const unsigned char *)&na->type.in; + ipbytes = 4; + if (prefixlen > 32) { + return (ISC_R_RANGE); + } + break; + case AF_INET6: + p = (const unsigned char *)&na->type.in6; + ipbytes = 16; + if (prefixlen > 128) { + return (ISC_R_RANGE); + } + break; + default: + return (ISC_R_NOTIMPLEMENTED); + } + nbytes = prefixlen / 8; + nbits = prefixlen % 8; + if (nbits != 0) { + INSIST(nbytes < ipbytes); + if ((p[nbytes] & (0xff >> nbits)) != 0U) { + return (ISC_R_FAILURE); + } + nbytes++; + } + if (nbytes < ipbytes && + memcmp(p + nbytes, zeros, ipbytes - nbytes) != 0) + { + return (ISC_R_FAILURE); + } + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_netaddr_masktoprefixlen(const isc_netaddr_t *s, unsigned int *lenp) { + unsigned int nbits = 0, nbytes = 0, ipbytes = 0, i; + const unsigned char *p; + + switch (s->family) { + case AF_INET: + p = (const unsigned char *)&s->type.in; + ipbytes = 4; + break; + case AF_INET6: + p = (const unsigned char *)&s->type.in6; + ipbytes = 16; + break; + default: + return (ISC_R_NOTIMPLEMENTED); + } + for (i = 0; i < ipbytes; i++) { + if (p[i] != 0xFF) { + break; + } + } + nbytes = i; + if (i < ipbytes) { + unsigned int c = p[nbytes]; + while ((c & 0x80) != 0 && nbits < 8) { + c <<= 1; + nbits++; + } + if ((c & 0xFF) != 0) { + return (ISC_R_MASKNONCONTIG); + } + i++; + } + for (; i < ipbytes; i++) { + if (p[i] != 0) { + return (ISC_R_MASKNONCONTIG); + } + } + *lenp = nbytes * 8 + nbits; + return (ISC_R_SUCCESS); +} + +void +isc_netaddr_fromin(isc_netaddr_t *netaddr, const struct in_addr *ina) { + memset(netaddr, 0, sizeof(*netaddr)); + netaddr->family = AF_INET; + netaddr->type.in = *ina; +} + +void +isc_netaddr_fromin6(isc_netaddr_t *netaddr, const struct in6_addr *ina6) { + memset(netaddr, 0, sizeof(*netaddr)); + netaddr->family = AF_INET6; + netaddr->type.in6 = *ina6; +} + +isc_result_t +isc_netaddr_frompath(isc_netaddr_t *netaddr, const char *path) { +#ifdef ISC_PLATFORM_HAVESYSUNH + if (strlen(path) > sizeof(netaddr->type.un) - 1) { + return (ISC_R_NOSPACE); + } + + memset(netaddr, 0, sizeof(*netaddr)); + netaddr->family = AF_UNIX; + strlcpy(netaddr->type.un, path, sizeof(netaddr->type.un)); + netaddr->zone = 0; + return (ISC_R_SUCCESS); +#else /* ifdef ISC_PLATFORM_HAVESYSUNH */ + UNUSED(netaddr); + UNUSED(path); + return (ISC_R_NOTIMPLEMENTED); +#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */ +} + +void +isc_netaddr_setzone(isc_netaddr_t *netaddr, uint32_t zone) { + /* we currently only support AF_INET6. */ + REQUIRE(netaddr->family == AF_INET6); + + netaddr->zone = zone; +} + +uint32_t +isc_netaddr_getzone(const isc_netaddr_t *netaddr) { + return (netaddr->zone); +} + +void +isc_netaddr_fromsockaddr(isc_netaddr_t *t, const isc_sockaddr_t *s) { + int family = s->type.sa.sa_family; + t->family = family; + switch (family) { + case AF_INET: + t->type.in = s->type.sin.sin_addr; + t->zone = 0; + break; + case AF_INET6: + memmove(&t->type.in6, &s->type.sin6.sin6_addr, 16); + t->zone = s->type.sin6.sin6_scope_id; + break; +#ifdef ISC_PLATFORM_HAVESYSUNH + case AF_UNIX: + memmove(t->type.un, s->type.sunix.sun_path, sizeof(t->type.un)); + t->zone = 0; + break; +#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */ + default: + UNREACHABLE(); + } +} + +void +isc_netaddr_any(isc_netaddr_t *netaddr) { + memset(netaddr, 0, sizeof(*netaddr)); + netaddr->family = AF_INET; + netaddr->type.in.s_addr = INADDR_ANY; +} + +void +isc_netaddr_any6(isc_netaddr_t *netaddr) { + memset(netaddr, 0, sizeof(*netaddr)); + netaddr->family = AF_INET6; + netaddr->type.in6 = in6addr_any; +} + +void +isc_netaddr_unspec(isc_netaddr_t *netaddr) { + memset(netaddr, 0, sizeof(*netaddr)); + netaddr->family = AF_UNSPEC; +} + +bool +isc_netaddr_ismulticast(const isc_netaddr_t *na) { + switch (na->family) { + case AF_INET: + return (ISC_IPADDR_ISMULTICAST(na->type.in.s_addr)); + case AF_INET6: + return (IN6_IS_ADDR_MULTICAST(&na->type.in6)); + default: + return (false); /* XXXMLG ? */ + } +} + +bool +isc_netaddr_isexperimental(const isc_netaddr_t *na) { + switch (na->family) { + case AF_INET: + return (ISC_IPADDR_ISEXPERIMENTAL(na->type.in.s_addr)); + default: + return (false); /* XXXMLG ? */ + } +} + +bool +isc_netaddr_islinklocal(const isc_netaddr_t *na) { + switch (na->family) { + case AF_INET: + return (false); + case AF_INET6: + return (IN6_IS_ADDR_LINKLOCAL(&na->type.in6)); + default: + return (false); + } +} + +bool +isc_netaddr_issitelocal(const isc_netaddr_t *na) { + switch (na->family) { + case AF_INET: + return (false); + case AF_INET6: + return (IN6_IS_ADDR_SITELOCAL(&na->type.in6)); + default: + return (false); + } +} + +#define ISC_IPADDR_ISNETZERO(i) \ + (((uint32_t)(i)&ISC__IPADDR(0xff000000)) == ISC__IPADDR(0x00000000)) + +bool +isc_netaddr_isnetzero(const isc_netaddr_t *na) { + switch (na->family) { + case AF_INET: + return (ISC_IPADDR_ISNETZERO(na->type.in.s_addr)); + case AF_INET6: + return (false); + default: + return (false); + } +} + +void +isc_netaddr_fromv4mapped(isc_netaddr_t *t, const isc_netaddr_t *s) { + isc_netaddr_t *src; + + DE_CONST(s, src); /* Must come before IN6_IS_ADDR_V4MAPPED. */ + + REQUIRE(s->family == AF_INET6); + REQUIRE(IN6_IS_ADDR_V4MAPPED(&src->type.in6)); + + memset(t, 0, sizeof(*t)); + t->family = AF_INET; + memmove(&t->type.in, (char *)&src->type.in6 + 12, 4); + return; +} + +bool +isc_netaddr_isloopback(const isc_netaddr_t *na) { + switch (na->family) { + case AF_INET: + return (((ntohl(na->type.in.s_addr) & 0xff000000U) == + 0x7f000000U)); + case AF_INET6: + return (IN6_IS_ADDR_LOOPBACK(&na->type.in6)); + default: + return (false); + } +} diff --git a/lib/isc/netmgr/Makefile.in b/lib/isc/netmgr/Makefile.in new file mode 100644 index 0000000..1806cb4 --- /dev/null +++ b/lib/isc/netmgr/Makefile.in @@ -0,0 +1,41 @@ +# 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. + +srcdir = @srcdir@ +VPATH = @srcdir@ +top_srcdir = @top_srcdir@ + +CINCLUDES = -I${srcdir}/include \ + -I${srcdir}/../unix/include \ + -I${srcdir}/../pthreads/include \ + -I../include \ + -I${srcdir}/../include \ + -I${srcdir}/.. \ + ${OPENSSL_CFLAGS} \ + ${JSON_C_CFLAGS} \ + ${LIBUV_CFLAGS} \ + ${LIBXML2_CFLAGS} + +CDEFINES = +CWARNINGS = + +# Alphabetically +OBJS = netmgr.@O@ tcp.@O@ udp.@O@ \ + tcpdns.@O@ \ + uverr2result.@O@ uv-compat.@O@ + +# Alphabetically +SRCS = netmgr.c tcp.c udp.c tcpdns.c \ + uverr2result.c uv-compat.c + +TARGETS = ${OBJS} + +@BIND9_MAKE_RULES@ diff --git a/lib/isc/netmgr/netmgr-int.h b/lib/isc/netmgr/netmgr-int.h new file mode 100644 index 0000000..05fde1a --- /dev/null +++ b/lib/isc/netmgr/netmgr-int.h @@ -0,0 +1,1615 @@ +/* + * 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. + */ + +#pragma once + +#include <unistd.h> +#include <uv.h> + +#include <openssl/err.h> +#include <openssl/ssl.h> + +#include <isc/astack.h> +#include <isc/atomic.h> +#include <isc/barrier.h> +#include <isc/buffer.h> +#include <isc/condition.h> +#include <isc/magic.h> +#include <isc/mem.h> +#include <isc/netmgr.h> +#include <isc/quota.h> +#include <isc/random.h> +#include <isc/refcount.h> +#include <isc/region.h> +#include <isc/result.h> +#include <isc/rwlock.h> +#include <isc/sockaddr.h> +#include <isc/stats.h> +#include <isc/thread.h> +#include <isc/util.h> + +#include "uv-compat.h" + +#define ISC_NETMGR_TID_UNKNOWN -1 + +/* Must be different from ISC_NETMGR_TID_UNKNOWN */ +#define ISC_NETMGR_NON_INTERLOCKED -2 + +/* + * Receive buffers + */ +#if HAVE_DECL_UV_UDP_MMSG_CHUNK +/* + * The value 20 here is UV__MMSG_MAXWIDTH taken from the current libuv source, + * libuv will not receive more that 20 datagrams in a single recvmmsg call. + */ +#define ISC_NETMGR_UDP_RECVBUF_SIZE (20 * UINT16_MAX) +#else +/* + * A single DNS message size + */ +#define ISC_NETMGR_UDP_RECVBUF_SIZE UINT16_MAX +#endif + +/* + * The TCP receive buffer can fit one maximum sized DNS message plus its size, + * the receive buffer here affects TCP, DoT and DoH. + */ +#define ISC_NETMGR_TCP_RECVBUF_SIZE (sizeof(uint16_t) + UINT16_MAX) + +/* Pick the larger buffer */ +#define ISC_NETMGR_RECVBUF_SIZE \ + (ISC_NETMGR_UDP_RECVBUF_SIZE >= ISC_NETMGR_TCP_RECVBUF_SIZE \ + ? ISC_NETMGR_UDP_RECVBUF_SIZE \ + : ISC_NETMGR_TCP_RECVBUF_SIZE) + +/* + * Send buffer + */ +#define ISC_NETMGR_SENDBUF_SIZE (sizeof(uint16_t) + UINT16_MAX) + +/*% + * Regular TCP buffer size. + */ +#define NM_REG_BUF 4096 + +/*% + * Larger buffer for when the regular one isn't enough; this will + * hold two full DNS packets with lengths. netmgr receives 64k at + * most in TCPDNS connections, so there's no risk of overrun + * when using a buffer this size. + */ +#define NM_BIG_BUF ISC_NETMGR_TCP_RECVBUF_SIZE * 2 + +/* + * Define NETMGR_TRACE to activate tracing of handles and sockets. + * This will impair performance but enables us to quickly determine, + * if netmgr resources haven't been cleaned up on shutdown, which ones + * are still in use. + */ +#ifdef NETMGR_TRACE +#define TRACE_SIZE 8 + +void +isc__nm_dump_active(isc_nm_t *nm); + +#if defined(__linux__) +#include <syscall.h> +#define gettid() (uint32_t) syscall(SYS_gettid) +#elif defined(_WIN32) +#define gettid() (uint32_t) GetCurrentThreadId() +#else +#define gettid() (uint32_t) pthread_self() +#endif + +#ifdef NETMGR_TRACE_VERBOSE +#define NETMGR_TRACE_LOG(format, ...) \ + fprintf(stderr, "%" PRIu32 ":%d:%s:%u:%s:" format, gettid(), \ + isc_nm_tid(), file, line, func, __VA_ARGS__) +#else +#define NETMGR_TRACE_LOG(format, ...) \ + (void)file; \ + (void)line; \ + (void)func; +#endif + +#define FLARG_PASS , file, line, func +#define FLARG \ + , const char *file __attribute__((unused)), \ + unsigned int line __attribute__((unused)), \ + const char *func __attribute__((unused)) +#define FLARG_IEVENT(ievent) \ + const char *file = ievent->file; \ + unsigned int line = ievent->line; \ + const char *func = ievent->func; +#define FLARG_IEVENT_PASS(ievent) \ + ievent->file = file; \ + ievent->line = line; \ + ievent->func = func; +#define isc__nm_uvreq_get(req, sock) \ + isc___nm_uvreq_get(req, sock, __FILE__, __LINE__, __func__) +#define isc__nm_uvreq_put(req, sock) \ + isc___nm_uvreq_put(req, sock, __FILE__, __LINE__, __func__) +#define isc__nmsocket_init(sock, mgr, type, iface) \ + isc___nmsocket_init(sock, mgr, type, iface, __FILE__, __LINE__, \ + __func__) +#define isc__nmsocket_put(sockp) \ + isc___nmsocket_put(sockp, __FILE__, __LINE__, __func__) +#define isc__nmsocket_attach(sock, target) \ + isc___nmsocket_attach(sock, target, __FILE__, __LINE__, __func__) +#define isc__nmsocket_detach(socketp) \ + isc___nmsocket_detach(socketp, __FILE__, __LINE__, __func__) +#define isc__nmsocket_close(socketp) \ + isc___nmsocket_close(socketp, __FILE__, __LINE__, __func__) +#define isc__nmhandle_get(sock, peer, local) \ + isc___nmhandle_get(sock, peer, local, __FILE__, __LINE__, __func__) +#define isc__nmsocket_prep_destroy(sock) \ + isc___nmsocket_prep_destroy(sock, __FILE__, __LINE__, __func__) +#else +#define NETMGR_TRACE_LOG(format, ...) + +#define FLARG_PASS +#define FLARG +#define FLARG_IEVENT(ievent) +#define FLARG_IEVENT_PASS(ievent) +#define isc__nm_uvreq_get(req, sock) isc___nm_uvreq_get(req, sock) +#define isc__nm_uvreq_put(req, sock) isc___nm_uvreq_put(req, sock) +#define isc__nmsocket_init(sock, mgr, type, iface) \ + isc___nmsocket_init(sock, mgr, type, iface) +#define isc__nmsocket_put(sockp) isc___nmsocket_put(sockp) +#define isc__nmsocket_attach(sock, target) isc___nmsocket_attach(sock, target) +#define isc__nmsocket_detach(socketp) isc___nmsocket_detach(socketp) +#define isc__nmsocket_close(socketp) isc___nmsocket_close(socketp) +#define isc__nmhandle_get(sock, peer, local) \ + isc___nmhandle_get(sock, peer, local) +#define isc__nmsocket_prep_destroy(sock) isc___nmsocket_prep_destroy(sock) +#endif + +/* + * Queue types in the order of processing priority. + */ +typedef enum { + NETIEVENT_PRIORITY = 0, + NETIEVENT_PRIVILEGED = 1, + NETIEVENT_TASK = 2, + NETIEVENT_NORMAL = 3, + NETIEVENT_MAX = 4, +} netievent_type_t; + +typedef struct isc__nm_uvreq isc__nm_uvreq_t; +typedef struct isc__netievent isc__netievent_t; + +typedef ISC_LIST(isc__netievent_t) isc__netievent_list_t; + +typedef struct ievent { + isc_mutex_t lock; + isc_condition_t cond; + isc__netievent_list_t list; +} ievent_t; + +/* + * Single network event loop worker. + */ +typedef struct isc__networker { + isc_nm_t *mgr; + int id; /* thread id */ + uv_loop_t loop; /* libuv loop structure */ + uv_async_t async; /* async channel to send + * data to this networker */ + bool paused; + bool finished; + isc_thread_t thread; + ievent_t ievents[NETIEVENT_MAX]; + + isc_refcount_t references; + atomic_int_fast64_t pktcount; + char *recvbuf; + char *sendbuf; + bool recvbuf_inuse; +} isc__networker_t; + +/* + * A general handle for a connection bound to a networker. For UDP + * connections we have peer address here, so both TCP and UDP can be + * handled with a simple send-like function + */ +#define NMHANDLE_MAGIC ISC_MAGIC('N', 'M', 'H', 'D') +#define VALID_NMHANDLE(t) \ + (ISC_MAGIC_VALID(t, NMHANDLE_MAGIC) && \ + atomic_load(&(t)->references) > 0) + +typedef void (*isc__nm_closecb)(isc_nmhandle_t *); + +struct isc_nmhandle { + int magic; + isc_refcount_t references; + + /* + * The socket is not 'attached' in the traditional + * reference-counting sense. Instead, we keep all handles in an + * array in the socket object. This way, we don't have circular + * dependencies and we can close all handles when we're destroying + * the socket. + */ + isc_nmsocket_t *sock; + + isc_sockaddr_t peer; + isc_sockaddr_t local; + isc_nm_opaquecb_t doreset; /* reset extra callback, external */ + isc_nm_opaquecb_t dofree; /* free extra callback, external */ +#ifdef NETMGR_TRACE + void *backtrace[TRACE_SIZE]; + int backtrace_size; + LINK(isc_nmhandle_t) active_link; +#endif + void *opaque; + char extra[]; +}; + +typedef enum isc__netievent_type { + netievent_udpconnect, + netievent_udpclose, + netievent_udpsend, + netievent_udpread, + netievent_udpcancel, + + netievent_tcpconnect, + netievent_tcpclose, + netievent_tcpsend, + netievent_tcpstartread, + netievent_tcppauseread, + netievent_tcpaccept, + netievent_tcpcancel, + + netievent_tcpdnsaccept, + netievent_tcpdnsconnect, + netievent_tcpdnsclose, + netievent_tcpdnssend, + netievent_tcpdnsread, + netievent_tcpdnscancel, + + netievent_shutdown, + netievent_stop, + netievent_pause, + + netievent_connectcb, + netievent_readcb, + netievent_sendcb, + + netievent_detach, + netievent_close, + + netievent_task, + netievent_privilegedtask, + + /* + * event type values higher than this will be treated + * as high-priority events, which can be processed + * while the netmgr is pausing or paused. + */ + netievent_prio = 0xff, + + netievent_udplisten, + netievent_udpstop, + netievent_tcplisten, + netievent_tcpstop, + netievent_tcpdnslisten, + netievent_tcpdnsstop, + + netievent_resume, +} isc__netievent_type; + +typedef union { + isc_nm_recv_cb_t recv; + isc_nm_cb_t send; + isc_nm_cb_t connect; + isc_nm_accept_cb_t accept; +} isc__nm_cb_t; + +/* + * Wrapper around uv_req_t with 'our' fields in it. req->data should + * always point to its parent. Note that we always allocate more than + * sizeof(struct) because we make room for different req types; + */ +#define UVREQ_MAGIC ISC_MAGIC('N', 'M', 'U', 'R') +#define VALID_UVREQ(t) ISC_MAGIC_VALID(t, UVREQ_MAGIC) + +typedef struct isc__nm_uvreq isc__nm_uvreq_t; +struct isc__nm_uvreq { + int magic; + isc_nmsocket_t *sock; + isc_nmhandle_t *handle; + char tcplen[2]; /* The TCP DNS message length */ + uv_buf_t uvbuf; /* translated isc_region_t, to be + * sent or received */ + isc_sockaddr_t local; /* local address */ + isc_sockaddr_t peer; /* peer address */ + isc__nm_cb_t cb; /* callback */ + void *cbarg; /* callback argument */ + isc_nm_timer_t *timer; /* TCP write timer */ + + union { + uv_handle_t handle; + uv_req_t req; + uv_getaddrinfo_t getaddrinfo; + uv_getnameinfo_t getnameinfo; + uv_shutdown_t shutdown; + uv_write_t write; + uv_connect_t connect; + uv_udp_send_t udp_send; + uv_fs_t fs; + uv_work_t work; + } uv_req; + ISC_LINK(isc__nm_uvreq_t) link; +}; + +struct isc_nm_timer { + isc_refcount_t references; + uv_timer_t timer; + isc_nmhandle_t *handle; + isc_nm_timer_cb cb; + void *cbarg; +}; + +void * +isc__nm_get_netievent(isc_nm_t *mgr, isc__netievent_type type); +/*%< + * Allocate an ievent and set the type. + */ +void +isc__nm_put_netievent(isc_nm_t *mgr, void *ievent); + +/* + * The macros here are used to simulate the "inheritance" in C, there's the base + * netievent structure that contains just its own type and socket, and there are + * extended netievent types that also have handles or requests or other data. + * + * The macros here ensure that: + * + * 1. every netievent type has matching definition, declaration and + * implementation + * + * 2. we handle all the netievent types of same subclass the same, e.g. if the + * extended netievent contains handle, we always attach to the handle in + * the ctor and detach from the handle in dtor. + * + * There are three macros here for each netievent subclass: + * + * 1. NETIEVENT_*_TYPE(type) creates the typedef for each type; used below in + * this header + * + * 2. NETIEVENT_*_DECL(type) generates the declaration of the get and put + * functions (isc__nm_get_netievent_* and isc__nm_put_netievent_*); used + * below in this header + * + * 3. NETIEVENT_*_DEF(type) generates the definition of the functions; used + * either in netmgr.c or matching protocol file (e.g. udp.c, tcp.c, etc.) + */ + +#define NETIEVENT__SOCKET \ + isc__netievent_type type; \ + ISC_LINK(isc__netievent_t) link; \ + isc_nmsocket_t *sock; \ + const char *file; \ + unsigned int line; \ + const char *func + +typedef struct isc__netievent__socket { + NETIEVENT__SOCKET; +} isc__netievent__socket_t; + +#define NETIEVENT_SOCKET_TYPE(type) \ + typedef isc__netievent__socket_t isc__netievent_##type##_t; + +#define NETIEVENT_SOCKET_DECL(type) \ + isc__netievent_##type##_t *isc__nm_get_netievent_##type( \ + isc_nm_t *nm, isc_nmsocket_t *sock); \ + void isc__nm_put_netievent_##type(isc_nm_t *nm, \ + isc__netievent_##type##_t *ievent); + +#define NETIEVENT_SOCKET_DEF(type) \ + isc__netievent_##type##_t *isc__nm_get_netievent_##type( \ + isc_nm_t *nm, isc_nmsocket_t *sock) { \ + isc__netievent_##type##_t *ievent = \ + isc__nm_get_netievent(nm, netievent_##type); \ + isc__nmsocket_attach(sock, &ievent->sock); \ + \ + return (ievent); \ + } \ + \ + void isc__nm_put_netievent_##type(isc_nm_t *nm, \ + isc__netievent_##type##_t *ievent) { \ + isc__nmsocket_detach(&ievent->sock); \ + isc__nm_put_netievent(nm, ievent); \ + } + +typedef struct isc__netievent__socket_req { + NETIEVENT__SOCKET; + isc__nm_uvreq_t *req; +} isc__netievent__socket_req_t; + +#define NETIEVENT_SOCKET_REQ_TYPE(type) \ + typedef isc__netievent__socket_req_t isc__netievent_##type##_t; + +#define NETIEVENT_SOCKET_REQ_DECL(type) \ + isc__netievent_##type##_t *isc__nm_get_netievent_##type( \ + isc_nm_t *nm, isc_nmsocket_t *sock, isc__nm_uvreq_t *req); \ + void isc__nm_put_netievent_##type(isc_nm_t *nm, \ + isc__netievent_##type##_t *ievent); + +#define NETIEVENT_SOCKET_REQ_DEF(type) \ + isc__netievent_##type##_t *isc__nm_get_netievent_##type( \ + isc_nm_t *nm, isc_nmsocket_t *sock, isc__nm_uvreq_t *req) { \ + isc__netievent_##type##_t *ievent = \ + isc__nm_get_netievent(nm, netievent_##type); \ + isc__nmsocket_attach(sock, &ievent->sock); \ + ievent->req = req; \ + \ + return (ievent); \ + } \ + \ + void isc__nm_put_netievent_##type(isc_nm_t *nm, \ + isc__netievent_##type##_t *ievent) { \ + isc__nmsocket_detach(&ievent->sock); \ + isc__nm_put_netievent(nm, ievent); \ + } + +typedef struct isc__netievent__socket_req_result { + NETIEVENT__SOCKET; + isc__nm_uvreq_t *req; + isc_result_t result; +} isc__netievent__socket_req_result_t; + +#define NETIEVENT_SOCKET_REQ_RESULT_TYPE(type) \ + typedef isc__netievent__socket_req_result_t isc__netievent_##type##_t; + +#define NETIEVENT_SOCKET_REQ_RESULT_DECL(type) \ + isc__netievent_##type##_t *isc__nm_get_netievent_##type( \ + isc_nm_t *nm, isc_nmsocket_t *sock, isc__nm_uvreq_t *req, \ + isc_result_t result); \ + void isc__nm_put_netievent_##type(isc_nm_t *nm, \ + isc__netievent_##type##_t *ievent); + +#define NETIEVENT_SOCKET_REQ_RESULT_DEF(type) \ + isc__netievent_##type##_t *isc__nm_get_netievent_##type( \ + isc_nm_t *nm, isc_nmsocket_t *sock, isc__nm_uvreq_t *req, \ + isc_result_t result) { \ + isc__netievent_##type##_t *ievent = \ + isc__nm_get_netievent(nm, netievent_##type); \ + isc__nmsocket_attach(sock, &ievent->sock); \ + ievent->req = req; \ + ievent->result = result; \ + \ + return (ievent); \ + } \ + \ + void isc__nm_put_netievent_##type(isc_nm_t *nm, \ + isc__netievent_##type##_t *ievent) { \ + isc__nmsocket_detach(&ievent->sock); \ + isc__nm_put_netievent(nm, ievent); \ + } + +typedef struct isc__netievent__socket_handle { + NETIEVENT__SOCKET; + isc_nmhandle_t *handle; +} isc__netievent__socket_handle_t; + +#define NETIEVENT_SOCKET_HANDLE_TYPE(type) \ + typedef isc__netievent__socket_handle_t isc__netievent_##type##_t; + +#define NETIEVENT_SOCKET_HANDLE_DECL(type) \ + isc__netievent_##type##_t *isc__nm_get_netievent_##type( \ + isc_nm_t *nm, isc_nmsocket_t *sock, isc_nmhandle_t *handle); \ + void isc__nm_put_netievent_##type(isc_nm_t *nm, \ + isc__netievent_##type##_t *ievent); + +#define NETIEVENT_SOCKET_HANDLE_DEF(type) \ + isc__netievent_##type##_t *isc__nm_get_netievent_##type( \ + isc_nm_t *nm, isc_nmsocket_t *sock, isc_nmhandle_t *handle) { \ + isc__netievent_##type##_t *ievent = \ + isc__nm_get_netievent(nm, netievent_##type); \ + isc__nmsocket_attach(sock, &ievent->sock); \ + isc_nmhandle_attach(handle, &ievent->handle); \ + \ + return (ievent); \ + } \ + \ + void isc__nm_put_netievent_##type(isc_nm_t *nm, \ + isc__netievent_##type##_t *ievent) { \ + isc__nmsocket_detach(&ievent->sock); \ + isc_nmhandle_detach(&ievent->handle); \ + isc__nm_put_netievent(nm, ievent); \ + } + +typedef struct isc__netievent__socket_quota { + NETIEVENT__SOCKET; + isc_quota_t *quota; +} isc__netievent__socket_quota_t; + +#define NETIEVENT_SOCKET_QUOTA_TYPE(type) \ + typedef isc__netievent__socket_quota_t isc__netievent_##type##_t; + +#define NETIEVENT_SOCKET_QUOTA_DECL(type) \ + isc__netievent_##type##_t *isc__nm_get_netievent_##type( \ + isc_nm_t *nm, isc_nmsocket_t *sock, isc_quota_t *quota); \ + void isc__nm_put_netievent_##type(isc_nm_t *nm, \ + isc__netievent_##type##_t *ievent); + +#define NETIEVENT_SOCKET_QUOTA_DEF(type) \ + isc__netievent_##type##_t *isc__nm_get_netievent_##type( \ + isc_nm_t *nm, isc_nmsocket_t *sock, isc_quota_t *quota) { \ + isc__netievent_##type##_t *ievent = \ + isc__nm_get_netievent(nm, netievent_##type); \ + isc__nmsocket_attach(sock, &ievent->sock); \ + ievent->quota = quota; \ + \ + return (ievent); \ + } \ + \ + void isc__nm_put_netievent_##type(isc_nm_t *nm, \ + isc__netievent_##type##_t *ievent) { \ + isc__nmsocket_detach(&ievent->sock); \ + isc__nm_put_netievent(nm, ievent); \ + } + +typedef struct isc__netievent__task { + isc__netievent_type type; + ISC_LINK(isc__netievent_t) link; + isc_task_t *task; +} isc__netievent__task_t; + +#define NETIEVENT_TASK_TYPE(type) \ + typedef isc__netievent__task_t isc__netievent_##type##_t; + +#define NETIEVENT_TASK_DECL(type) \ + isc__netievent_##type##_t *isc__nm_get_netievent_##type( \ + isc_nm_t *nm, isc_task_t *task); \ + void isc__nm_put_netievent_##type(isc_nm_t *nm, \ + isc__netievent_##type##_t *ievent); + +#define NETIEVENT_TASK_DEF(type) \ + isc__netievent_##type##_t *isc__nm_get_netievent_##type( \ + isc_nm_t *nm, isc_task_t *task) { \ + isc__netievent_##type##_t *ievent = \ + isc__nm_get_netievent(nm, netievent_##type); \ + ievent->task = task; \ + \ + return (ievent); \ + } \ + \ + void isc__nm_put_netievent_##type(isc_nm_t *nm, \ + isc__netievent_##type##_t *ievent) { \ + ievent->task = NULL; \ + isc__nm_put_netievent(nm, ievent); \ + } + +typedef struct isc__netievent_udpsend { + NETIEVENT__SOCKET; + isc_sockaddr_t peer; + isc__nm_uvreq_t *req; +} isc__netievent_udpsend_t; + +typedef struct isc__netievent { + isc__netievent_type type; + ISC_LINK(isc__netievent_t) link; +} isc__netievent_t; + +#define NETIEVENT_TYPE(type) typedef isc__netievent_t isc__netievent_##type##_t; + +#define NETIEVENT_DECL(type) \ + isc__netievent_##type##_t *isc__nm_get_netievent_##type(isc_nm_t *nm); \ + void isc__nm_put_netievent_##type(isc_nm_t *nm, \ + isc__netievent_##type##_t *ievent); + +#define NETIEVENT_DEF(type) \ + isc__netievent_##type##_t *isc__nm_get_netievent_##type( \ + isc_nm_t *nm) { \ + isc__netievent_##type##_t *ievent = \ + isc__nm_get_netievent(nm, netievent_##type); \ + \ + return (ievent); \ + } \ + \ + void isc__nm_put_netievent_##type(isc_nm_t *nm, \ + isc__netievent_##type##_t *ievent) { \ + isc__nm_put_netievent(nm, ievent); \ + } + +typedef union { + isc__netievent_t ni; + isc__netievent__socket_t nis; + isc__netievent__socket_req_t nisr; + isc__netievent_udpsend_t nius; + isc__netievent__socket_quota_t nisq; +} isc__netievent_storage_t; + +/* + * Work item for a uv_work threadpool. + */ +typedef struct isc__nm_work { + isc_nm_t *netmgr; + uv_work_t req; + isc_nm_workcb_t cb; + isc_nm_after_workcb_t after_cb; + void *data; +} isc__nm_work_t; + +/* + * Network manager + */ +#define NM_MAGIC ISC_MAGIC('N', 'E', 'T', 'M') +#define VALID_NM(t) ISC_MAGIC_VALID(t, NM_MAGIC) + +struct isc_nm { + int magic; + isc_refcount_t references; + isc_mem_t *mctx; + int nworkers; + isc_mutex_t lock; + isc_condition_t wkstatecond; + isc_condition_t wkpausecond; + isc__networker_t *workers; + + isc_stats_t *stats; + + uint_fast32_t workers_running; + atomic_uint_fast32_t workers_paused; + atomic_uint_fast32_t maxudp; + + bool load_balance_sockets; + + atomic_bool paused; + + /* + * Active connections are being closed and new connections are + * no longer allowed. + */ + atomic_bool closing; + + /* + * A worker is actively waiting for other workers, for example to + * stop listening; that means no other thread can do the same thing + * or pause, or we'll deadlock. We have to either re-enqueue our + * event or wait for the other one to finish if we want to pause. + */ + atomic_int interlocked; + + /* + * Timeout values for TCP connections, corresponding to + * tcp-intiial-timeout, tcp-idle-timeout, tcp-keepalive-timeout, + * and tcp-advertised-timeout. Note that these are stored in + * milliseconds so they can be used directly with the libuv timer, + * but they are configured in tenths of seconds. + */ + atomic_uint_fast32_t init; + atomic_uint_fast32_t idle; + atomic_uint_fast32_t keepalive; + atomic_uint_fast32_t advertised; + + isc_barrier_t pausing; + isc_barrier_t resuming; + +#ifdef NETMGR_TRACE + ISC_LIST(isc_nmsocket_t) active_sockets; +#endif +}; + +typedef enum isc_nmsocket_type { + isc_nm_udpsocket, + isc_nm_udplistener, /* Aggregate of nm_udpsocks */ + isc_nm_tcpsocket, + isc_nm_tcplistener, + isc_nm_tcpdnslistener, + isc_nm_tcpdnssocket, +} isc_nmsocket_type; + +/*% + * A universal structure for either a single socket or a group of + * dup'd/SO_REUSE_PORT-using sockets listening on the same interface. + */ +#define NMSOCK_MAGIC ISC_MAGIC('N', 'M', 'S', 'K') +#define VALID_NMSOCK(t) ISC_MAGIC_VALID(t, NMSOCK_MAGIC) + +/*% + * Index into socket stat counter arrays. + */ +enum { + STATID_OPEN = 0, + STATID_OPENFAIL = 1, + STATID_CLOSE = 2, + STATID_BINDFAIL = 3, + STATID_CONNECTFAIL = 4, + STATID_CONNECT = 5, + STATID_ACCEPTFAIL = 6, + STATID_ACCEPT = 7, + STATID_SENDFAIL = 8, + STATID_RECVFAIL = 9, + STATID_ACTIVE = 10 +}; + +typedef void (*isc_nm_closehandlecb_t)(void *arg); +/*%< + * Opaque callback function, used for isc_nmhandle 'reset' and 'free' + * callbacks. + */ + +struct isc_nmsocket { + /*% Unlocked, RO */ + int magic; + int tid; + isc_nmsocket_type type; + isc_nm_t *mgr; + + /*% Parent socket for multithreaded listeners */ + isc_nmsocket_t *parent; + /*% Listener socket this connection was accepted on */ + isc_nmsocket_t *listener; + /*% Self socket */ + isc_nmsocket_t *self; + + isc_barrier_t startlistening; + isc_barrier_t stoplistening; + + /*% + * quota is the TCP client, attached when a TCP connection + * is established. pquota is a non-attached pointer to the + * TCP client quota, stored in listening sockets but only + * attached in connected sockets. + */ + isc_quota_t *quota; + isc_quota_t *pquota; + isc_quota_cb_t quotacb; + + /*% + * Socket statistics + */ + const isc_statscounter_t *statsindex; + + /*% + * TCP read/connect timeout timers. + */ + uv_timer_t read_timer; + uint64_t read_timeout; + uint64_t connect_timeout; + + /*% + * TCP write timeout timer. + */ + uint64_t write_timeout; + + /*% outer socket is for 'wrapped' sockets - e.g. tcpdns in tcp */ + isc_nmsocket_t *outer; + + /*% server socket for connections */ + isc_nmsocket_t *server; + + /*% Child sockets for multi-socket setups */ + isc_nmsocket_t *children; + uint_fast32_t nchildren; + isc_sockaddr_t iface; + isc_nmhandle_t *statichandle; + isc_nmhandle_t *outerhandle; + + /*% Extra data allocated at the end of each isc_nmhandle_t */ + size_t extrahandlesize; + + /*% TCP backlog */ + int backlog; + + /*% libuv data */ + uv_os_sock_t fd; + union uv_any_handle uv_handle; + + /*% Peer address */ + isc_sockaddr_t peer; + + /* Atomic */ + /*% Number of running (e.g. listening) child sockets */ + atomic_uint_fast32_t rchildren; + + /*% + * Socket is active if it's listening, working, etc. If it's + * closing, then it doesn't make a sense, for example, to + * push handles or reqs for reuse. + */ + atomic_bool active; + atomic_bool destroying; + + /*% + * Socket is closed if it's not active and all the possible + * callbacks were fired, there are no active handles, etc. + * If active==false but closed==false, that means the socket + * is closing. + */ + atomic_bool closing; + atomic_bool closed; + atomic_bool listening; + atomic_bool connecting; + atomic_bool connected; + bool accepting; + bool reading; + atomic_bool timedout; + isc_refcount_t references; + + /*% + * Established an outgoing connection, as client not server. + */ + atomic_bool client; + + /*% + * TCPDNS socket has been set not to pipeline. + */ + atomic_bool sequential; + + /*% + * The socket is processing read callback, this is guard to not read + * data before the readcb is back. + */ + bool processing; + + /*% + * A TCP socket has had isc_nm_pauseread() called. + */ + atomic_bool readpaused; + + /*% + * A TCP or TCPDNS socket has been set to use the keepalive + * timeout instead of the default idle timeout. + */ + atomic_bool keepalive; + + /*% + * 'spare' handles for that can be reused to avoid allocations, + * for UDP. + */ + isc_astack_t *inactivehandles; + isc_astack_t *inactivereqs; + + /*% + * Used to wait for TCP listening events to complete, and + * for the number of running children to reach zero during + * shutdown. + * + * We use two condition variables to prevent the race where the netmgr + * threads would be able to finish and destroy the socket before it's + * unlocked by the isc_nm_listen<proto>() function. So, the flow is as + * follows: + * + * 1. parent thread creates all children sockets and passes then to + * netthreads, looks at the signaling variable and WAIT(cond) until + * the childrens are done initializing + * + * 2. the events get picked by netthreads, calls the libuv API (and + * either succeeds or fails) and WAIT(scond) until all other + * children sockets in netthreads are initialized and the listening + * socket lock is unlocked + * + * 3. the control is given back to the parent thread which now either + * returns success or shutdowns the listener if an error has + * occured in the children netthread + * + * NOTE: The other approach would be doing an extra attach to the parent + * listening socket, and then detach it in the parent thread, but that + * breaks the promise that once the libuv socket is initialized on the + * nmsocket, the nmsocket needs to be handled only by matching + * netthread, so in fact that would add a complexity in a way that + * isc__nmsocket_detach would have to be converted to use an + * asynchrounous netievent. + */ + isc_mutex_t lock; + isc_condition_t cond; + isc_condition_t scond; + + /*% + * Used to pass a result back from listen or connect events. + */ + isc_result_t result; + + /*% + * Current number of active handles. + */ + atomic_int_fast32_t ah; + + /*% Buffer for TCPDNS processing */ + size_t buf_size; + size_t buf_len; + unsigned char *buf; + + /*% + * This function will be called with handle->sock + * as the argument whenever a handle's references drop + * to zero, after its reset callback has been called. + */ + isc_nm_closehandlecb_t closehandle_cb; + + isc_nmhandle_t *recv_handle; + isc_nm_recv_cb_t recv_cb; + void *recv_cbarg; + bool recv_read; + + isc_nm_cb_t connect_cb; + void *connect_cbarg; + + isc_nm_accept_cb_t accept_cb; + void *accept_cbarg; + + atomic_int_fast32_t active_child_connections; + +#ifdef NETMGR_TRACE + void *backtrace[TRACE_SIZE]; + int backtrace_size; + LINK(isc_nmsocket_t) active_link; + ISC_LIST(isc_nmhandle_t) active_handles; +#endif +}; + +bool +isc__nm_in_netthread(void); +/*% + * Returns 'true' if we're in the network thread. + */ + +void +isc__nm_maybe_enqueue_ievent(isc__networker_t *worker, isc__netievent_t *event); +/*%< + * If the caller is already in the matching nmthread, process the netievent + * directly, if not enqueue using isc__nm_enqueue_ievent(). + */ + +void +isc__nm_enqueue_ievent(isc__networker_t *worker, isc__netievent_t *event); +/*%< + * Enqueue an ievent onto a specific worker queue. (This the only safe + * way to use an isc__networker_t from another thread.) + */ + +void +isc__nm_free_uvbuf(isc_nmsocket_t *sock, const uv_buf_t *buf); +/*%< + * Free a buffer allocated for a receive operation. + * + * Note that as currently implemented, this doesn't actually + * free anything, marks the isc__networker's UDP receive buffer + * as "not in use". + */ + +isc_nmhandle_t * +isc___nmhandle_get(isc_nmsocket_t *sock, isc_sockaddr_t *peer, + isc_sockaddr_t *local FLARG); +/*%< + * Get a handle for the socket 'sock', allocating a new one + * if there isn't one available in 'sock->inactivehandles'. + * + * If 'peer' is not NULL, set the handle's peer address to 'peer', + * otherwise set it to 'sock->peer'. + * + * If 'local' is not NULL, set the handle's local address to 'local', + * otherwise set it to 'sock->iface->addr'. + * + * 'sock' will be attached to 'handle->sock'. The caller may need + * to detach the socket afterward. + */ + +isc__nm_uvreq_t * +isc___nm_uvreq_get(isc_nm_t *mgr, isc_nmsocket_t *sock FLARG); +/*%< + * Get a UV request structure for the socket 'sock', allocating a + * new one if there isn't one available in 'sock->inactivereqs'. + */ + +void +isc___nm_uvreq_put(isc__nm_uvreq_t **req, isc_nmsocket_t *sock FLARG); +/*%< + * Completes the use of a UV request structure, setting '*req' to NULL. + * + * The UV request is pushed onto the 'sock->inactivereqs' stack or, + * if that doesn't work, freed. + */ + +void +isc___nmsocket_init(isc_nmsocket_t *sock, isc_nm_t *mgr, isc_nmsocket_type type, + isc_sockaddr_t *iface FLARG); +/*%< + * Initialize socket 'sock', attach it to 'mgr', and set it to type 'type' + * and its interface to 'iface'. + */ + +void +isc___nmsocket_attach(isc_nmsocket_t *sock, isc_nmsocket_t **target FLARG); +/*%< + * Attach to a socket, increasing refcount + */ + +void +isc___nmsocket_detach(isc_nmsocket_t **socketp FLARG); +/*%< + * Detach from socket, decreasing refcount and possibly destroying the + * socket if it's no longer referenced. + */ + +void +isc___nmsocket_prep_destroy(isc_nmsocket_t *sock FLARG); +/*%< + * Market 'sock' as inactive, close it if necessary, and destroy it + * if there are no remaining references or active handles. + */ + +void +isc__nmsocket_shutdown(isc_nmsocket_t *sock); +/*%< + * Initiate the socket shutdown which actively calls the active + * callbacks. + */ + +bool +isc__nmsocket_active(isc_nmsocket_t *sock); +/*%< + * Determine whether 'sock' is active by checking 'sock->active' + * or, for child sockets, 'sock->parent->active'. + */ + +bool +isc__nmsocket_deactivate(isc_nmsocket_t *sock); +/*%< + * @brief Deactivate active socket + * + * Atomically deactive the socket by setting @p sock->active or, for child + * sockets, @p sock->parent->active to @c false + * + * @param[in] sock - valid nmsocket + * @return @c false if the socket was already inactive, @c true otherwise + */ + +void +isc__nmsocket_clearcb(isc_nmsocket_t *sock); +/*%< + * Clear the recv and accept callbacks in 'sock'. + */ + +void +isc__nmsocket_timer_stop(isc_nmsocket_t *sock); +void +isc__nmsocket_timer_start(isc_nmsocket_t *sock); +void +isc__nmsocket_timer_restart(isc_nmsocket_t *sock); +bool +isc__nmsocket_timer_running(isc_nmsocket_t *sock); +/*%< + * Start/stop/restart/check the timeout on the socket + */ + +void +isc__nm_connectcb(isc_nmsocket_t *sock, isc__nm_uvreq_t *uvreq, + isc_result_t eresult, bool async); + +void +isc__nm_async_connectcb(isc__networker_t *worker, isc__netievent_t *ev0); +/*%< + * Issue a connect callback on the socket, used to call the callback + */ + +void +isc__nm_readcb(isc_nmsocket_t *sock, isc__nm_uvreq_t *uvreq, + isc_result_t eresult); +void +isc__nm_async_readcb(isc__networker_t *worker, isc__netievent_t *ev0); + +/*%< + * Issue a read callback on the socket, used to call the callback + * on failed conditions when the event can't be scheduled on the uv loop. + * + */ + +void +isc__nm_sendcb(isc_nmsocket_t *sock, isc__nm_uvreq_t *uvreq, + isc_result_t eresult, bool async); +void +isc__nm_async_sendcb(isc__networker_t *worker, isc__netievent_t *ev0); +/*%< + * Issue a write callback on the socket, used to call the callback + * on failed conditions when the event can't be scheduled on the uv loop. + */ + +void +isc__nm_async_shutdown(isc__networker_t *worker, isc__netievent_t *ev0); +/*%< + * Walk through all uv handles, get the underlying sockets and issue + * close on them. + */ + +void +isc__nm_udp_send(isc_nmhandle_t *handle, const isc_region_t *region, + isc_nm_cb_t cb, void *cbarg); +/*%< + * Back-end implementation of isc_nm_send() for UDP handles. + */ + +void +isc__nm_udp_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg); +/* + * Back-end implementation of isc_nm_read() for UDP handles. + */ + +void +isc__nm_udp_close(isc_nmsocket_t *sock); +/*%< + * Close a UDP socket. + */ + +void +isc__nm_udp_cancelread(isc_nmhandle_t *handle); +/*%< + * Stop reading on a connected UDP handle. + */ + +void +isc__nm_udp_shutdown(isc_nmsocket_t *sock); +/*%< + * Called during the shutdown process to close and clean up connected + * sockets. + */ + +void +isc__nm_udp_stoplistening(isc_nmsocket_t *sock); +/*%< + * Stop listening on 'sock'. + */ + +void +isc__nm_udp_settimeout(isc_nmhandle_t *handle, uint32_t timeout); +/*%< + * Set or clear the recv timeout for the UDP socket associated with 'handle'. + */ + +void +isc__nm_async_udplisten(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_udpconnect(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_udpstop(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_udpsend(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_udpread(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_udpcancel(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_udpclose(isc__networker_t *worker, isc__netievent_t *ev0); +/*%< + * Callback handlers for asynchronous UDP events (listen, stoplisten, send). + */ + +void +isc__nm_tcp_send(isc_nmhandle_t *handle, const isc_region_t *region, + isc_nm_cb_t cb, void *cbarg); +/*%< + * Back-end implementation of isc_nm_send() for TCP handles. + */ + +void +isc__nm_tcp_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg); +/* + * Back-end implementation of isc_nm_read() for TCP handles. + */ + +void +isc__nm_tcp_close(isc_nmsocket_t *sock); +/*%< + * Close a TCP socket. + */ +void +isc__nm_tcp_pauseread(isc_nmhandle_t *handle); +/*%< + * Pause reading on this handle, while still remembering the callback. + */ + +void +isc__nm_tcp_resumeread(isc_nmhandle_t *handle); +/*%< + * Resume reading from socket. + * + */ + +void +isc__nm_tcp_shutdown(isc_nmsocket_t *sock); +/*%< + * Called during the shutdown process to close and clean up connected + * sockets. + */ + +void +isc__nm_tcp_cancelread(isc_nmhandle_t *handle); +/*%< + * Stop reading on a connected TCP handle. + */ + +void +isc__nm_tcp_stoplistening(isc_nmsocket_t *sock); +/*%< + * Stop listening on 'sock'. + */ + +int_fast32_t +isc__nm_tcp_listener_nactive(isc_nmsocket_t *sock); +/*%< + * Returns the number of active connections for the TCP listener socket. + */ + +void +isc__nm_tcp_settimeout(isc_nmhandle_t *handle, uint32_t timeout); +/*%< + * Set the read timeout for the TCP socket associated with 'handle'. + */ + +void +isc__nm_async_tcpconnect(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_tcplisten(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_tcpaccept(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_tcpstop(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_tcpsend(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_startread(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_pauseread(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_tcpstartread(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_tcppauseread(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_tcpcancel(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_tcpclose(isc__networker_t *worker, isc__netievent_t *ev0); +/*%< + * Callback handlers for asynchronous TCP events (connect, listen, + * stoplisten, send, read, pause, close). + */ + +void +isc__nm_async_tcpdnsaccept(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_tcpdnsconnect(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_tcpdnslisten(isc__networker_t *worker, isc__netievent_t *ev0); + +void +isc__nm_tcpdns_send(isc_nmhandle_t *handle, isc_region_t *region, + isc_nm_cb_t cb, void *cbarg); +/*%< + * Back-end implementation of isc_nm_send() for TCPDNS handles. + */ + +void +isc__nm_tcpdns_shutdown(isc_nmsocket_t *sock); + +void +isc__nm_tcpdns_close(isc_nmsocket_t *sock); +/*%< + * Close a TCPDNS socket. + */ + +void +isc__nm_tcpdns_stoplistening(isc_nmsocket_t *sock); +/*%< + * Stop listening on 'sock'. + */ + +void +isc__nm_tcpdns_settimeout(isc_nmhandle_t *handle, uint32_t timeout); +/*%< + * Set the read timeout and reset the timer for the TCPDNS socket + * associated with 'handle', and the TCP socket it wraps around. + */ + +void +isc__nm_async_tcpdnsaccept(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_tcpdnsconnect(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_tcpdnslisten(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_tcpdnscancel(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_tcpdnsclose(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_tcpdnssend(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_tcpdnsstop(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_tcpdnsread(isc__networker_t *worker, isc__netievent_t *ev0); +/*%< + * Callback handlers for asynchronous TCPDNS events. + */ + +void +isc__nm_tcpdns_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg); +/* + * Back-end implementation of isc_nm_read() for TCPDNS handles. + */ + +void +isc__nm_tcpdns_cancelread(isc_nmhandle_t *handle); +/*%< + * Stop reading on a connected TCPDNS handle. + */ + +#define isc__nm_uverr2result(x) \ + isc___nm_uverr2result(x, true, __FILE__, __LINE__, __func__) +isc_result_t +isc___nm_uverr2result(int uverr, bool dolog, const char *file, + unsigned int line, const char *func); +/*%< + * Convert a libuv error value into an isc_result_t. The + * list of supported error values is not complete; new users + * of this function should add any expected errors that are + * not already there. + */ + +bool +isc__nm_acquire_interlocked(isc_nm_t *mgr); +/*%< + * Try to acquire interlocked state; return true if successful. + */ + +void +isc__nm_drop_interlocked(isc_nm_t *mgr); +/*%< + * Drop interlocked state; signal waiters. + */ + +void +isc__nm_acquire_interlocked_force(isc_nm_t *mgr); +/*%< + * Actively wait for interlocked state. + */ + +void +isc__nm_incstats(isc_nm_t *mgr, isc_statscounter_t counterid); +/*%< + * Increment socket-related statistics counters. + */ + +void +isc__nm_decstats(isc_nm_t *mgr, isc_statscounter_t counterid); +/*%< + * Decrement socket-related statistics counters. + */ + +isc_result_t +isc__nm_socket(int domain, int type, int protocol, uv_os_sock_t *sockp); +/*%< + * Platform independent socket() version + */ + +void +isc__nm_closesocket(uv_os_sock_t sock); +/*%< + * Platform independent closesocket() version + */ + +isc_result_t +isc__nm_socket_freebind(uv_os_sock_t fd, sa_family_t sa_family); +/*%< + * Set the IP_FREEBIND (or equivalent) socket option on the uv_handle + */ + +isc_result_t +isc__nm_socket_reuse(uv_os_sock_t fd); +/*%< + * Set the SO_REUSEADDR or SO_REUSEPORT (or equivalent) socket option on the fd + */ + +isc_result_t +isc__nm_socket_reuse_lb(uv_os_sock_t fd); +/*%< + * Set the SO_REUSEPORT_LB (or equivalent) socket option on the fd + */ + +isc_result_t +isc__nm_socket_incoming_cpu(uv_os_sock_t fd); +/*%< + * Set the SO_INCOMING_CPU socket option on the fd if available + */ + +isc_result_t +isc__nm_socket_disable_pmtud(uv_os_sock_t fd, sa_family_t sa_family); +/*%< + * Disable the Path MTU Discovery, either by disabling IP(V6)_DONTFRAG socket + * option, or setting the IP(V6)_MTU_DISCOVER socket option to IP_PMTUDISC_OMIT + */ + +isc_result_t +isc__nm_socket_connectiontimeout(uv_os_sock_t fd, int timeout_ms); +/*%< + * Set the connection timeout in milliseconds, on non-Linux platforms, + * the minimum value must be at least 1000 (1 second). + */ + +isc_result_t +isc__nm_socket_tcp_nodelay(uv_os_sock_t fd); +/*%< + * Disables Nagle's algorithm on a TCP socket (sets TCP_NODELAY). + */ + +/* + * typedef all the netievent types + */ + +NETIEVENT_SOCKET_TYPE(close); +NETIEVENT_SOCKET_TYPE(tcpclose); +NETIEVENT_SOCKET_TYPE(tcplisten); +NETIEVENT_SOCKET_TYPE(tcppauseread); +NETIEVENT_SOCKET_TYPE(tcpstop); +NETIEVENT_SOCKET_TYPE(udpclose); +NETIEVENT_SOCKET_TYPE(udplisten); +NETIEVENT_SOCKET_TYPE(udpread); +/* NETIEVENT_SOCKET_TYPE(udpsend); */ /* unique type, defined independently */ +NETIEVENT_SOCKET_TYPE(udpstop); + +NETIEVENT_SOCKET_TYPE(tcpdnsclose); +NETIEVENT_SOCKET_TYPE(tcpdnsread); +NETIEVENT_SOCKET_TYPE(tcpdnsstop); +NETIEVENT_SOCKET_TYPE(tcpdnslisten); +NETIEVENT_SOCKET_REQ_TYPE(tcpdnsconnect); +NETIEVENT_SOCKET_REQ_TYPE(tcpdnssend); +NETIEVENT_SOCKET_HANDLE_TYPE(tcpdnscancel); +NETIEVENT_SOCKET_QUOTA_TYPE(tcpdnsaccept); + +NETIEVENT_SOCKET_REQ_TYPE(tcpconnect); +NETIEVENT_SOCKET_REQ_TYPE(tcpsend); +NETIEVENT_SOCKET_TYPE(tcpstartread); +NETIEVENT_SOCKET_REQ_TYPE(udpconnect); + +NETIEVENT_SOCKET_REQ_RESULT_TYPE(connectcb); +NETIEVENT_SOCKET_REQ_RESULT_TYPE(readcb); +NETIEVENT_SOCKET_REQ_RESULT_TYPE(sendcb); + +NETIEVENT_SOCKET_HANDLE_TYPE(detach); +NETIEVENT_SOCKET_HANDLE_TYPE(tcpcancel); +NETIEVENT_SOCKET_HANDLE_TYPE(udpcancel); + +NETIEVENT_SOCKET_QUOTA_TYPE(tcpaccept); + +NETIEVENT_TYPE(pause); +NETIEVENT_TYPE(resume); +NETIEVENT_TYPE(shutdown); +NETIEVENT_TYPE(stop); + +NETIEVENT_TASK_TYPE(task); +NETIEVENT_TASK_TYPE(privilegedtask); + +/* Now declared the helper functions */ + +NETIEVENT_SOCKET_DECL(close); +NETIEVENT_SOCKET_DECL(tcpclose); +NETIEVENT_SOCKET_DECL(tcplisten); +NETIEVENT_SOCKET_DECL(tcppauseread); +NETIEVENT_SOCKET_DECL(tcpstartread); +NETIEVENT_SOCKET_DECL(tcpstop); +NETIEVENT_SOCKET_DECL(udpclose); +NETIEVENT_SOCKET_DECL(udplisten); +NETIEVENT_SOCKET_DECL(udpread); +NETIEVENT_SOCKET_DECL(udpsend); +NETIEVENT_SOCKET_DECL(udpstop); + +NETIEVENT_SOCKET_DECL(tcpdnsclose); +NETIEVENT_SOCKET_DECL(tcpdnsread); +NETIEVENT_SOCKET_DECL(tcpdnsstop); +NETIEVENT_SOCKET_DECL(tcpdnslisten); +NETIEVENT_SOCKET_REQ_DECL(tcpdnsconnect); +NETIEVENT_SOCKET_REQ_DECL(tcpdnssend); +NETIEVENT_SOCKET_HANDLE_DECL(tcpdnscancel); +NETIEVENT_SOCKET_QUOTA_DECL(tcpdnsaccept); + +NETIEVENT_SOCKET_REQ_DECL(tcpconnect); +NETIEVENT_SOCKET_REQ_DECL(tcpsend); +NETIEVENT_SOCKET_REQ_DECL(udpconnect); + +NETIEVENT_SOCKET_REQ_RESULT_DECL(connectcb); +NETIEVENT_SOCKET_REQ_RESULT_DECL(readcb); +NETIEVENT_SOCKET_REQ_RESULT_DECL(sendcb); + +NETIEVENT_SOCKET_HANDLE_DECL(udpcancel); +NETIEVENT_SOCKET_HANDLE_DECL(tcpcancel); +NETIEVENT_SOCKET_DECL(detach); + +NETIEVENT_SOCKET_QUOTA_DECL(tcpaccept); + +NETIEVENT_DECL(pause); +NETIEVENT_DECL(resume); +NETIEVENT_DECL(shutdown); +NETIEVENT_DECL(stop); + +NETIEVENT_TASK_DECL(task); +NETIEVENT_TASK_DECL(privilegedtask); + +void +isc__nm_udp_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result); +void +isc__nm_tcp_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result); +void +isc__nm_tcpdns_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result); + +isc_result_t +isc__nm_tcpdns_processbuffer(isc_nmsocket_t *sock); + +isc__nm_uvreq_t * +isc__nm_get_read_req(isc_nmsocket_t *sock, isc_sockaddr_t *sockaddr); + +void +isc__nm_alloc_cb(uv_handle_t *handle, size_t size, uv_buf_t *buf); + +void +isc__nm_udp_read_cb(uv_udp_t *handle, ssize_t nrecv, const uv_buf_t *buf, + const struct sockaddr *addr, unsigned flags); +void +isc__nm_tcp_read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf); +void +isc__nm_tcpdns_read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf); + +isc_result_t +isc__nm_start_reading(isc_nmsocket_t *sock); +void +isc__nm_stop_reading(isc_nmsocket_t *sock); +isc_result_t +isc__nm_process_sock_buffer(isc_nmsocket_t *sock); +void +isc__nm_resume_processing(void *arg); +bool +isc__nmsocket_closing(isc_nmsocket_t *sock); +bool +isc__nm_closing(isc_nmsocket_t *sock); + +void +isc__nm_alloc_dnsbuf(isc_nmsocket_t *sock, size_t len); + +void +isc__nm_failed_send_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req, + isc_result_t eresult); +void +isc__nm_failed_accept_cb(isc_nmsocket_t *sock, isc_result_t eresult); +void +isc__nm_failed_connect_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req, + isc_result_t eresult, bool async); +void +isc__nm_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result, bool async); + +void +isc__nm_accept_connection_log(isc_result_t result, bool can_log_quota); + +/* + * Timeout callbacks + */ +void +isc__nmsocket_connecttimeout_cb(uv_timer_t *timer); +void +isc__nmsocket_readtimeout_cb(uv_timer_t *timer); +void +isc__nmsocket_writetimeout_cb(void *data, isc_result_t eresult); + +/*%< + * + * Maximum number of simultaneous handles in flight supported for a single + * connected TCPDNS socket. This value was chosen arbitrarily, and may be + * changed in the future. + */ +#define STREAM_CLIENTS_PER_CONN 23 + +#define UV_RUNTIME_CHECK(func, ret) \ + if (ret != 0) { \ + isc_error_fatal(__FILE__, __LINE__, "%s failed: %s\n", #func, \ + uv_strerror(ret)); \ + } diff --git a/lib/isc/netmgr/netmgr.c b/lib/isc/netmgr/netmgr.c new file mode 100644 index 0000000..6f42ec9 --- /dev/null +++ b/lib/isc/netmgr/netmgr.c @@ -0,0 +1,3396 @@ +/* + * 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. + */ + +#include <inttypes.h> +#include <unistd.h> +#include <uv.h> +#ifdef HAVE_LIBCTRACE +#include <execinfo.h> +#endif /* ifdef HAVE_LIBCTRACE */ + +#include <isc/atomic.h> +#include <isc/barrier.h> +#include <isc/buffer.h> +#include <isc/condition.h> +#include <isc/errno.h> +#include <isc/list.h> +#include <isc/log.h> +#include <isc/magic.h> +#include <isc/mem.h> +#include <isc/netmgr.h> +#include <isc/print.h> +#include <isc/quota.h> +#include <isc/random.h> +#include <isc/refcount.h> +#include <isc/region.h> +#include <isc/result.h> +#include <isc/sockaddr.h> +#include <isc/stats.h> +#include <isc/strerr.h> +#include <isc/task.h> +#include <isc/thread.h> +#include <isc/util.h> + +#include "netmgr-int.h" +#include "netmgr_p.h" +#include "openssl_shim.h" +#include "trampoline_p.h" +#include "uv-compat.h" + +/*% + * How many isc_nmhandles and isc_nm_uvreqs will we be + * caching for reuse in a socket. + */ +#define ISC_NM_HANDLES_STACK_SIZE 600 +#define ISC_NM_REQS_STACK_SIZE 600 + +/*% + * Shortcut index arrays to get access to statistics counters. + */ + +static const isc_statscounter_t udp4statsindex[] = { + isc_sockstatscounter_udp4open, + isc_sockstatscounter_udp4openfail, + isc_sockstatscounter_udp4close, + isc_sockstatscounter_udp4bindfail, + isc_sockstatscounter_udp4connectfail, + isc_sockstatscounter_udp4connect, + -1, + -1, + isc_sockstatscounter_udp4sendfail, + isc_sockstatscounter_udp4recvfail, + isc_sockstatscounter_udp4active +}; + +static const isc_statscounter_t udp6statsindex[] = { + isc_sockstatscounter_udp6open, + isc_sockstatscounter_udp6openfail, + isc_sockstatscounter_udp6close, + isc_sockstatscounter_udp6bindfail, + isc_sockstatscounter_udp6connectfail, + isc_sockstatscounter_udp6connect, + -1, + -1, + isc_sockstatscounter_udp6sendfail, + isc_sockstatscounter_udp6recvfail, + isc_sockstatscounter_udp6active +}; + +static const isc_statscounter_t tcp4statsindex[] = { + isc_sockstatscounter_tcp4open, isc_sockstatscounter_tcp4openfail, + isc_sockstatscounter_tcp4close, isc_sockstatscounter_tcp4bindfail, + isc_sockstatscounter_tcp4connectfail, isc_sockstatscounter_tcp4connect, + isc_sockstatscounter_tcp4acceptfail, isc_sockstatscounter_tcp4accept, + isc_sockstatscounter_tcp4sendfail, isc_sockstatscounter_tcp4recvfail, + isc_sockstatscounter_tcp4active +}; + +static const isc_statscounter_t tcp6statsindex[] = { + isc_sockstatscounter_tcp6open, isc_sockstatscounter_tcp6openfail, + isc_sockstatscounter_tcp6close, isc_sockstatscounter_tcp6bindfail, + isc_sockstatscounter_tcp6connectfail, isc_sockstatscounter_tcp6connect, + isc_sockstatscounter_tcp6acceptfail, isc_sockstatscounter_tcp6accept, + isc_sockstatscounter_tcp6sendfail, isc_sockstatscounter_tcp6recvfail, + isc_sockstatscounter_tcp6active +}; + +#if 0 +/* XXX: not currently used */ +static const isc_statscounter_t unixstatsindex[] = { + isc_sockstatscounter_unixopen, + isc_sockstatscounter_unixopenfail, + isc_sockstatscounter_unixclose, + isc_sockstatscounter_unixbindfail, + isc_sockstatscounter_unixconnectfail, + isc_sockstatscounter_unixconnect, + isc_sockstatscounter_unixacceptfail, + isc_sockstatscounter_unixaccept, + isc_sockstatscounter_unixsendfail, + isc_sockstatscounter_unixrecvfail, + isc_sockstatscounter_unixactive +}; +#endif /* if 0 */ + +/* + * libuv is not thread safe, but has mechanisms to pass messages + * between threads. Each socket is owned by a thread. For UDP + * sockets we have a set of sockets for each interface and we can + * choose a sibling and send the message directly. For TCP, or if + * we're calling from a non-networking thread, we need to pass the + * request using async_cb. + */ + +#if defined(HAVE_THREAD_LOCAL) +#include <threads.h> +static thread_local int isc__nm_tid_v = ISC_NETMGR_TID_UNKNOWN; +#elif defined(HAVE___THREAD) +static __thread int isc__nm_tid_v = ISC_NETMGR_TID_UNKNOWN; +#elif HAVE___DECLSPEC_THREAD +__declspec(thread) int isc__nm_tid_v = ISC_NETMGR_TID_UNKNOWN; +#endif /* if defined(HAVE_THREAD_LOCAL) */ + +static void +nmsocket_maybe_destroy(isc_nmsocket_t *sock FLARG); +static void +nmhandle_free(isc_nmsocket_t *sock, isc_nmhandle_t *handle); +static isc_threadresult_t +nm_thread(isc_threadarg_t worker0); +static void +async_cb(uv_async_t *handle); + +static bool +process_netievent(isc__networker_t *worker, isc__netievent_t *ievent); +static isc_result_t +process_queue(isc__networker_t *worker, netievent_type_t type); +static void +wait_for_priority_queue(isc__networker_t *worker); +static void +drain_queue(isc__networker_t *worker, netievent_type_t type); + +static void +isc__nm_async_stop(isc__networker_t *worker, isc__netievent_t *ev0); +static void +isc__nm_async_pause(isc__networker_t *worker, isc__netievent_t *ev0); +static void +isc__nm_async_resume(isc__networker_t *worker, isc__netievent_t *ev0); +static void +isc__nm_async_detach(isc__networker_t *worker, isc__netievent_t *ev0); +static void +isc__nm_async_close(isc__networker_t *worker, isc__netievent_t *ev0); + +static void +isc__nm_threadpool_initialize(uint32_t workers); +static void +isc__nm_work_cb(uv_work_t *req); +static void +isc__nm_after_work_cb(uv_work_t *req, int status); + +void +isc__nmsocket_reset(isc_nmsocket_t *sock); + +/*%< + * Issue a 'handle closed' callback on the socket. + */ + +static void +nmhandle_detach_cb(isc_nmhandle_t **handlep FLARG); + +int +isc_nm_tid(void) { + return (isc__nm_tid_v); +} + +bool +isc__nm_in_netthread(void) { + return (isc__nm_tid_v >= 0); +} + +#ifdef WIN32 +static void +isc__nm_winsock_initialize(void) { + WORD wVersionRequested = MAKEWORD(2, 2); + WSADATA wsaData; + int result; + + result = WSAStartup(wVersionRequested, &wsaData); + if (result != 0) { + char strbuf[ISC_STRERRORSIZE]; + strerror_r(result, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "WSAStartup() failed with error code %lu: %s", + result, strbuf); + } + + /* + * Confirm that the WinSock DLL supports version 2.2. Note that if the + * DLL supports versions greater than 2.2 in addition to 2.2, it will + * still return 2.2 in wVersion since that is the version we requested. + */ + if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "Unusable WinSock DLL version: %u.%u", + LOBYTE(wsaData.wVersion), + HIBYTE(wsaData.wVersion)); + } +} + +static void +isc__nm_winsock_destroy(void) { + WSACleanup(); +} +#endif /* WIN32 */ + +static void +isc__nm_threadpool_initialize(uint32_t workers) { + char buf[11]; + int r = uv_os_getenv("UV_THREADPOOL_SIZE", buf, + &(size_t){ sizeof(buf) }); + if (r == UV_ENOENT) { + snprintf(buf, sizeof(buf), "%" PRIu32, workers); + uv_os_setenv("UV_THREADPOOL_SIZE", buf); + } +} + +#if HAVE_DECL_UV_UDP_MMSG_FREE +#define MINIMAL_UV_VERSION UV_VERSION(1, 40, 0) +#elif HAVE_DECL_UV_UDP_RECVMMSG +#define MAXIMAL_UV_VERSION UV_VERSION(1, 39, 99) +#define MINIMAL_UV_VERSION UV_VERSION(1, 37, 0) +#elif _WIN32 +#define MINIMAL_UV_VERSION UV_VERSION(1, 0, 0) +#else +#define MAXIMAL_UV_VERSION UV_VERSION(1, 34, 99) +#define MINIMAL_UV_VERSION UV_VERSION(1, 0, 0) +#endif + +void +isc__netmgr_create(isc_mem_t *mctx, uint32_t workers, isc_nm_t **netmgrp) { + isc_nm_t *mgr = NULL; + char name[32]; + + REQUIRE(workers > 0); + +#ifdef MAXIMAL_UV_VERSION + if (uv_version() > MAXIMAL_UV_VERSION) { + isc_error_fatal(__FILE__, __LINE__, + "libuv version too new: running with libuv %s " + "when compiled with libuv %s will lead to " + "libuv failures", + uv_version_string(), UV_VERSION_STRING); + } +#endif /* MAXIMAL_UV_VERSION */ + + if (uv_version() < MINIMAL_UV_VERSION) { + isc_error_fatal(__FILE__, __LINE__, + "libuv version too old: running with libuv %s " + "when compiled with libuv %s will lead to " + "libuv failures", + uv_version_string(), UV_VERSION_STRING); + } + +#ifdef WIN32 + isc__nm_winsock_initialize(); +#endif /* WIN32 */ + + isc__nm_threadpool_initialize(workers); + + mgr = isc_mem_get(mctx, sizeof(*mgr)); + *mgr = (isc_nm_t){ .nworkers = workers }; + + isc_mem_attach(mctx, &mgr->mctx); + isc_mutex_init(&mgr->lock); + isc_condition_init(&mgr->wkstatecond); + isc_condition_init(&mgr->wkpausecond); + isc_refcount_init(&mgr->references, 1); + atomic_init(&mgr->maxudp, 0); + atomic_init(&mgr->interlocked, ISC_NETMGR_NON_INTERLOCKED); + atomic_init(&mgr->workers_paused, 0); + atomic_init(&mgr->paused, false); + atomic_init(&mgr->closing, false); +#if HAVE_SO_REUSEPORT_LB + mgr->load_balance_sockets = true; +#else + mgr->load_balance_sockets = false; +#endif + +#ifdef NETMGR_TRACE + ISC_LIST_INIT(mgr->active_sockets); +#endif + + /* + * Default TCP timeout values. + * May be updated by isc_nm_tcptimeouts(). + */ + atomic_init(&mgr->init, 30000); + atomic_init(&mgr->idle, 30000); + atomic_init(&mgr->keepalive, 30000); + atomic_init(&mgr->advertised, 30000); + + isc_barrier_init(&mgr->pausing, workers); + isc_barrier_init(&mgr->resuming, workers); + + mgr->workers = isc_mem_get(mctx, workers * sizeof(isc__networker_t)); + for (size_t i = 0; i < workers; i++) { + isc__networker_t *worker = &mgr->workers[i]; + int r; + + *worker = (isc__networker_t){ + .mgr = mgr, + .id = i, + }; + + r = uv_loop_init(&worker->loop); + UV_RUNTIME_CHECK(uv_loop_init, r); + + worker->loop.data = &mgr->workers[i]; + + r = uv_async_init(&worker->loop, &worker->async, async_cb); + UV_RUNTIME_CHECK(uv_async_init, r); + + for (size_t type = 0; type < NETIEVENT_MAX; type++) { + isc_mutex_init(&worker->ievents[type].lock); + isc_condition_init(&worker->ievents[type].cond); + ISC_LIST_INIT(worker->ievents[type].list); + } + + worker->recvbuf = isc_mem_get(mctx, ISC_NETMGR_RECVBUF_SIZE); + worker->sendbuf = isc_mem_get(mctx, ISC_NETMGR_SENDBUF_SIZE); + + /* + * We need to do this here and not in nm_thread to avoid a + * race - we could exit isc_nm_start, launch nm_destroy, + * and nm_thread would still not be up. + */ + mgr->workers_running++; + isc_thread_create(nm_thread, &mgr->workers[i], &worker->thread); + + snprintf(name, sizeof(name), "isc-net-%04zu", i); + isc_thread_setname(worker->thread, name); + } + + mgr->magic = NM_MAGIC; + *netmgrp = mgr; +} + +/* + * Free the resources of the network manager. + */ +static void +nm_destroy(isc_nm_t **mgr0) { + REQUIRE(VALID_NM(*mgr0)); + REQUIRE(!isc__nm_in_netthread()); + + isc_nm_t *mgr = *mgr0; + *mgr0 = NULL; + + isc_refcount_destroy(&mgr->references); + + mgr->magic = 0; + + for (int i = 0; i < mgr->nworkers; i++) { + isc__networker_t *worker = &mgr->workers[i]; + isc__netievent_t *event = isc__nm_get_netievent_stop(mgr); + isc__nm_enqueue_ievent(worker, event); + } + + LOCK(&mgr->lock); + while (mgr->workers_running > 0) { + WAIT(&mgr->wkstatecond, &mgr->lock); + } + UNLOCK(&mgr->lock); + + for (int i = 0; i < mgr->nworkers; i++) { + isc__networker_t *worker = &mgr->workers[i]; + int r; + + r = uv_loop_close(&worker->loop); + UV_RUNTIME_CHECK(uv_loop_close, r); + + for (size_t type = 0; type < NETIEVENT_MAX; type++) { + INSIST(ISC_LIST_EMPTY(worker->ievents[type].list)); + isc_condition_destroy(&worker->ievents[type].cond); + isc_mutex_destroy(&worker->ievents[type].lock); + } + + isc_mem_put(mgr->mctx, worker->sendbuf, + ISC_NETMGR_SENDBUF_SIZE); + isc_mem_put(mgr->mctx, worker->recvbuf, + ISC_NETMGR_RECVBUF_SIZE); + isc_thread_join(worker->thread, NULL); + } + + if (mgr->stats != NULL) { + isc_stats_detach(&mgr->stats); + } + + isc_barrier_destroy(&mgr->resuming); + isc_barrier_destroy(&mgr->pausing); + + isc_condition_destroy(&mgr->wkstatecond); + isc_condition_destroy(&mgr->wkpausecond); + isc_mutex_destroy(&mgr->lock); + + isc_mem_put(mgr->mctx, mgr->workers, + mgr->nworkers * sizeof(isc__networker_t)); + isc_mem_putanddetach(&mgr->mctx, mgr, sizeof(*mgr)); + +#ifdef WIN32 + isc__nm_winsock_destroy(); +#endif /* WIN32 */ +} + +static void +enqueue_pause(isc__networker_t *worker) { + isc__netievent_pause_t *event = + isc__nm_get_netievent_pause(worker->mgr); + isc__nm_enqueue_ievent(worker, (isc__netievent_t *)event); +} + +static void +isc__nm_async_pause(isc__networker_t *worker, isc__netievent_t *ev0) { + UNUSED(ev0); + REQUIRE(worker->paused == false); + + worker->paused = true; + uv_stop(&worker->loop); +} + +void +isc_nm_pause(isc_nm_t *mgr) { + REQUIRE(VALID_NM(mgr)); + REQUIRE(!atomic_load(&mgr->paused)); + + isc__nm_acquire_interlocked_force(mgr); + + if (isc__nm_in_netthread()) { + REQUIRE(isc_nm_tid() == 0); + } + + for (int i = 0; i < mgr->nworkers; i++) { + isc__networker_t *worker = &mgr->workers[i]; + if (i == isc_nm_tid()) { + isc__nm_async_pause(worker, NULL); + } else { + enqueue_pause(worker); + } + } + + if (isc__nm_in_netthread()) { + atomic_fetch_add(&mgr->workers_paused, 1); + isc_barrier_wait(&mgr->pausing); + } + + LOCK(&mgr->lock); + while (atomic_load(&mgr->workers_paused) != mgr->workers_running) { + WAIT(&mgr->wkstatecond, &mgr->lock); + } + UNLOCK(&mgr->lock); + + REQUIRE(atomic_compare_exchange_strong(&mgr->paused, &(bool){ false }, + true)); +} + +static void +enqueue_resume(isc__networker_t *worker) { + isc__netievent_resume_t *event = + isc__nm_get_netievent_resume(worker->mgr); + isc__nm_enqueue_ievent(worker, (isc__netievent_t *)event); +} + +static void +isc__nm_async_resume(isc__networker_t *worker, isc__netievent_t *ev0) { + UNUSED(ev0); + REQUIRE(worker->paused == true); + + worker->paused = false; +} + +void +isc_nm_resume(isc_nm_t *mgr) { + REQUIRE(VALID_NM(mgr)); + REQUIRE(atomic_load(&mgr->paused)); + + if (isc__nm_in_netthread()) { + REQUIRE(isc_nm_tid() == 0); + drain_queue(&mgr->workers[isc_nm_tid()], NETIEVENT_PRIORITY); + } + + for (int i = 0; i < mgr->nworkers; i++) { + isc__networker_t *worker = &mgr->workers[i]; + if (i == isc_nm_tid()) { + isc__nm_async_resume(worker, NULL); + } else { + enqueue_resume(worker); + } + } + + if (isc__nm_in_netthread()) { + drain_queue(&mgr->workers[isc_nm_tid()], NETIEVENT_PRIVILEGED); + + atomic_fetch_sub(&mgr->workers_paused, 1); + isc_barrier_wait(&mgr->resuming); + } + + LOCK(&mgr->lock); + while (atomic_load(&mgr->workers_paused) != 0) { + WAIT(&mgr->wkstatecond, &mgr->lock); + } + UNLOCK(&mgr->lock); + + REQUIRE(atomic_compare_exchange_strong(&mgr->paused, &(bool){ true }, + false)); + + isc__nm_drop_interlocked(mgr); +} + +void +isc_nm_attach(isc_nm_t *mgr, isc_nm_t **dst) { + REQUIRE(VALID_NM(mgr)); + REQUIRE(dst != NULL && *dst == NULL); + + isc_refcount_increment(&mgr->references); + + *dst = mgr; +} + +void +isc_nm_detach(isc_nm_t **mgr0) { + isc_nm_t *mgr = NULL; + + REQUIRE(mgr0 != NULL); + REQUIRE(VALID_NM(*mgr0)); + + mgr = *mgr0; + *mgr0 = NULL; + + if (isc_refcount_decrement(&mgr->references) == 1) { + nm_destroy(&mgr); + } +} + +void +isc__netmgr_shutdown(isc_nm_t *mgr) { + REQUIRE(VALID_NM(mgr)); + + atomic_store(&mgr->closing, true); + for (int i = 0; i < mgr->nworkers; i++) { + isc__netievent_t *event = NULL; + event = isc__nm_get_netievent_shutdown(mgr); + isc__nm_enqueue_ievent(&mgr->workers[i], event); + } +} + +void +isc__netmgr_destroy(isc_nm_t **netmgrp) { + isc_nm_t *mgr = NULL; + int counter = 0; + + REQUIRE(VALID_NM(*netmgrp)); + + mgr = *netmgrp; + + /* + * Close active connections. + */ + isc__netmgr_shutdown(mgr); + + /* + * Wait for the manager to be dereferenced elsewhere. + */ + while (isc_refcount_current(&mgr->references) > 1 && counter++ < 1000) { + uv_sleep(10); + } + +#ifdef NETMGR_TRACE + if (isc_refcount_current(&mgr->references) > 1) { + isc__nm_dump_active(mgr); + UNREACHABLE(); + } +#endif + + /* + * Now just patiently wait + */ + while (isc_refcount_current(&mgr->references) > 1) { + uv_sleep(10); + } + + /* + * Detach final reference. + */ + isc_nm_detach(netmgrp); +} + +void +isc_nm_maxudp(isc_nm_t *mgr, uint32_t maxudp) { + REQUIRE(VALID_NM(mgr)); + + atomic_store(&mgr->maxudp, maxudp); +} + +void +isc_nmhandle_setwritetimeout(isc_nmhandle_t *handle, uint64_t write_timeout) { + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + + handle->sock->write_timeout = write_timeout; +} + +void +isc_nm_settimeouts(isc_nm_t *mgr, uint32_t init, uint32_t idle, + uint32_t keepalive, uint32_t advertised) { + REQUIRE(VALID_NM(mgr)); + + atomic_store(&mgr->init, init); + atomic_store(&mgr->idle, idle); + atomic_store(&mgr->keepalive, keepalive); + atomic_store(&mgr->advertised, advertised); +} + +bool +isc_nm_getloadbalancesockets(isc_nm_t *mgr) { + REQUIRE(VALID_NM(mgr)); + + return (mgr->load_balance_sockets); +} + +void +isc_nm_setloadbalancesockets(isc_nm_t *mgr, bool enabled) { + REQUIRE(VALID_NM(mgr)); + +#if HAVE_SO_REUSEPORT_LB + mgr->load_balance_sockets = enabled; +#else + UNUSED(enabled); +#endif +} + +void +isc_nm_gettimeouts(isc_nm_t *mgr, uint32_t *initial, uint32_t *idle, + uint32_t *keepalive, uint32_t *advertised) { + REQUIRE(VALID_NM(mgr)); + + if (initial != NULL) { + *initial = atomic_load(&mgr->init); + } + + if (idle != NULL) { + *idle = atomic_load(&mgr->idle); + } + + if (keepalive != NULL) { + *keepalive = atomic_load(&mgr->keepalive); + } + + if (advertised != NULL) { + *advertised = atomic_load(&mgr->advertised); + } +} + +/* + * nm_thread is a single worker thread, that runs uv_run event loop + * until asked to stop. + * + * There are four queues for asynchronous events: + * + * 1. priority queue - netievents on the priority queue are run even when + * the taskmgr enters exclusive mode and the netmgr is paused. This + * is needed to properly start listening on the interfaces, free + * resources on shutdown, or resume from a pause. + * + * 2. privileged task queue - only privileged tasks are queued here and + * this is the first queue that gets processed when network manager + * is unpaused using isc_nm_resume(). All netmgr workers need to + * clean the privileged task queue before they all proceed to normal + * operation. Both task queues are processed when the workers are + * shutting down. + * + * 3. task queue - only (traditional) tasks are scheduled here, and this + * queue and the privileged task queue are both processed when the + * netmgr workers are finishing. This is needed to process the task + * shutdown events. + * + * 4. normal queue - this is the queue with netmgr events, e.g. reading, + * sending, callbacks, etc. + */ + +static isc_threadresult_t +nm_thread(isc_threadarg_t worker0) { + isc__networker_t *worker = (isc__networker_t *)worker0; + isc_nm_t *mgr = worker->mgr; + + isc__nm_tid_v = worker->id; + + while (true) { + /* + * uv_run() runs async_cb() in a loop, which processes + * all four event queues until a "pause" or "stop" event + * is encountered. On pause, we process only priority and + * privileged events until resuming. + */ + int r = uv_run(&worker->loop, UV_RUN_DEFAULT); + INSIST(r > 0 || worker->finished); + + if (worker->paused) { + INSIST(atomic_load(&mgr->interlocked) != isc_nm_tid()); + + atomic_fetch_add(&mgr->workers_paused, 1); + if (isc_barrier_wait(&mgr->pausing) != 0) { + LOCK(&mgr->lock); + SIGNAL(&mgr->wkstatecond); + UNLOCK(&mgr->lock); + } + + while (worker->paused) { + wait_for_priority_queue(worker); + } + + /* + * All workers must drain the privileged event + * queue before we resume from pause. + */ + drain_queue(worker, NETIEVENT_PRIVILEGED); + + atomic_fetch_sub(&mgr->workers_paused, 1); + if (isc_barrier_wait(&mgr->resuming) != 0) { + LOCK(&mgr->lock); + SIGNAL(&mgr->wkstatecond); + UNLOCK(&mgr->lock); + } + } + + if (r == 0) { + INSIST(worker->finished); + break; + } + + INSIST(!worker->finished); + } + + /* + * We are shutting down. Drain the queues. + */ + drain_queue(worker, NETIEVENT_PRIVILEGED); + drain_queue(worker, NETIEVENT_TASK); + + for (size_t type = 0; type < NETIEVENT_MAX; type++) { + LOCK(&worker->ievents[type].lock); + INSIST(ISC_LIST_EMPTY(worker->ievents[type].list)); + UNLOCK(&worker->ievents[type].lock); + } + + LOCK(&mgr->lock); + mgr->workers_running--; + SIGNAL(&mgr->wkstatecond); + UNLOCK(&mgr->lock); + + return ((isc_threadresult_t)0); +} + +static bool +process_all_queues(isc__networker_t *worker) { + bool reschedule = false; + /* + * The queue processing functions will return false when the + * system is pausing or stopping and we don't want to process + * the other queues in such case, but we need the async event + * to be rescheduled in the next uv_run(). + */ + for (size_t type = 0; type < NETIEVENT_MAX; type++) { + isc_result_t result = process_queue(worker, type); + switch (result) { + case ISC_R_SUSPEND: + reschedule = true; + break; + case ISC_R_EMPTY: + /* empty queue */ + break; + case ISC_R_SUCCESS: + reschedule = true; + break; + default: + UNREACHABLE(); + } + } + + return (reschedule); +} + +/* + * async_cb() is a universal callback for 'async' events sent to event loop. + * It's the only way to safely pass data to the libuv event loop. We use a + * single async event and a set of lockless queues of 'isc__netievent_t' + * structures passed from other threads. + */ +static void +async_cb(uv_async_t *handle) { + isc__networker_t *worker = (isc__networker_t *)handle->loop->data; + + if (process_all_queues(worker)) { + /* + * If we didn't process all the events, we need to enqueue + * async_cb to be run in the next iteration of the uv_loop + */ + uv_async_send(handle); + } +} + +static void +isc__nm_async_stop(isc__networker_t *worker, isc__netievent_t *ev0) { + UNUSED(ev0); + worker->finished = true; + /* Close the async handler */ + uv_close((uv_handle_t *)&worker->async, NULL); +} + +void +isc_nm_task_enqueue(isc_nm_t *nm, isc_task_t *task, int threadid) { + isc__netievent_t *event = NULL; + int tid; + isc__networker_t *worker = NULL; + + if (threadid == -1) { + tid = (int)isc_random_uniform(nm->nworkers); + } else { + tid = threadid % nm->nworkers; + } + + worker = &nm->workers[tid]; + + if (isc_task_privileged(task)) { + event = (isc__netievent_t *) + isc__nm_get_netievent_privilegedtask(nm, task); + } else { + event = (isc__netievent_t *)isc__nm_get_netievent_task(nm, + task); + } + + isc__nm_enqueue_ievent(worker, event); +} + +#define isc__nm_async_privilegedtask(worker, ev0) \ + isc__nm_async_task(worker, ev0) + +static void +isc__nm_async_task(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_task_t *ievent = (isc__netievent_task_t *)ev0; + isc_result_t result; + + UNUSED(worker); + + result = isc_task_run(ievent->task); + + switch (result) { + case ISC_R_QUOTA: + isc_task_ready(ievent->task); + return; + case ISC_R_SUCCESS: + return; + default: + UNREACHABLE(); + } +} + +static void +wait_for_priority_queue(isc__networker_t *worker) { + isc_condition_t *cond = &worker->ievents[NETIEVENT_PRIORITY].cond; + isc_mutex_t *lock = &worker->ievents[NETIEVENT_PRIORITY].lock; + isc__netievent_list_t *list = + &(worker->ievents[NETIEVENT_PRIORITY].list); + + LOCK(lock); + while (ISC_LIST_EMPTY(*list)) { + WAIT(cond, lock); + } + UNLOCK(lock); + + drain_queue(worker, NETIEVENT_PRIORITY); +} + +static void +drain_queue(isc__networker_t *worker, netievent_type_t type) { + bool empty = false; + while (!empty) { + if (process_queue(worker, type) == ISC_R_EMPTY) { + LOCK(&worker->ievents[type].lock); + empty = ISC_LIST_EMPTY(worker->ievents[type].list); + UNLOCK(&worker->ievents[type].lock); + } + } +} + +/* + * The two macros here generate the individual cases for the process_netievent() + * function. The NETIEVENT_CASE(type) macro is the common case, and + * NETIEVENT_CASE_NOMORE(type) is a macro that causes the loop in the + * process_queue() to stop, e.g. it's only used for the netievent that + * stops/pauses processing the enqueued netievents. + */ +#define NETIEVENT_CASE(type) \ + case netievent_##type: { \ + isc__nm_async_##type(worker, ievent); \ + isc__nm_put_netievent_##type( \ + worker->mgr, (isc__netievent_##type##_t *)ievent); \ + return (true); \ + } + +#define NETIEVENT_CASE_NOMORE(type) \ + case netievent_##type: { \ + isc__nm_async_##type(worker, ievent); \ + isc__nm_put_netievent_##type(worker->mgr, ievent); \ + return (false); \ + } + +static bool +process_netievent(isc__networker_t *worker, isc__netievent_t *ievent) { + REQUIRE(worker->id == isc_nm_tid()); + + switch (ievent->type) { + /* Don't process more ievents when we are stopping */ + NETIEVENT_CASE_NOMORE(stop); + + NETIEVENT_CASE(privilegedtask); + NETIEVENT_CASE(task); + + NETIEVENT_CASE(udpconnect); + NETIEVENT_CASE(udplisten); + NETIEVENT_CASE(udpstop); + NETIEVENT_CASE(udpsend); + NETIEVENT_CASE(udpread); + NETIEVENT_CASE(udpcancel); + NETIEVENT_CASE(udpclose); + + NETIEVENT_CASE(tcpaccept); + NETIEVENT_CASE(tcpconnect); + NETIEVENT_CASE(tcplisten); + NETIEVENT_CASE(tcpstartread); + NETIEVENT_CASE(tcppauseread); + NETIEVENT_CASE(tcpsend); + NETIEVENT_CASE(tcpstop); + NETIEVENT_CASE(tcpcancel); + NETIEVENT_CASE(tcpclose); + + NETIEVENT_CASE(tcpdnsaccept); + NETIEVENT_CASE(tcpdnslisten); + NETIEVENT_CASE(tcpdnsconnect); + NETIEVENT_CASE(tcpdnssend); + NETIEVENT_CASE(tcpdnscancel); + NETIEVENT_CASE(tcpdnsclose); + NETIEVENT_CASE(tcpdnsread); + NETIEVENT_CASE(tcpdnsstop); + + NETIEVENT_CASE(connectcb); + NETIEVENT_CASE(readcb); + NETIEVENT_CASE(sendcb); + + NETIEVENT_CASE(close); + NETIEVENT_CASE(detach); + + NETIEVENT_CASE(shutdown); + NETIEVENT_CASE(resume); + NETIEVENT_CASE_NOMORE(pause); + default: + UNREACHABLE(); + } + return (true); +} + +static isc_result_t +process_queue(isc__networker_t *worker, netievent_type_t type) { + isc__netievent_t *ievent = NULL; + isc__netievent_list_t list; + + ISC_LIST_INIT(list); + + LOCK(&worker->ievents[type].lock); + ISC_LIST_MOVE(list, worker->ievents[type].list); + UNLOCK(&worker->ievents[type].lock); + + ievent = ISC_LIST_HEAD(list); + if (ievent == NULL) { + /* There's nothing scheduled */ + return (ISC_R_EMPTY); + } + + while (ievent != NULL) { + isc__netievent_t *next = ISC_LIST_NEXT(ievent, link); + ISC_LIST_DEQUEUE(list, ievent, link); + + if (!process_netievent(worker, ievent)) { + /* The netievent told us to stop */ + if (!ISC_LIST_EMPTY(list)) { + /* + * Reschedule the rest of the unprocessed + * events. + */ + LOCK(&worker->ievents[type].lock); + ISC_LIST_PREPENDLIST(worker->ievents[type].list, + list, link); + UNLOCK(&worker->ievents[type].lock); + } + return (ISC_R_SUSPEND); + } + + ievent = next; + } + + /* We processed at least one */ + return (ISC_R_SUCCESS); +} + +void * +isc__nm_get_netievent(isc_nm_t *mgr, isc__netievent_type type) { + isc__netievent_storage_t *event = isc_mem_get(mgr->mctx, + sizeof(*event)); + + *event = (isc__netievent_storage_t){ .ni.type = type }; + ISC_LINK_INIT(&(event->ni), link); + return (event); +} + +void +isc__nm_put_netievent(isc_nm_t *mgr, void *ievent) { + isc_mem_put(mgr->mctx, ievent, sizeof(isc__netievent_storage_t)); +} + +NETIEVENT_SOCKET_DEF(tcpclose); +NETIEVENT_SOCKET_DEF(tcplisten); +NETIEVENT_SOCKET_DEF(tcppauseread); +NETIEVENT_SOCKET_DEF(tcpstartread); +NETIEVENT_SOCKET_DEF(tcpstop); +NETIEVENT_SOCKET_DEF(udpclose); +NETIEVENT_SOCKET_DEF(udplisten); +NETIEVENT_SOCKET_DEF(udpread); +NETIEVENT_SOCKET_DEF(udpsend); +NETIEVENT_SOCKET_DEF(udpstop); + +NETIEVENT_SOCKET_DEF(tcpdnsclose); +NETIEVENT_SOCKET_DEF(tcpdnsread); +NETIEVENT_SOCKET_DEF(tcpdnsstop); +NETIEVENT_SOCKET_DEF(tcpdnslisten); +NETIEVENT_SOCKET_REQ_DEF(tcpdnsconnect); +NETIEVENT_SOCKET_REQ_DEF(tcpdnssend); +NETIEVENT_SOCKET_HANDLE_DEF(tcpdnscancel); +NETIEVENT_SOCKET_QUOTA_DEF(tcpdnsaccept); + +NETIEVENT_SOCKET_REQ_DEF(tcpconnect); +NETIEVENT_SOCKET_REQ_DEF(tcpsend); +NETIEVENT_SOCKET_REQ_DEF(udpconnect); +NETIEVENT_SOCKET_REQ_RESULT_DEF(connectcb); +NETIEVENT_SOCKET_REQ_RESULT_DEF(readcb); +NETIEVENT_SOCKET_REQ_RESULT_DEF(sendcb); + +NETIEVENT_SOCKET_DEF(detach); +NETIEVENT_SOCKET_HANDLE_DEF(tcpcancel); +NETIEVENT_SOCKET_HANDLE_DEF(udpcancel); + +NETIEVENT_SOCKET_QUOTA_DEF(tcpaccept); + +NETIEVENT_SOCKET_DEF(close); +NETIEVENT_DEF(pause); +NETIEVENT_DEF(resume); +NETIEVENT_DEF(shutdown); +NETIEVENT_DEF(stop); + +NETIEVENT_TASK_DEF(task); +NETIEVENT_TASK_DEF(privilegedtask); + +void +isc__nm_maybe_enqueue_ievent(isc__networker_t *worker, + isc__netievent_t *event) { + /* + * If we are already in the matching nmthread, process the ievent + * directly. + */ + if (worker->id == isc_nm_tid()) { + process_netievent(worker, event); + return; + } + + isc__nm_enqueue_ievent(worker, event); +} + +void +isc__nm_enqueue_ievent(isc__networker_t *worker, isc__netievent_t *event) { + netievent_type_t type; + + if (event->type > netievent_prio) { + type = NETIEVENT_PRIORITY; + } else { + switch (event->type) { + case netievent_prio: + UNREACHABLE(); + break; + case netievent_privilegedtask: + type = NETIEVENT_PRIVILEGED; + break; + case netievent_task: + type = NETIEVENT_TASK; + break; + default: + type = NETIEVENT_NORMAL; + break; + } + } + + /* + * We need to make sure this signal will be delivered and + * the queue will be processed. + */ + LOCK(&worker->ievents[type].lock); + ISC_LIST_ENQUEUE(worker->ievents[type].list, event, link); + if (type == NETIEVENT_PRIORITY) { + SIGNAL(&worker->ievents[type].cond); + } + UNLOCK(&worker->ievents[type].lock); + + uv_async_send(&worker->async); +} + +bool +isc__nmsocket_active(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + if (sock->parent != NULL) { + return (atomic_load(&sock->parent->active)); + } + + return (atomic_load(&sock->active)); +} + +bool +isc__nmsocket_deactivate(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + + if (sock->parent != NULL) { + return (atomic_compare_exchange_strong(&sock->parent->active, + &(bool){ true }, false)); + } + + return (atomic_compare_exchange_strong(&sock->active, &(bool){ true }, + false)); +} + +void +isc___nmsocket_attach(isc_nmsocket_t *sock, isc_nmsocket_t **target FLARG) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(target != NULL && *target == NULL); + + isc_nmsocket_t *rsock = NULL; + + if (sock->parent != NULL) { + rsock = sock->parent; + INSIST(rsock->parent == NULL); /* sanity check */ + } else { + rsock = sock; + } + + NETMGR_TRACE_LOG("isc__nmsocket_attach():%p->references = %" PRIuFAST32 + "\n", + rsock, isc_refcount_current(&rsock->references) + 1); + + isc_refcount_increment0(&rsock->references); + + *target = sock; +} + +/* + * Free all resources inside a socket (including its children if any). + */ +static void +nmsocket_cleanup(isc_nmsocket_t *sock, bool dofree FLARG) { + isc_nmhandle_t *handle = NULL; + isc__nm_uvreq_t *uvreq = NULL; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(!isc__nmsocket_active(sock)); + + NETMGR_TRACE_LOG("nmsocket_cleanup():%p->references = %" PRIuFAST32 + "\n", + sock, isc_refcount_current(&sock->references)); + + atomic_store(&sock->destroying, true); + + if (sock->parent == NULL && sock->children != NULL) { + /* + * We shouldn't be here unless there are no active handles, + * so we can clean up and free the children. + */ + for (size_t i = 0; i < sock->nchildren; i++) { + if (!atomic_load(&sock->children[i].destroying)) { + nmsocket_cleanup(&sock->children[i], + false FLARG_PASS); + } + } + + /* + * This was a parent socket: destroy the listening + * barriers that synchronized the children. + */ + isc_barrier_destroy(&sock->startlistening); + isc_barrier_destroy(&sock->stoplistening); + + /* + * Now free them. + */ + isc_mem_put(sock->mgr->mctx, sock->children, + sock->nchildren * sizeof(*sock)); + sock->children = NULL; + sock->nchildren = 0; + } + if (sock->statsindex != NULL) { + isc__nm_decstats(sock->mgr, sock->statsindex[STATID_ACTIVE]); + } + + sock->statichandle = NULL; + + if (sock->outerhandle != NULL) { + isc__nmhandle_detach(&sock->outerhandle FLARG_PASS); + } + + if (sock->outer != NULL) { + isc___nmsocket_detach(&sock->outer FLARG_PASS); + } + + while ((handle = isc_astack_pop(sock->inactivehandles)) != NULL) { + nmhandle_free(sock, handle); + } + + if (sock->buf != NULL) { + isc_mem_free(sock->mgr->mctx, sock->buf); + } + + if (sock->quota != NULL) { + isc_quota_detach(&sock->quota); + } + + sock->pquota = NULL; + + isc_astack_destroy(sock->inactivehandles); + + while ((uvreq = isc_astack_pop(sock->inactivereqs)) != NULL) { + isc_mem_put(sock->mgr->mctx, uvreq, sizeof(*uvreq)); + } + + isc_astack_destroy(sock->inactivereqs); + sock->magic = 0; + + isc_condition_destroy(&sock->scond); + isc_condition_destroy(&sock->cond); + isc_mutex_destroy(&sock->lock); +#ifdef NETMGR_TRACE + LOCK(&sock->mgr->lock); + ISC_LIST_UNLINK(sock->mgr->active_sockets, sock, active_link); + UNLOCK(&sock->mgr->lock); +#endif + if (dofree) { + isc_nm_t *mgr = sock->mgr; + isc_mem_put(mgr->mctx, sock, sizeof(*sock)); + isc_nm_detach(&mgr); + } else { + isc_nm_detach(&sock->mgr); + } +} + +static void +nmsocket_maybe_destroy(isc_nmsocket_t *sock FLARG) { + int active_handles; + bool destroy = false; + + NETMGR_TRACE_LOG("%s():%p->references = %" PRIuFAST32 "\n", __func__, + sock, isc_refcount_current(&sock->references)); + + if (sock->parent != NULL) { + /* + * This is a child socket and cannot be destroyed except + * as a side effect of destroying the parent, so let's go + * see if the parent is ready to be destroyed. + */ + nmsocket_maybe_destroy(sock->parent FLARG_PASS); + return; + } + + /* + * This is a parent socket (or a standalone). See whether the + * children have active handles before deciding whether to + * accept destruction. + */ + LOCK(&sock->lock); + if (atomic_load(&sock->active) || atomic_load(&sock->destroying) || + !atomic_load(&sock->closed) || atomic_load(&sock->references) != 0) + { + UNLOCK(&sock->lock); + return; + } + + active_handles = atomic_load(&sock->ah); + if (sock->children != NULL) { + for (size_t i = 0; i < sock->nchildren; i++) { + LOCK(&sock->children[i].lock); + active_handles += atomic_load(&sock->children[i].ah); + UNLOCK(&sock->children[i].lock); + } + } + + if (active_handles == 0 || sock->statichandle != NULL) { + destroy = true; + } + + NETMGR_TRACE_LOG("%s:%p->active_handles = %d, .statichandle = %p\n", + __func__, sock, active_handles, sock->statichandle); + + if (destroy) { + atomic_store(&sock->destroying, true); + UNLOCK(&sock->lock); + nmsocket_cleanup(sock, true FLARG_PASS); + } else { + UNLOCK(&sock->lock); + } +} + +void +isc___nmsocket_prep_destroy(isc_nmsocket_t *sock FLARG) { + REQUIRE(sock->parent == NULL); + + NETMGR_TRACE_LOG("isc___nmsocket_prep_destroy():%p->references = " + "%" PRIuFAST32 "\n", + sock, isc_refcount_current(&sock->references)); + + /* + * The final external reference to the socket is gone. We can try + * destroying the socket, but we have to wait for all the inflight + * handles to finish first. + */ + atomic_store(&sock->active, false); + + /* + * If the socket has children, they'll need to be marked inactive + * so they can be cleaned up too. + */ + if (sock->children != NULL) { + for (size_t i = 0; i < sock->nchildren; i++) { + atomic_store(&sock->children[i].active, false); + } + } + + /* + * If we're here then we already stopped listening; otherwise + * we'd have a hanging reference from the listening process. + * + * If it's a regular socket we may need to close it. + */ + if (!atomic_load(&sock->closed)) { + switch (sock->type) { + case isc_nm_udpsocket: + isc__nm_udp_close(sock); + return; + case isc_nm_tcpsocket: + isc__nm_tcp_close(sock); + return; + case isc_nm_tcpdnssocket: + isc__nm_tcpdns_close(sock); + return; + default: + break; + } + } + + nmsocket_maybe_destroy(sock FLARG_PASS); +} + +void +isc___nmsocket_detach(isc_nmsocket_t **sockp FLARG) { + REQUIRE(sockp != NULL && *sockp != NULL); + REQUIRE(VALID_NMSOCK(*sockp)); + + isc_nmsocket_t *sock = *sockp, *rsock = NULL; + *sockp = NULL; + + /* + * If the socket is a part of a set (a child socket) we are + * counting references for the whole set at the parent. + */ + if (sock->parent != NULL) { + rsock = sock->parent; + INSIST(rsock->parent == NULL); /* Sanity check */ + } else { + rsock = sock; + } + + NETMGR_TRACE_LOG("isc__nmsocket_detach():%p->references = %" PRIuFAST32 + "\n", + rsock, isc_refcount_current(&rsock->references) - 1); + + if (isc_refcount_decrement(&rsock->references) == 1) { + isc___nmsocket_prep_destroy(rsock FLARG_PASS); + } +} + +void +isc_nmsocket_close(isc_nmsocket_t **sockp) { + REQUIRE(sockp != NULL); + REQUIRE(VALID_NMSOCK(*sockp)); + REQUIRE((*sockp)->type == isc_nm_udplistener || + (*sockp)->type == isc_nm_tcplistener || + (*sockp)->type == isc_nm_tcpdnslistener); + + isc__nmsocket_detach(sockp); +} + +void +isc___nmsocket_init(isc_nmsocket_t *sock, isc_nm_t *mgr, isc_nmsocket_type type, + isc_sockaddr_t *iface FLARG) { + uint16_t family; + + REQUIRE(sock != NULL); + REQUIRE(mgr != NULL); + REQUIRE(iface != NULL); + + family = iface->type.sa.sa_family; + + *sock = (isc_nmsocket_t){ .type = type, + .iface = *iface, + .fd = -1, + .inactivehandles = isc_astack_new( + mgr->mctx, ISC_NM_HANDLES_STACK_SIZE), + .inactivereqs = isc_astack_new( + mgr->mctx, ISC_NM_REQS_STACK_SIZE) }; + +#if NETMGR_TRACE + sock->backtrace_size = backtrace(sock->backtrace, TRACE_SIZE); + ISC_LINK_INIT(sock, active_link); + ISC_LIST_INIT(sock->active_handles); + LOCK(&mgr->lock); + ISC_LIST_APPEND(mgr->active_sockets, sock, active_link); + UNLOCK(&mgr->lock); +#endif + + isc_nm_attach(mgr, &sock->mgr); + sock->uv_handle.handle.data = sock; + + ISC_LINK_INIT(&sock->quotacb, link); + + switch (type) { + case isc_nm_udpsocket: + case isc_nm_udplistener: + if (family == AF_INET) { + sock->statsindex = udp4statsindex; + } else { + sock->statsindex = udp6statsindex; + } + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_ACTIVE]); + break; + case isc_nm_tcpsocket: + case isc_nm_tcplistener: + case isc_nm_tcpdnssocket: + case isc_nm_tcpdnslistener: + if (family == AF_INET) { + sock->statsindex = tcp4statsindex; + } else { + sock->statsindex = tcp6statsindex; + } + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_ACTIVE]); + break; + default: + break; + } + + isc_mutex_init(&sock->lock); + isc_condition_init(&sock->cond); + isc_condition_init(&sock->scond); + isc_refcount_init(&sock->references, 1); + + NETMGR_TRACE_LOG("isc__nmsocket_init():%p->references = %" PRIuFAST32 + "\n", + sock, isc_refcount_current(&sock->references)); + + atomic_init(&sock->active, true); + atomic_init(&sock->sequential, false); + atomic_init(&sock->readpaused, false); + atomic_init(&sock->closing, false); + atomic_init(&sock->listening, 0); + atomic_init(&sock->closed, 0); + atomic_init(&sock->destroying, 0); + atomic_init(&sock->ah, 0); + atomic_init(&sock->client, 0); + atomic_init(&sock->connecting, false); + atomic_init(&sock->keepalive, false); + atomic_init(&sock->connected, false); + atomic_init(&sock->timedout, false); + + atomic_init(&sock->active_child_connections, 0); + + sock->magic = NMSOCK_MAGIC; +} + +void +isc__nmsocket_clearcb(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(!isc__nm_in_netthread() || sock->tid == isc_nm_tid()); + + sock->recv_cb = NULL; + sock->recv_cbarg = NULL; + sock->accept_cb = NULL; + sock->accept_cbarg = NULL; + sock->connect_cb = NULL; + sock->connect_cbarg = NULL; +} + +void +isc__nm_free_uvbuf(isc_nmsocket_t *sock, const uv_buf_t *buf) { + isc__networker_t *worker = NULL; + + REQUIRE(VALID_NMSOCK(sock)); + + worker = &sock->mgr->workers[sock->tid]; + REQUIRE(buf->base == worker->recvbuf); + + worker->recvbuf_inuse = false; +} + +static isc_nmhandle_t * +alloc_handle(isc_nmsocket_t *sock) { + isc_nmhandle_t *handle = + isc_mem_get(sock->mgr->mctx, + sizeof(isc_nmhandle_t) + sock->extrahandlesize); + + *handle = (isc_nmhandle_t){ .magic = NMHANDLE_MAGIC }; +#ifdef NETMGR_TRACE + ISC_LINK_INIT(handle, active_link); +#endif + isc_refcount_init(&handle->references, 1); + + return (handle); +} + +isc_nmhandle_t * +isc___nmhandle_get(isc_nmsocket_t *sock, isc_sockaddr_t *peer, + isc_sockaddr_t *local FLARG) { + isc_nmhandle_t *handle = NULL; + + REQUIRE(VALID_NMSOCK(sock)); + + handle = isc_astack_pop(sock->inactivehandles); + + if (handle == NULL) { + handle = alloc_handle(sock); + } else { + isc_refcount_init(&handle->references, 1); + INSIST(VALID_NMHANDLE(handle)); + } + + NETMGR_TRACE_LOG( + "isc__nmhandle_get():handle %p->references = %" PRIuFAST32 "\n", + handle, isc_refcount_current(&handle->references)); + + isc___nmsocket_attach(sock, &handle->sock FLARG_PASS); + +#if NETMGR_TRACE + handle->backtrace_size = backtrace(handle->backtrace, TRACE_SIZE); +#endif + + if (peer != NULL) { + handle->peer = *peer; + } else { + handle->peer = sock->peer; + } + + if (local != NULL) { + handle->local = *local; + } else { + handle->local = sock->iface; + } + + (void)atomic_fetch_add(&sock->ah, 1); + +#ifdef NETMGR_TRACE + LOCK(&sock->lock); + ISC_LIST_APPEND(sock->active_handles, handle, active_link); + UNLOCK(&sock->lock); +#endif + + switch (sock->type) { + case isc_nm_udpsocket: + case isc_nm_tcpdnssocket: + if (!atomic_load(&sock->client)) { + break; + } + FALLTHROUGH; + case isc_nm_tcpsocket: + INSIST(sock->statichandle == NULL); + + /* + * statichandle must be assigned, not attached; + * otherwise, if a handle was detached elsewhere + * it could never reach 0 references, and the + * handle and socket would never be freed. + */ + sock->statichandle = handle; + break; + default: + break; + } + + return (handle); +} + +void +isc__nmhandle_attach(isc_nmhandle_t *handle, isc_nmhandle_t **handlep FLARG) { + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(handlep != NULL && *handlep == NULL); + + NETMGR_TRACE_LOG("isc__nmhandle_attach():handle %p->references = " + "%" PRIuFAST32 "\n", + handle, isc_refcount_current(&handle->references) + 1); + + isc_refcount_increment(&handle->references); + *handlep = handle; +} + +bool +isc_nmhandle_is_stream(isc_nmhandle_t *handle) { + REQUIRE(VALID_NMHANDLE(handle)); + + return (handle->sock->type == isc_nm_tcpsocket || + handle->sock->type == isc_nm_tcpdnssocket); +} + +static void +nmhandle_free(isc_nmsocket_t *sock, isc_nmhandle_t *handle) { + size_t extra = sock->extrahandlesize; + + isc_refcount_destroy(&handle->references); + + if (handle->dofree != NULL) { + handle->dofree(handle->opaque); + } + + *handle = (isc_nmhandle_t){ .magic = 0 }; + + isc_mem_put(sock->mgr->mctx, handle, sizeof(isc_nmhandle_t) + extra); +} + +static void +nmhandle_deactivate(isc_nmsocket_t *sock, isc_nmhandle_t *handle) { + bool reuse = false; + + /* + * We do all of this under lock to avoid races with socket + * destruction. We have to do this now, because at this point the + * socket is either unused or still attached to event->sock. + */ + LOCK(&sock->lock); + +#ifdef NETMGR_TRACE + ISC_LIST_UNLINK(sock->active_handles, handle, active_link); +#endif + + INSIST(atomic_fetch_sub(&sock->ah, 1) > 0); + +#if !__SANITIZE_ADDRESS__ && !__SANITIZE_THREAD__ + if (atomic_load(&sock->active)) { + reuse = isc_astack_trypush(sock->inactivehandles, handle); + } +#endif /* !__SANITIZE_ADDRESS__ && !__SANITIZE_THREAD__ */ + if (!reuse) { + nmhandle_free(sock, handle); + } + UNLOCK(&sock->lock); +} + +void +isc__nmhandle_detach(isc_nmhandle_t **handlep FLARG) { + isc_nmsocket_t *sock = NULL; + isc_nmhandle_t *handle = NULL; + + REQUIRE(handlep != NULL); + REQUIRE(VALID_NMHANDLE(*handlep)); + + handle = *handlep; + *handlep = NULL; + + /* + * If the closehandle_cb is set, it needs to run asynchronously to + * ensure correct ordering of the isc__nm_process_sock_buffer(). + */ + sock = handle->sock; + if (sock->tid == isc_nm_tid() && sock->closehandle_cb == NULL) { + nmhandle_detach_cb(&handle FLARG_PASS); + } else { + isc__netievent_detach_t *event = + isc__nm_get_netievent_detach(sock->mgr, sock); + /* + * we are using implicit "attach" as the last reference + * need to be destroyed explicitly in the async callback + */ + event->handle = handle; + FLARG_IEVENT_PASS(event); + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)event); + } +} + +void +isc__nmsocket_shutdown(isc_nmsocket_t *sock); + +static void +nmhandle_detach_cb(isc_nmhandle_t **handlep FLARG) { + isc_nmsocket_t *sock = NULL; + isc_nmhandle_t *handle = NULL; + + REQUIRE(handlep != NULL); + REQUIRE(VALID_NMHANDLE(*handlep)); + + handle = *handlep; + *handlep = NULL; + + NETMGR_TRACE_LOG("isc__nmhandle_detach():%p->references = %" PRIuFAST32 + "\n", + handle, isc_refcount_current(&handle->references) - 1); + + if (isc_refcount_decrement(&handle->references) > 1) { + return; + } + + /* We need an acquire memory barrier here */ + (void)isc_refcount_current(&handle->references); + + sock = handle->sock; + handle->sock = NULL; + + if (handle->doreset != NULL) { + handle->doreset(handle->opaque); + } + + nmhandle_deactivate(sock, handle); + + /* + * The handle is gone now. If the socket has a callback configured + * for that (e.g., to perform cleanup after request processing), + * call it now, or schedule it to run asynchronously. + */ + if (sock->closehandle_cb != NULL) { + if (sock->tid == isc_nm_tid()) { + sock->closehandle_cb(sock); + } else { + isc__netievent_close_t *event = + isc__nm_get_netievent_close(sock->mgr, sock); + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)event); + } + } + + if (handle == sock->statichandle) { + /* statichandle is assigned, not attached. */ + sock->statichandle = NULL; + } + + isc___nmsocket_detach(&sock FLARG_PASS); +} + +void * +isc_nmhandle_getdata(isc_nmhandle_t *handle) { + REQUIRE(VALID_NMHANDLE(handle)); + + return (handle->opaque); +} + +void +isc_nmhandle_setdata(isc_nmhandle_t *handle, void *arg, + isc_nm_opaquecb_t doreset, isc_nm_opaquecb_t dofree) { + REQUIRE(VALID_NMHANDLE(handle)); + + handle->opaque = arg; + handle->doreset = doreset; + handle->dofree = dofree; +} + +void +isc__nm_alloc_dnsbuf(isc_nmsocket_t *sock, size_t len) { + REQUIRE(len <= NM_BIG_BUF); + + if (sock->buf == NULL) { + /* We don't have the buffer at all */ + size_t alloc_len = len < NM_REG_BUF ? NM_REG_BUF : NM_BIG_BUF; + sock->buf = isc_mem_allocate(sock->mgr->mctx, alloc_len); + sock->buf_size = alloc_len; + } else { + /* We have the buffer but it's too small */ + sock->buf = isc_mem_reallocate(sock->mgr->mctx, sock->buf, + NM_BIG_BUF); + sock->buf_size = NM_BIG_BUF; + } +} + +void +isc__nm_failed_send_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req, + isc_result_t eresult) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(VALID_UVREQ(req)); + + if (req->cb.send != NULL) { + isc__nm_sendcb(sock, req, eresult, true); + } else { + isc__nm_uvreq_put(&req, sock); + } +} + +void +isc__nm_failed_accept_cb(isc_nmsocket_t *sock, isc_result_t eresult) { + REQUIRE(sock->accepting); + REQUIRE(sock->server); + + /* + * Detach the quota early to make room for other connections; + * otherwise it'd be detached later asynchronously, and clog + * the quota unnecessarily. + */ + if (sock->quota != NULL) { + isc_quota_detach(&sock->quota); + } + + isc__nmsocket_detach(&sock->server); + + sock->accepting = false; + + switch (eresult) { + case ISC_R_NOTCONNECTED: + /* IGNORE: The client disconnected before we could accept */ + break; + default: + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_NETMGR, ISC_LOG_ERROR, + "Accepting TCP connection failed: %s", + isc_result_totext(eresult)); + } +} + +void +isc__nm_failed_connect_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req, + isc_result_t eresult, bool async) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(VALID_UVREQ(req)); + REQUIRE(sock->tid == isc_nm_tid()); + REQUIRE(req->cb.connect != NULL); + + isc__nmsocket_timer_stop(sock); + uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock); + + INSIST(atomic_compare_exchange_strong(&sock->connecting, + &(bool){ true }, false)); + + isc__nmsocket_clearcb(sock); + isc__nm_connectcb(sock, req, eresult, async); + + isc__nmsocket_prep_destroy(sock); +} + +void +isc__nm_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result, bool async) { + REQUIRE(VALID_NMSOCK(sock)); + UNUSED(async); + + switch (sock->type) { + case isc_nm_udpsocket: + isc__nm_udp_failed_read_cb(sock, result); + return; + case isc_nm_tcpsocket: + isc__nm_tcp_failed_read_cb(sock, result); + return; + case isc_nm_tcpdnssocket: + isc__nm_tcpdns_failed_read_cb(sock, result); + return; + default: + UNREACHABLE(); + } +} + +void +isc__nmsocket_connecttimeout_cb(uv_timer_t *timer) { + uv_connect_t *uvreq = uv_handle_get_data((uv_handle_t *)timer); + isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)uvreq->handle); + isc__nm_uvreq_t *req = uv_handle_get_data((uv_handle_t *)uvreq); + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + REQUIRE(atomic_load(&sock->connecting)); + REQUIRE(VALID_UVREQ(req)); + REQUIRE(VALID_NMHANDLE(req->handle)); + + isc__nmsocket_timer_stop(sock); + + /* + * Mark the connection as timed out and shutdown the socket. + */ + + INSIST(atomic_compare_exchange_strong(&sock->timedout, &(bool){ false }, + true)); + isc__nmsocket_clearcb(sock); + isc__nmsocket_shutdown(sock); +} + +void +isc__nm_accept_connection_log(isc_result_t result, bool can_log_quota) { + int level; + + switch (result) { + case ISC_R_SUCCESS: + case ISC_R_NOCONN: + return; + case ISC_R_QUOTA: + case ISC_R_SOFTQUOTA: + if (!can_log_quota) { + return; + } + level = ISC_LOG_INFO; + break; + case ISC_R_NOTCONNECTED: + level = ISC_LOG_INFO; + break; + default: + level = ISC_LOG_ERROR; + } + + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, ISC_LOGMODULE_NETMGR, + level, "Accepting TCP connection failed: %s", + isc_result_totext(result)); +} + +void +isc__nmsocket_writetimeout_cb(void *data, isc_result_t eresult) { + isc__nm_uvreq_t *req = data; + isc_nmsocket_t *sock = NULL; + + REQUIRE(eresult == ISC_R_TIMEDOUT); + REQUIRE(VALID_UVREQ(req)); + REQUIRE(VALID_NMSOCK(req->sock)); + + sock = req->sock; + + isc__nmsocket_reset(sock); +} + +void +isc__nmsocket_readtimeout_cb(uv_timer_t *timer) { + isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)timer); + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + REQUIRE(sock->reading); + + if (atomic_load(&sock->client)) { + uv_timer_stop(timer); + + if (sock->recv_cb != NULL) { + isc__nm_uvreq_t *req = isc__nm_get_read_req(sock, NULL); + isc__nm_readcb(sock, req, ISC_R_TIMEDOUT); + } + + if (!isc__nmsocket_timer_running(sock)) { + isc__nmsocket_clearcb(sock); + isc__nm_failed_read_cb(sock, ISC_R_CANCELED, false); + } + } else { + isc__nm_failed_read_cb(sock, ISC_R_TIMEDOUT, false); + } +} + +void +isc__nmsocket_timer_restart(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + + if (atomic_load(&sock->connecting)) { + int r; + + if (sock->connect_timeout == 0) { + return; + } + + r = uv_timer_start(&sock->read_timer, + isc__nmsocket_connecttimeout_cb, + sock->connect_timeout + 10, 0); + UV_RUNTIME_CHECK(uv_timer_start, r); + + } else { + int r; + + if (sock->read_timeout == 0) { + return; + } + + r = uv_timer_start(&sock->read_timer, + isc__nmsocket_readtimeout_cb, + sock->read_timeout, 0); + UV_RUNTIME_CHECK(uv_timer_start, r); + } +} + +bool +isc__nmsocket_timer_running(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + + return (uv_is_active((uv_handle_t *)&sock->read_timer)); +} + +void +isc__nmsocket_timer_start(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + + if (isc__nmsocket_timer_running(sock)) { + return; + } + + isc__nmsocket_timer_restart(sock); +} + +void +isc__nmsocket_timer_stop(isc_nmsocket_t *sock) { + int r; + + REQUIRE(VALID_NMSOCK(sock)); + + /* uv_timer_stop() is idempotent, no need to check if running */ + + r = uv_timer_stop(&sock->read_timer); + UV_RUNTIME_CHECK(uv_timer_stop, r); +} + +isc__nm_uvreq_t * +isc__nm_get_read_req(isc_nmsocket_t *sock, isc_sockaddr_t *sockaddr) { + isc__nm_uvreq_t *req = NULL; + + req = isc__nm_uvreq_get(sock->mgr, sock); + req->cb.recv = sock->recv_cb; + req->cbarg = sock->recv_cbarg; + + switch (sock->type) { + case isc_nm_tcpsocket: + isc_nmhandle_attach(sock->statichandle, &req->handle); + break; + default: + if (atomic_load(&sock->client)) { + isc_nmhandle_attach(sock->statichandle, &req->handle); + } else { + req->handle = isc__nmhandle_get(sock, sockaddr, NULL); + } + break; + } + + return (req); +} + +/*%< + * Allocator callback for read operations. + * + * Note this doesn't actually allocate anything, it just assigns the + * worker's receive buffer to a socket, and marks it as "in use". + */ +void +isc__nm_alloc_cb(uv_handle_t *handle, size_t size, uv_buf_t *buf) { + isc_nmsocket_t *sock = uv_handle_get_data(handle); + isc__networker_t *worker = NULL; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(isc__nm_in_netthread()); + /* + * The size provided by libuv is only suggested size, and it always + * defaults to 64 * 1024 in the current versions of libuv (see + * src/unix/udp.c and src/unix/stream.c). + */ + UNUSED(size); + + worker = &sock->mgr->workers[sock->tid]; + INSIST(!worker->recvbuf_inuse); + INSIST(worker->recvbuf != NULL); + + switch (sock->type) { + case isc_nm_udpsocket: + buf->len = ISC_NETMGR_UDP_RECVBUF_SIZE; + break; + case isc_nm_tcpsocket: + case isc_nm_tcpdnssocket: + buf->len = ISC_NETMGR_TCP_RECVBUF_SIZE; + break; + default: + UNREACHABLE(); + } + + REQUIRE(buf->len <= ISC_NETMGR_RECVBUF_SIZE); + buf->base = worker->recvbuf; + + worker->recvbuf_inuse = true; +} + +isc_result_t +isc__nm_start_reading(isc_nmsocket_t *sock) { + isc_result_t result = ISC_R_SUCCESS; + int r; + + if (sock->reading) { + return (ISC_R_SUCCESS); + } + + switch (sock->type) { + case isc_nm_udpsocket: + r = uv_udp_recv_start(&sock->uv_handle.udp, isc__nm_alloc_cb, + isc__nm_udp_read_cb); + break; + case isc_nm_tcpsocket: + r = uv_read_start(&sock->uv_handle.stream, isc__nm_alloc_cb, + isc__nm_tcp_read_cb); + break; + case isc_nm_tcpdnssocket: + r = uv_read_start(&sock->uv_handle.stream, isc__nm_alloc_cb, + isc__nm_tcpdns_read_cb); + break; + default: + UNREACHABLE(); + } + + if (r != 0) { + result = isc__nm_uverr2result(r); + } else { + sock->reading = true; + } + + return (result); +} + +void +isc__nm_stop_reading(isc_nmsocket_t *sock) { + int r; + + if (!sock->reading) { + return; + } + + switch (sock->type) { + case isc_nm_udpsocket: + r = uv_udp_recv_stop(&sock->uv_handle.udp); + UV_RUNTIME_CHECK(uv_udp_recv_stop, r); + break; + case isc_nm_tcpsocket: + case isc_nm_tcpdnssocket: + r = uv_read_stop(&sock->uv_handle.stream); + UV_RUNTIME_CHECK(uv_read_stop, r); + break; + default: + UNREACHABLE(); + } + sock->reading = false; +} + +bool +isc__nm_closing(isc_nmsocket_t *sock) { + return (atomic_load(&sock->mgr->closing)); +} + +bool +isc__nmsocket_closing(isc_nmsocket_t *sock) { + return (!isc__nmsocket_active(sock) || atomic_load(&sock->closing) || + atomic_load(&sock->mgr->closing) || + (sock->server != NULL && !isc__nmsocket_active(sock->server))); +} + +static isc_result_t +processbuffer(isc_nmsocket_t *sock) { + switch (sock->type) { + case isc_nm_tcpdnssocket: + return (isc__nm_tcpdns_processbuffer(sock)); + default: + UNREACHABLE(); + } +} + +/* + * Process a DNS message. + * + * If we only have an incomplete DNS message, we don't touch any + * timers. If we do have a full message, reset the timer. + * + * Stop reading if this is a client socket, or if the server socket + * has been set to sequential mode, or the number of queries we are + * processing simultaneously has reached the clients-per-connection + * limit. In this case we'll be called again by resume_processing() + * later. + */ +isc_result_t +isc__nm_process_sock_buffer(isc_nmsocket_t *sock) { + for (;;) { + int_fast32_t ah = atomic_load(&sock->ah); + isc_result_t result = processbuffer(sock); + switch (result) { + case ISC_R_NOMORE: + /* + * Don't reset the timer until we have a + * full DNS message. + */ + result = isc__nm_start_reading(sock); + if (result != ISC_R_SUCCESS) { + return (result); + } + /* + * Start the timer only if there are no externally used + * active handles, there's always one active handle + * attached internally to sock->recv_handle in + * accept_connection() + */ + if (ah == 1) { + isc__nmsocket_timer_start(sock); + } + goto done; + case ISC_R_CANCELED: + isc__nmsocket_timer_stop(sock); + isc__nm_stop_reading(sock); + goto done; + case ISC_R_SUCCESS: + /* + * Stop the timer on the successful message read, this + * also allows to restart the timer when we have no more + * data. + */ + isc__nmsocket_timer_stop(sock); + + if (atomic_load(&sock->client) || + atomic_load(&sock->sequential) || + ah >= STREAM_CLIENTS_PER_CONN) + { + isc__nm_stop_reading(sock); + goto done; + } + break; + default: + UNREACHABLE(); + } + } +done: + return (ISC_R_SUCCESS); +} + +void +isc__nm_resume_processing(void *arg) { + isc_nmsocket_t *sock = (isc_nmsocket_t *)arg; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + REQUIRE(!atomic_load(&sock->client)); + + if (isc__nmsocket_closing(sock)) { + return; + } + + isc__nm_process_sock_buffer(sock); +} + +void +isc_nmhandle_cleartimeout(isc_nmhandle_t *handle) { + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + + switch (handle->sock->type) { + default: + handle->sock->read_timeout = 0; + + if (uv_is_active((uv_handle_t *)&handle->sock->read_timer)) { + isc__nmsocket_timer_stop(handle->sock); + } + } +} + +void +isc_nmhandle_settimeout(isc_nmhandle_t *handle, uint32_t timeout) { + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + + switch (handle->sock->type) { + default: + handle->sock->read_timeout = timeout; + isc__nmsocket_timer_restart(handle->sock); + } +} + +void +isc_nmhandle_keepalive(isc_nmhandle_t *handle, bool value) { + isc_nmsocket_t *sock = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + + sock = handle->sock; + + switch (sock->type) { + case isc_nm_tcpsocket: + case isc_nm_tcpdnssocket: + atomic_store(&sock->keepalive, value); + sock->read_timeout = value ? atomic_load(&sock->mgr->keepalive) + : atomic_load(&sock->mgr->idle); + sock->write_timeout = value ? atomic_load(&sock->mgr->keepalive) + : atomic_load(&sock->mgr->idle); + break; + default: + /* + * For any other protocol, this is a no-op. + */ + return; + } +} + +void * +isc_nmhandle_getextra(isc_nmhandle_t *handle) { + REQUIRE(VALID_NMHANDLE(handle)); + + return (handle->extra); +} + +isc_sockaddr_t +isc_nmhandle_peeraddr(isc_nmhandle_t *handle) { + REQUIRE(VALID_NMHANDLE(handle)); + + return (handle->peer); +} + +isc_sockaddr_t +isc_nmhandle_localaddr(isc_nmhandle_t *handle) { + REQUIRE(VALID_NMHANDLE(handle)); + + return (handle->local); +} + +isc_nm_t * +isc_nmhandle_netmgr(isc_nmhandle_t *handle) { + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + + return (handle->sock->mgr); +} + +isc__nm_uvreq_t * +isc___nm_uvreq_get(isc_nm_t *mgr, isc_nmsocket_t *sock FLARG) { + isc__nm_uvreq_t *req = NULL; + + REQUIRE(VALID_NM(mgr)); + REQUIRE(VALID_NMSOCK(sock)); + + if (sock != NULL && isc__nmsocket_active(sock)) { + /* Try to reuse one */ + req = isc_astack_pop(sock->inactivereqs); + } + + if (req == NULL) { + req = isc_mem_get(mgr->mctx, sizeof(*req)); + } + + *req = (isc__nm_uvreq_t){ .magic = 0 }; + ISC_LINK_INIT(req, link); + req->uv_req.req.data = req; + isc___nmsocket_attach(sock, &req->sock FLARG_PASS); + req->magic = UVREQ_MAGIC; + + return (req); +} + +void +isc___nm_uvreq_put(isc__nm_uvreq_t **req0, isc_nmsocket_t *sock FLARG) { + isc__nm_uvreq_t *req = NULL; + isc_nmhandle_t *handle = NULL; + + REQUIRE(req0 != NULL); + REQUIRE(VALID_UVREQ(*req0)); + + req = *req0; + *req0 = NULL; + + INSIST(sock == req->sock); + + req->magic = 0; + + /* + * We need to save this first to make sure that handle, + * sock, and the netmgr won't all disappear. + */ + handle = req->handle; + req->handle = NULL; + +#if !__SANITIZE_ADDRESS__ && !__SANITIZE_THREAD__ + if (!isc__nmsocket_active(sock) || + !isc_astack_trypush(sock->inactivereqs, req)) + { + isc_mem_put(sock->mgr->mctx, req, sizeof(*req)); + } +#else /* !__SANITIZE_ADDRESS__ && !__SANITIZE_THREAD__ */ + isc_mem_put(sock->mgr->mctx, req, sizeof(*req)); +#endif /* !__SANITIZE_ADDRESS__ && !__SANITIZE_THREAD__ */ + + if (handle != NULL) { + isc__nmhandle_detach(&handle FLARG_PASS); + } + + isc___nmsocket_detach(&sock FLARG_PASS); +} + +void +isc_nm_send(isc_nmhandle_t *handle, isc_region_t *region, isc_nm_cb_t cb, + void *cbarg) { + REQUIRE(VALID_NMHANDLE(handle)); + + switch (handle->sock->type) { + case isc_nm_udpsocket: + case isc_nm_udplistener: + isc__nm_udp_send(handle, region, cb, cbarg); + break; + case isc_nm_tcpsocket: + isc__nm_tcp_send(handle, region, cb, cbarg); + break; + case isc_nm_tcpdnssocket: + isc__nm_tcpdns_send(handle, region, cb, cbarg); + break; + default: + UNREACHABLE(); + } +} + +void +isc_nm_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg) { + REQUIRE(VALID_NMHANDLE(handle)); + + /* + * This is always called via callback (from accept or connect), and + * caller must attach to the handle, so the references always need to be + * at least 2. + */ + REQUIRE(isc_refcount_current(&handle->references) >= 2); + + switch (handle->sock->type) { + case isc_nm_udpsocket: + isc__nm_udp_read(handle, cb, cbarg); + break; + case isc_nm_tcpsocket: + isc__nm_tcp_read(handle, cb, cbarg); + break; + case isc_nm_tcpdnssocket: + isc__nm_tcpdns_read(handle, cb, cbarg); + break; + default: + UNREACHABLE(); + } +} + +void +isc_nm_cancelread(isc_nmhandle_t *handle) { + REQUIRE(VALID_NMHANDLE(handle)); + + switch (handle->sock->type) { + case isc_nm_udpsocket: + isc__nm_udp_cancelread(handle); + break; + case isc_nm_tcpsocket: + isc__nm_tcp_cancelread(handle); + break; + case isc_nm_tcpdnssocket: + isc__nm_tcpdns_cancelread(handle); + break; + default: + UNREACHABLE(); + } +} + +void +isc_nm_pauseread(isc_nmhandle_t *handle) { + REQUIRE(VALID_NMHANDLE(handle)); + + isc_nmsocket_t *sock = handle->sock; + + switch (sock->type) { + case isc_nm_tcpsocket: + isc__nm_tcp_pauseread(handle); + break; + default: + UNREACHABLE(); + } +} + +void +isc_nm_resumeread(isc_nmhandle_t *handle) { + REQUIRE(VALID_NMHANDLE(handle)); + + isc_nmsocket_t *sock = handle->sock; + + switch (sock->type) { + case isc_nm_tcpsocket: + isc__nm_tcp_resumeread(handle); + break; + default: + UNREACHABLE(); + } +} + +void +isc_nm_stoplistening(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + + switch (sock->type) { + case isc_nm_udplistener: + isc__nm_udp_stoplistening(sock); + break; + case isc_nm_tcpdnslistener: + isc__nm_tcpdns_stoplistening(sock); + break; + case isc_nm_tcplistener: + isc__nm_tcp_stoplistening(sock); + break; + default: + UNREACHABLE(); + } +} + +void +isc__nm_connectcb(isc_nmsocket_t *sock, isc__nm_uvreq_t *uvreq, + isc_result_t eresult, bool async) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(VALID_UVREQ(uvreq)); + REQUIRE(VALID_NMHANDLE(uvreq->handle)); + + if (!async) { + isc__netievent_connectcb_t ievent = { .sock = sock, + .req = uvreq, + .result = eresult }; + isc__nm_async_connectcb(NULL, (isc__netievent_t *)&ievent); + } else { + isc__netievent_connectcb_t *ievent = + isc__nm_get_netievent_connectcb(sock->mgr, sock, uvreq, + eresult); + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); + } +} + +void +isc__nm_async_connectcb(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_connectcb_t *ievent = (isc__netievent_connectcb_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + isc__nm_uvreq_t *uvreq = ievent->req; + isc_result_t eresult = ievent->result; + + UNUSED(worker); + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(VALID_UVREQ(uvreq)); + REQUIRE(VALID_NMHANDLE(uvreq->handle)); + REQUIRE(ievent->sock->tid == isc_nm_tid()); + REQUIRE(uvreq->cb.connect != NULL); + + uvreq->cb.connect(uvreq->handle, eresult, uvreq->cbarg); + + isc__nm_uvreq_put(&uvreq, sock); +} + +void +isc__nm_readcb(isc_nmsocket_t *sock, isc__nm_uvreq_t *uvreq, + isc_result_t eresult) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(VALID_UVREQ(uvreq)); + REQUIRE(VALID_NMHANDLE(uvreq->handle)); + + if (eresult == ISC_R_SUCCESS || eresult == ISC_R_TIMEDOUT) { + isc__netievent_readcb_t ievent = { .sock = sock, + .req = uvreq, + .result = eresult }; + + isc__nm_async_readcb(NULL, (isc__netievent_t *)&ievent); + } else { + isc__netievent_readcb_t *ievent = isc__nm_get_netievent_readcb( + sock->mgr, sock, uvreq, eresult); + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); + } +} + +void +isc__nm_async_readcb(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_readcb_t *ievent = (isc__netievent_readcb_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + isc__nm_uvreq_t *uvreq = ievent->req; + isc_result_t eresult = ievent->result; + isc_region_t region; + + UNUSED(worker); + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(VALID_UVREQ(uvreq)); + REQUIRE(VALID_NMHANDLE(uvreq->handle)); + REQUIRE(sock->tid == isc_nm_tid()); + + region.base = (unsigned char *)uvreq->uvbuf.base; + region.length = uvreq->uvbuf.len; + + uvreq->cb.recv(uvreq->handle, eresult, ®ion, uvreq->cbarg); + + isc__nm_uvreq_put(&uvreq, sock); +} + +void +isc__nm_sendcb(isc_nmsocket_t *sock, isc__nm_uvreq_t *uvreq, + isc_result_t eresult, bool async) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(VALID_UVREQ(uvreq)); + REQUIRE(VALID_NMHANDLE(uvreq->handle)); + + if (!async) { + isc__netievent_sendcb_t ievent = { .sock = sock, + .req = uvreq, + .result = eresult }; + isc__nm_async_sendcb(NULL, (isc__netievent_t *)&ievent); + return; + } + + isc__netievent_sendcb_t *ievent = + isc__nm_get_netievent_sendcb(sock->mgr, sock, uvreq, eresult); + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); +} + +void +isc__nm_async_sendcb(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_sendcb_t *ievent = (isc__netievent_sendcb_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + isc__nm_uvreq_t *uvreq = ievent->req; + isc_result_t eresult = ievent->result; + + UNUSED(worker); + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(VALID_UVREQ(uvreq)); + REQUIRE(VALID_NMHANDLE(uvreq->handle)); + REQUIRE(sock->tid == isc_nm_tid()); + + uvreq->cb.send(uvreq->handle, eresult, uvreq->cbarg); + + isc__nm_uvreq_put(&uvreq, sock); +} + +static void +isc__nm_async_close(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_close_t *ievent = (isc__netievent_close_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + + REQUIRE(VALID_NMSOCK(ievent->sock)); + REQUIRE(sock->tid == isc_nm_tid()); + REQUIRE(sock->closehandle_cb != NULL); + + UNUSED(worker); + + ievent->sock->closehandle_cb(sock); +} + +void +isc__nm_async_detach(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_detach_t *ievent = (isc__netievent_detach_t *)ev0; + FLARG_IEVENT(ievent); + + REQUIRE(VALID_NMSOCK(ievent->sock)); + REQUIRE(VALID_NMHANDLE(ievent->handle)); + REQUIRE(ievent->sock->tid == isc_nm_tid()); + + UNUSED(worker); + + nmhandle_detach_cb(&ievent->handle FLARG_PASS); +} + +static void +reset_shutdown(uv_handle_t *handle) { + isc_nmsocket_t *sock = uv_handle_get_data(handle); + + isc__nmsocket_shutdown(sock); + isc__nmsocket_detach(&sock); +} + +void +isc__nmsocket_reset(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + + switch (sock->type) { + case isc_nm_tcpsocket: + case isc_nm_tcpdnssocket: + /* + * This can be called from the TCP write timeout. + */ + REQUIRE(sock->parent == NULL); + break; + default: + UNREACHABLE(); + break; + } + + if (!uv_is_closing(&sock->uv_handle.handle) && + uv_is_active(&sock->uv_handle.handle)) + { + /* + * The real shutdown will be handled in the respective + * close functions. + */ + isc__nmsocket_attach(sock, &(isc_nmsocket_t *){ NULL }); + int r = uv_tcp_close_reset(&sock->uv_handle.tcp, + reset_shutdown); + UV_RUNTIME_CHECK(uv_tcp_close_reset, r); + } else { + isc__nmsocket_shutdown(sock); + } +} + +void +isc__nmsocket_shutdown(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + switch (sock->type) { + case isc_nm_udpsocket: + isc__nm_udp_shutdown(sock); + break; + case isc_nm_tcpsocket: + isc__nm_tcp_shutdown(sock); + break; + case isc_nm_tcpdnssocket: + isc__nm_tcpdns_shutdown(sock); + break; + case isc_nm_udplistener: + case isc_nm_tcplistener: + case isc_nm_tcpdnslistener: + return; + default: + UNREACHABLE(); + } +} + +static void +shutdown_walk_cb(uv_handle_t *handle, void *arg) { + isc_nmsocket_t *sock = uv_handle_get_data(handle); + UNUSED(arg); + + if (uv_is_closing(handle)) { + return; + } + + switch (handle->type) { + case UV_UDP: + isc__nmsocket_shutdown(sock); + return; + case UV_TCP: + switch (sock->type) { + case isc_nm_tcpsocket: + case isc_nm_tcpdnssocket: + if (sock->parent == NULL) { + /* Reset the TCP connections on shutdown */ + isc__nmsocket_reset(sock); + return; + } + FALLTHROUGH; + default: + isc__nmsocket_shutdown(sock); + } + + return; + default: + return; + } +} + +void +isc__nm_async_shutdown(isc__networker_t *worker, isc__netievent_t *ev0) { + UNUSED(ev0); + + uv_walk(&worker->loop, shutdown_walk_cb, NULL); +} + +bool +isc__nm_acquire_interlocked(isc_nm_t *mgr) { + if (!isc__nm_in_netthread()) { + return (false); + } + + LOCK(&mgr->lock); + bool success = atomic_compare_exchange_strong( + &mgr->interlocked, &(int){ ISC_NETMGR_NON_INTERLOCKED }, + isc_nm_tid()); + + UNLOCK(&mgr->lock); + return (success); +} + +void +isc__nm_drop_interlocked(isc_nm_t *mgr) { + if (!isc__nm_in_netthread()) { + return; + } + + LOCK(&mgr->lock); + int tid = atomic_exchange(&mgr->interlocked, + ISC_NETMGR_NON_INTERLOCKED); + INSIST(tid != ISC_NETMGR_NON_INTERLOCKED); + BROADCAST(&mgr->wkstatecond); + UNLOCK(&mgr->lock); +} + +void +isc__nm_acquire_interlocked_force(isc_nm_t *mgr) { + if (!isc__nm_in_netthread()) { + return; + } + + LOCK(&mgr->lock); + while (!atomic_compare_exchange_strong( + &mgr->interlocked, &(int){ ISC_NETMGR_NON_INTERLOCKED }, + isc_nm_tid())) + { + WAIT(&mgr->wkstatecond, &mgr->lock); + } + UNLOCK(&mgr->lock); +} + +void +isc_nm_setstats(isc_nm_t *mgr, isc_stats_t *stats) { + REQUIRE(VALID_NM(mgr)); + REQUIRE(mgr->stats == NULL); + REQUIRE(isc_stats_ncounters(stats) == isc_sockstatscounter_max); + + isc_stats_attach(stats, &mgr->stats); +} + +void +isc__nm_incstats(isc_nm_t *mgr, isc_statscounter_t counterid) { + REQUIRE(VALID_NM(mgr)); + REQUIRE(counterid != -1); + + if (mgr->stats != NULL) { + isc_stats_increment(mgr->stats, counterid); + } +} + +void +isc__nm_decstats(isc_nm_t *mgr, isc_statscounter_t counterid) { + REQUIRE(VALID_NM(mgr)); + REQUIRE(counterid != -1); + + if (mgr->stats != NULL) { + isc_stats_decrement(mgr->stats, counterid); + } +} + +isc_result_t +isc__nm_socket(int domain, int type, int protocol, uv_os_sock_t *sockp) { +#ifdef WIN32 + SOCKET sock; + sock = socket(domain, type, protocol); + if (sock == INVALID_SOCKET) { + char strbuf[ISC_STRERRORSIZE]; + DWORD socket_errno = WSAGetLastError(); + switch (socket_errno) { + case WSAEMFILE: + case WSAENOBUFS: + return (ISC_R_NORESOURCES); + + case WSAEPROTONOSUPPORT: + case WSAEPFNOSUPPORT: + case WSAEAFNOSUPPORT: + return (ISC_R_FAMILYNOSUPPORT); + default: + strerror_r(socket_errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR( + __FILE__, __LINE__, + "socket() failed with error code %lu: %s", + socket_errno, strbuf); + return (ISC_R_UNEXPECTED); + } + } +#else + int sock = socket(domain, type, protocol); + if (sock < 0) { + return (isc_errno_toresult(errno)); + } +#endif + *sockp = (uv_os_sock_t)sock; + return (ISC_R_SUCCESS); +} + +void +isc__nm_closesocket(uv_os_sock_t sock) { +#ifdef WIN32 + closesocket(sock); +#else + close(sock); +#endif +} + +#define setsockopt_on(socket, level, name) \ + setsockopt(socket, level, name, &(int){ 1 }, sizeof(int)) + +#define setsockopt_off(socket, level, name) \ + setsockopt(socket, level, name, &(int){ 0 }, sizeof(int)) + +isc_result_t +isc__nm_socket_freebind(uv_os_sock_t fd, sa_family_t sa_family) { + /* + * Set the IP_FREEBIND (or equivalent option) on the uv_handle. + */ +#ifdef IP_FREEBIND + UNUSED(sa_family); + if (setsockopt_on(fd, IPPROTO_IP, IP_FREEBIND) == -1) { + return (ISC_R_FAILURE); + } + return (ISC_R_SUCCESS); +#elif defined(IP_BINDANY) || defined(IPV6_BINDANY) + if (sa_family == AF_INET) { +#if defined(IP_BINDANY) + if (setsockopt_on(fd, IPPROTO_IP, IP_BINDANY) == -1) { + return (ISC_R_FAILURE); + } + return (ISC_R_SUCCESS); +#endif + } else if (sa_family == AF_INET6) { +#if defined(IPV6_BINDANY) + if (setsockopt_on(fd, IPPROTO_IPV6, IPV6_BINDANY) == -1) { + return (ISC_R_FAILURE); + } + return (ISC_R_SUCCESS); +#endif + } + return (ISC_R_NOTIMPLEMENTED); +#elif defined(SO_BINDANY) + UNUSED(sa_family); + if (setsockopt_on(fd, SOL_SOCKET, SO_BINDANY) == -1) { + return (ISC_R_FAILURE); + } + return (ISC_R_SUCCESS); +#else + UNUSED(fd); + UNUSED(sa_family); + return (ISC_R_NOTIMPLEMENTED); +#endif +} + +isc_result_t +isc__nm_socket_reuse(uv_os_sock_t fd) { + /* + * Generally, the SO_REUSEADDR socket option allows reuse of + * local addresses. + * + * On the BSDs, SO_REUSEPORT implies SO_REUSEADDR but with some + * additional refinements for programs that use multicast. + * + * On Linux, SO_REUSEPORT has different semantics: it _shares_ the port + * rather than steal it from the current listener, so we don't use it + * here, but rather in isc__nm_socket_reuse_lb(). + * + * On Windows, it also allows a socket to forcibly bind to a port in use + * by another socket. + */ + +#if defined(SO_REUSEPORT) && !defined(__linux__) + if (setsockopt_on(fd, SOL_SOCKET, SO_REUSEPORT) == -1) { + return (ISC_R_FAILURE); + } + return (ISC_R_SUCCESS); +#elif defined(SO_REUSEADDR) + if (setsockopt_on(fd, SOL_SOCKET, SO_REUSEADDR) == -1) { + return (ISC_R_FAILURE); + } + return (ISC_R_SUCCESS); +#else + UNUSED(fd); + return (ISC_R_NOTIMPLEMENTED); +#endif +} + +isc_result_t +isc__nm_socket_reuse_lb(uv_os_sock_t fd) { + /* + * On FreeBSD 12+, SO_REUSEPORT_LB socket option allows sockets to be + * bound to an identical socket address. For UDP sockets, the use of + * this option can provide better distribution of incoming datagrams to + * multiple processes (or threads) as compared to the traditional + * technique of having multiple processes compete to receive datagrams + * on the same socket. + * + * On Linux, the same thing is achieved simply with SO_REUSEPORT. + */ +#if defined(SO_REUSEPORT_LB) + if (setsockopt_on(fd, SOL_SOCKET, SO_REUSEPORT_LB) == -1) { + return (ISC_R_FAILURE); + } else { + return (ISC_R_SUCCESS); + } +#elif defined(SO_REUSEPORT) && defined(__linux__) + if (setsockopt_on(fd, SOL_SOCKET, SO_REUSEPORT) == -1) { + return (ISC_R_FAILURE); + } else { + return (ISC_R_SUCCESS); + } +#else + UNUSED(fd); + return (ISC_R_NOTIMPLEMENTED); +#endif +} + +isc_result_t +isc__nm_socket_incoming_cpu(uv_os_sock_t fd) { +#ifdef SO_INCOMING_CPU + if (setsockopt_on(fd, SOL_SOCKET, SO_INCOMING_CPU) == -1) { + return (ISC_R_FAILURE); + } else { + return (ISC_R_SUCCESS); + } +#else + UNUSED(fd); +#endif + return (ISC_R_NOTIMPLEMENTED); +} + +isc_result_t +isc__nm_socket_disable_pmtud(uv_os_sock_t fd, sa_family_t sa_family) { + /* + * Disable the Path MTU Discovery on IP packets + */ + if (sa_family == AF_INET6) { +#if defined(IPV6_DONTFRAG) + if (setsockopt_off(fd, IPPROTO_IPV6, IPV6_DONTFRAG) == -1) { + return (ISC_R_FAILURE); + } else { + return (ISC_R_SUCCESS); + } +#elif defined(IPV6_MTU_DISCOVER) && defined(IP_PMTUDISC_OMIT) + if (setsockopt(fd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, + &(int){ IP_PMTUDISC_OMIT }, sizeof(int)) == -1) + { + return (ISC_R_FAILURE); + } else { + return (ISC_R_SUCCESS); + } +#else + UNUSED(fd); +#endif + } else if (sa_family == AF_INET) { +#if defined(IP_DONTFRAG) + if (setsockopt_off(fd, IPPROTO_IP, IP_DONTFRAG) == -1) { + return (ISC_R_FAILURE); + } else { + return (ISC_R_SUCCESS); + } +#elif defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_OMIT) + if (setsockopt(fd, IPPROTO_IP, IP_MTU_DISCOVER, + &(int){ IP_PMTUDISC_OMIT }, sizeof(int)) == -1) + { + return (ISC_R_FAILURE); + } else { + return (ISC_R_SUCCESS); + } +#else + UNUSED(fd); +#endif + } else { + return (ISC_R_FAMILYNOSUPPORT); + } + + return (ISC_R_NOTIMPLEMENTED); +} + +#if defined(_WIN32) +#define TIMEOUT_TYPE DWORD +#define TIMEOUT_DIV 1000 +#define TIMEOUT_OPTNAME TCP_MAXRT +#elif defined(TCP_CONNECTIONTIMEOUT) +#define TIMEOUT_TYPE int +#define TIMEOUT_DIV 1000 +#define TIMEOUT_OPTNAME TCP_CONNECTIONTIMEOUT +#elif defined(TCP_RXT_CONNDROPTIME) +#define TIMEOUT_TYPE int +#define TIMEOUT_DIV 1000 +#define TIMEOUT_OPTNAME TCP_RXT_CONNDROPTIME +#elif defined(TCP_USER_TIMEOUT) +#define TIMEOUT_TYPE unsigned int +#define TIMEOUT_DIV 1 +#define TIMEOUT_OPTNAME TCP_USER_TIMEOUT +#elif defined(TCP_KEEPINIT) +#define TIMEOUT_TYPE int +#define TIMEOUT_DIV 1000 +#define TIMEOUT_OPTNAME TCP_KEEPINIT +#endif + +isc_result_t +isc__nm_socket_connectiontimeout(uv_os_sock_t fd, int timeout_ms) { +#if defined(TIMEOUT_OPTNAME) + TIMEOUT_TYPE timeout = timeout_ms / TIMEOUT_DIV; + + if (timeout == 0) { + timeout = 1; + } + + if (setsockopt(fd, IPPROTO_TCP, TIMEOUT_OPTNAME, &timeout, + sizeof(timeout)) == -1) + { + return (ISC_R_FAILURE); + } + + return (ISC_R_SUCCESS); +#else + UNUSED(fd); + UNUSED(timeout_ms); + + return (ISC_R_SUCCESS); +#endif +} + +isc_result_t +isc__nm_socket_tcp_nodelay(uv_os_sock_t fd) { +#ifdef TCP_NODELAY + if (setsockopt_on(fd, IPPROTO_TCP, TCP_NODELAY) == -1) { + return (ISC_R_FAILURE); + } else { + return (ISC_R_SUCCESS); + } +#else + UNUSED(fd); + return (ISC_R_SUCCESS); +#endif +} + +static isc_threadresult_t +isc__nm_work_run(isc_threadarg_t arg) { + isc__nm_work_t *work = (isc__nm_work_t *)arg; + + work->cb(work->data); + + return ((isc_threadresult_t)0); +} + +static void +isc__nm_work_cb(uv_work_t *req) { + isc__nm_work_t *work = uv_req_get_data((uv_req_t *)req); + + if (isc_tid_v == SIZE_MAX) { + isc__trampoline_t *trampoline_arg = + isc__trampoline_get(isc__nm_work_run, work); + (void)isc__trampoline_run(trampoline_arg); + } else { + (void)isc__nm_work_run((isc_threadarg_t)work); + } +} + +static void +isc__nm_after_work_cb(uv_work_t *req, int status) { + isc_result_t result = ISC_R_SUCCESS; + isc__nm_work_t *work = uv_req_get_data((uv_req_t *)req); + isc_nm_t *netmgr = work->netmgr; + + if (status != 0) { + result = isc__nm_uverr2result(status); + } + + work->after_cb(work->data, result); + + isc_mem_put(netmgr->mctx, work, sizeof(*work)); + + isc_nm_detach(&netmgr); +} + +void +isc_nm_work_offload(isc_nm_t *netmgr, isc_nm_workcb_t work_cb, + isc_nm_after_workcb_t after_work_cb, void *data) { + isc__networker_t *worker = NULL; + isc__nm_work_t *work = NULL; + int r; + + REQUIRE(isc__nm_in_netthread()); + REQUIRE(VALID_NM(netmgr)); + + worker = &netmgr->workers[isc_nm_tid()]; + + work = isc_mem_get(netmgr->mctx, sizeof(*work)); + *work = (isc__nm_work_t){ + .cb = work_cb, + .after_cb = after_work_cb, + .data = data, + }; + + isc_nm_attach(netmgr, &work->netmgr); + + uv_req_set_data((uv_req_t *)&work->req, work); + + r = uv_queue_work(&worker->loop, &work->req, isc__nm_work_cb, + isc__nm_after_work_cb); + UV_RUNTIME_CHECK(uv_queue_work, r); +} + +void +isc_nm_timer_create(isc_nmhandle_t *handle, isc_nm_timer_cb cb, void *cbarg, + isc_nm_timer_t **timerp) { + isc__networker_t *worker = NULL; + isc_nmsocket_t *sock = NULL; + isc_nm_timer_t *timer = NULL; + int r; + + REQUIRE(isc__nm_in_netthread()); + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + + sock = handle->sock; + worker = &sock->mgr->workers[isc_nm_tid()]; + + timer = isc_mem_get(sock->mgr->mctx, sizeof(*timer)); + *timer = (isc_nm_timer_t){ .cb = cb, .cbarg = cbarg }; + isc_refcount_init(&timer->references, 1); + isc_nmhandle_attach(handle, &timer->handle); + + r = uv_timer_init(&worker->loop, &timer->timer); + UV_RUNTIME_CHECK(uv_timer_init, r); + + uv_handle_set_data((uv_handle_t *)&timer->timer, timer); + + *timerp = timer; +} + +void +isc_nm_timer_attach(isc_nm_timer_t *timer, isc_nm_timer_t **timerp) { + REQUIRE(timer != NULL); + REQUIRE(timerp != NULL && *timerp == NULL); + + isc_refcount_increment(&timer->references); + *timerp = timer; +} + +static void +timer_destroy(uv_handle_t *uvhandle) { + isc_nm_timer_t *timer = uv_handle_get_data(uvhandle); + isc_nmhandle_t *handle = timer->handle; + isc_mem_t *mctx = timer->handle->sock->mgr->mctx; + + isc_mem_put(mctx, timer, sizeof(*timer)); + + isc_nmhandle_detach(&handle); +} + +void +isc_nm_timer_detach(isc_nm_timer_t **timerp) { + isc_nm_timer_t *timer = NULL; + isc_nmhandle_t *handle = NULL; + + REQUIRE(timerp != NULL && *timerp != NULL); + + timer = *timerp; + *timerp = NULL; + + handle = timer->handle; + + REQUIRE(isc__nm_in_netthread()); + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + + if (isc_refcount_decrement(&timer->references) == 1) { + int r = uv_timer_stop(&timer->timer); + UV_RUNTIME_CHECK(uv_timer_stop, r); + uv_close((uv_handle_t *)&timer->timer, timer_destroy); + } +} + +static void +timer_cb(uv_timer_t *uvtimer) { + isc_nm_timer_t *timer = uv_handle_get_data((uv_handle_t *)uvtimer); + + REQUIRE(timer->cb != NULL); + + timer->cb(timer->cbarg, ISC_R_TIMEDOUT); +} + +void +isc_nm_timer_start(isc_nm_timer_t *timer, uint64_t timeout) { + int r = uv_timer_start(&timer->timer, timer_cb, timeout, 0); + UV_RUNTIME_CHECK(uv_timer_start, r); +} + +void +isc_nm_timer_stop(isc_nm_timer_t *timer) { + int r = uv_timer_stop(&timer->timer); + UV_RUNTIME_CHECK(uv_timer_stop, r); +} + +#ifdef NETMGR_TRACE +/* + * Dump all active sockets in netmgr. We output to stderr + * as the logger might be already shut down. + */ + +static const char * +nmsocket_type_totext(isc_nmsocket_type type) { + switch (type) { + case isc_nm_udpsocket: + return ("isc_nm_udpsocket"); + case isc_nm_udplistener: + return ("isc_nm_udplistener"); + case isc_nm_tcpsocket: + return ("isc_nm_tcpsocket"); + case isc_nm_tcplistener: + return ("isc_nm_tcplistener"); + case isc_nm_tcpdnslistener: + return ("isc_nm_tcpdnslistener"); + case isc_nm_tcpdnssocket: + return ("isc_nm_tcpdnssocket"); + default: + UNREACHABLE(); + } +} + +static void +nmhandle_dump(isc_nmhandle_t *handle) { + fprintf(stderr, "Active handle %p, refs %" PRIuFAST32 "\n", handle, + isc_refcount_current(&handle->references)); + fprintf(stderr, "Created by:\n"); + backtrace_symbols_fd(handle->backtrace, handle->backtrace_size, + STDERR_FILENO); + fprintf(stderr, "\n\n"); +} + +static void +nmsocket_dump(isc_nmsocket_t *sock) { + isc_nmhandle_t *handle = NULL; + + LOCK(&sock->lock); + fprintf(stderr, "\n=================\n"); + fprintf(stderr, "Active %s socket %p, type %s, refs %" PRIuFAST32 "\n", + atomic_load(&sock->client) ? "client" : "server", sock, + nmsocket_type_totext(sock->type), + isc_refcount_current(&sock->references)); + fprintf(stderr, + "Parent %p, listener %p, server %p, statichandle = " + "%p\n", + sock->parent, sock->listener, sock->server, sock->statichandle); + fprintf(stderr, "Flags:%s%s%s%s%s\n", + atomic_load(&sock->active) ? " active" : "", + atomic_load(&sock->closing) ? " closing" : "", + atomic_load(&sock->destroying) ? " destroying" : "", + atomic_load(&sock->connecting) ? " connecting" : "", + sock->accepting ? " accepting" : ""); + fprintf(stderr, "Created by:\n"); + backtrace_symbols_fd(sock->backtrace, sock->backtrace_size, + STDERR_FILENO); + fprintf(stderr, "\n"); + + for (handle = ISC_LIST_HEAD(sock->active_handles); handle != NULL; + handle = ISC_LIST_NEXT(handle, active_link)) + { + static bool first = true; + if (first) { + fprintf(stderr, "Active handles:\n"); + first = false; + } + nmhandle_dump(handle); + } + + fprintf(stderr, "\n"); + UNLOCK(&sock->lock); +} + +void +isc__nm_dump_active(isc_nm_t *nm) { + isc_nmsocket_t *sock = NULL; + + REQUIRE(VALID_NM(nm)); + + LOCK(&nm->lock); + for (sock = ISC_LIST_HEAD(nm->active_sockets); sock != NULL; + sock = ISC_LIST_NEXT(sock, active_link)) + { + static bool first = true; + if (first) { + fprintf(stderr, "Outstanding sockets\n"); + first = false; + } + nmsocket_dump(sock); + } + UNLOCK(&nm->lock); +} +#endif diff --git a/lib/isc/netmgr/tcp.c b/lib/isc/netmgr/tcp.c new file mode 100644 index 0000000..821d6c4 --- /dev/null +++ b/lib/isc/netmgr/tcp.c @@ -0,0 +1,1456 @@ +/* + * 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. + */ + +#include <libgen.h> +#include <unistd.h> +#include <uv.h> + +#include <isc/atomic.h> +#include <isc/barrier.h> +#include <isc/buffer.h> +#include <isc/condition.h> +#include <isc/errno.h> +#include <isc/log.h> +#include <isc/magic.h> +#include <isc/mem.h> +#include <isc/netmgr.h> +#include <isc/quota.h> +#include <isc/random.h> +#include <isc/refcount.h> +#include <isc/region.h> +#include <isc/result.h> +#include <isc/sockaddr.h> +#include <isc/stdtime.h> +#include <isc/thread.h> +#include <isc/util.h> + +#include "netmgr-int.h" +#include "uv-compat.h" + +static atomic_uint_fast32_t last_tcpquota_log = 0; + +static bool +can_log_tcp_quota(void) { + isc_stdtime_t now, last; + + isc_stdtime_get(&now); + last = atomic_exchange_relaxed(&last_tcpquota_log, now); + if (now != last) { + return (true); + } + + return (false); +} + +static isc_result_t +tcp_connect_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req); + +static void +tcp_close_direct(isc_nmsocket_t *sock); + +static isc_result_t +tcp_send_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req); +static void +tcp_connect_cb(uv_connect_t *uvreq, int status); + +static void +tcp_connection_cb(uv_stream_t *server, int status); + +static void +tcp_close_cb(uv_handle_t *uvhandle); + +static isc_result_t +accept_connection(isc_nmsocket_t *ssock, isc_quota_t *quota); + +static void +quota_accept_cb(isc_quota_t *quota, void *sock0); + +static void +failed_accept_cb(isc_nmsocket_t *sock, isc_result_t eresult); + +static void +stop_tcp_parent(isc_nmsocket_t *sock); +static void +stop_tcp_child(isc_nmsocket_t *sock); + +static void +failed_accept_cb(isc_nmsocket_t *sock, isc_result_t eresult) { + REQUIRE(sock->accepting); + REQUIRE(sock->server); + + /* + * Detach the quota early to make room for other connections; + * otherwise it'd be detached later asynchronously, and clog + * the quota unnecessarily. + */ + if (sock->quota != NULL) { + isc_quota_detach(&sock->quota); + } + + isc__nmsocket_detach(&sock->server); + + sock->accepting = false; + + switch (eresult) { + case ISC_R_NOTCONNECTED: + /* IGNORE: The client disconnected before we could accept */ + break; + default: + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_NETMGR, ISC_LOG_ERROR, + "Accepting TCP connection failed: %s", + isc_result_totext(eresult)); + } +} + +static isc_result_t +tcp_connect_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req) { + isc__networker_t *worker = NULL; + isc_result_t result = ISC_R_UNSET; + int r; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(VALID_UVREQ(req)); + + REQUIRE(isc__nm_in_netthread()); + REQUIRE(sock->tid == isc_nm_tid()); + + worker = &sock->mgr->workers[sock->tid]; + + atomic_store(&sock->connecting, true); + + /* 2 minute timeout */ + result = isc__nm_socket_connectiontimeout(sock->fd, 120 * 1000); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + r = uv_tcp_init(&worker->loop, &sock->uv_handle.tcp); + UV_RUNTIME_CHECK(uv_tcp_init, r); + uv_handle_set_data(&sock->uv_handle.handle, sock); + + r = uv_timer_init(&worker->loop, &sock->read_timer); + UV_RUNTIME_CHECK(uv_timer_init, r); + uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock); + + r = uv_tcp_open(&sock->uv_handle.tcp, sock->fd); + if (r != 0) { + isc__nm_closesocket(sock->fd); + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_OPENFAIL]); + goto done; + } + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_OPEN]); + + if (req->local.length != 0) { + r = uv_tcp_bind(&sock->uv_handle.tcp, &req->local.type.sa, 0); + if (r != 0) { + isc__nm_incstats(sock->mgr, + sock->statsindex[STATID_BINDFAIL]); + goto done; + } + } + + uv_handle_set_data(&req->uv_req.handle, req); + r = uv_tcp_connect(&req->uv_req.connect, &sock->uv_handle.tcp, + &req->peer.type.sa, tcp_connect_cb); + if (r != 0) { + isc__nm_incstats(sock->mgr, + sock->statsindex[STATID_CONNECTFAIL]); + goto done; + } + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_CONNECT]); + + uv_handle_set_data((uv_handle_t *)&sock->read_timer, + &req->uv_req.connect); + isc__nmsocket_timer_start(sock); + + atomic_store(&sock->connected, true); + +done: + result = isc__nm_uverr2result(r); + LOCK(&sock->lock); + sock->result = result; + SIGNAL(&sock->cond); + if (!atomic_load(&sock->active)) { + WAIT(&sock->scond, &sock->lock); + } + INSIST(atomic_load(&sock->active)); + UNLOCK(&sock->lock); + + return (result); +} + +void +isc__nm_async_tcpconnect(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_tcpconnect_t *ievent = + (isc__netievent_tcpconnect_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + isc__nm_uvreq_t *req = ievent->req; + isc_result_t result = ISC_R_SUCCESS; + + UNUSED(worker); + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->type == isc_nm_tcpsocket); + REQUIRE(sock->parent == NULL); + REQUIRE(sock->tid == isc_nm_tid()); + + result = tcp_connect_direct(sock, req); + if (result != ISC_R_SUCCESS) { + atomic_store(&sock->active, false); + if (sock->fd != (uv_os_sock_t)(-1)) { + isc__nm_tcp_close(sock); + } + isc__nm_connectcb(sock, req, result, true); + } + + /* + * The sock is now attached to the handle. + */ + isc__nmsocket_detach(&sock); +} + +static void +tcp_connect_cb(uv_connect_t *uvreq, int status) { + isc_result_t result; + isc__nm_uvreq_t *req = NULL; + isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)uvreq->handle); + struct sockaddr_storage ss; + int r; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + + isc__nmsocket_timer_stop(sock); + uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock); + + req = uv_handle_get_data((uv_handle_t *)uvreq); + + REQUIRE(VALID_UVREQ(req)); + REQUIRE(VALID_NMHANDLE(req->handle)); + + if (atomic_load(&sock->timedout)) { + result = ISC_R_TIMEDOUT; + goto error; + } + + if (!atomic_load(&sock->connecting)) { + /* + * The connect was cancelled from timeout; just clean up + * the req. + */ + isc__nm_uvreq_put(&req, sock); + return; + } else if (isc__nmsocket_closing(sock)) { + /* Socket was closed midflight by isc__nm_tcp_shutdown() */ + result = ISC_R_CANCELED; + goto error; + } else if (status == UV_ETIMEDOUT) { + /* Timeout status code here indicates hard error */ + result = ISC_R_TIMEDOUT; + goto error; + } else if (status != 0) { + result = isc__nm_uverr2result(status); + goto error; + } + + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_CONNECT]); + r = uv_tcp_getpeername(&sock->uv_handle.tcp, (struct sockaddr *)&ss, + &(int){ sizeof(ss) }); + if (r != 0) { + result = isc__nm_uverr2result(r); + goto error; + } + + atomic_store(&sock->connecting, false); + + result = isc_sockaddr_fromsockaddr(&sock->peer, (struct sockaddr *)&ss); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + isc__nm_connectcb(sock, req, ISC_R_SUCCESS, false); + + return; + +error: + isc__nm_failed_connect_cb(sock, req, result, false); +} + +void +isc_nm_tcpconnect(isc_nm_t *mgr, isc_sockaddr_t *local, isc_sockaddr_t *peer, + isc_nm_cb_t cb, void *cbarg, unsigned int timeout, + size_t extrahandlesize) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *sock = NULL; + isc__netievent_tcpconnect_t *ievent = NULL; + isc__nm_uvreq_t *req = NULL; + sa_family_t sa_family; + + REQUIRE(VALID_NM(mgr)); + REQUIRE(local != NULL); + REQUIRE(peer != NULL); + + sa_family = peer->type.sa.sa_family; + + sock = isc_mem_get(mgr->mctx, sizeof(*sock)); + isc__nmsocket_init(sock, mgr, isc_nm_tcpsocket, local); + + sock->extrahandlesize = extrahandlesize; + sock->connect_timeout = timeout; + sock->result = ISC_R_UNSET; + sock->fd = (uv_os_sock_t)-1; + atomic_init(&sock->client, true); + + req = isc__nm_uvreq_get(mgr, sock); + req->cb.connect = cb; + req->cbarg = cbarg; + req->peer = *peer; + req->local = *local; + req->handle = isc__nmhandle_get(sock, &req->peer, &sock->iface); + + result = isc__nm_socket(sa_family, SOCK_STREAM, 0, &sock->fd); + if (result != ISC_R_SUCCESS) { + if (isc__nm_in_netthread()) { + sock->tid = isc_nm_tid(); + isc__nmsocket_clearcb(sock); + isc__nm_connectcb(sock, req, result, false); + } else { + isc__nmsocket_clearcb(sock); + sock->tid = isc_random_uniform(mgr->nworkers); + isc__nm_connectcb(sock, req, result, true); + } + atomic_store(&sock->closed, true); + isc__nmsocket_detach(&sock); + return; + } + + ievent = isc__nm_get_netievent_tcpconnect(mgr, sock, req); + + if (isc__nm_in_netthread()) { + atomic_store(&sock->active, true); + sock->tid = isc_nm_tid(); + isc__nm_async_tcpconnect(&mgr->workers[sock->tid], + (isc__netievent_t *)ievent); + isc__nm_put_netievent_tcpconnect(mgr, ievent); + } else { + atomic_init(&sock->active, false); + sock->tid = isc_random_uniform(mgr->nworkers); + isc__nm_enqueue_ievent(&mgr->workers[sock->tid], + (isc__netievent_t *)ievent); + } + LOCK(&sock->lock); + while (sock->result == ISC_R_UNSET) { + WAIT(&sock->cond, &sock->lock); + } + atomic_store(&sock->active, true); + BROADCAST(&sock->scond); + UNLOCK(&sock->lock); +} + +static uv_os_sock_t +isc__nm_tcp_lb_socket(isc_nm_t *mgr, sa_family_t sa_family) { + isc_result_t result; + uv_os_sock_t sock; + + result = isc__nm_socket(sa_family, SOCK_STREAM, 0, &sock); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + (void)isc__nm_socket_incoming_cpu(sock); + + /* FIXME: set mss */ + + result = isc__nm_socket_reuse(sock); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + +#ifndef _WIN32 + if (mgr->load_balance_sockets) { + result = isc__nm_socket_reuse_lb(sock); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } +#endif + + return (sock); +} + +static void +start_tcp_child(isc_nm_t *mgr, isc_sockaddr_t *iface, isc_nmsocket_t *sock, + uv_os_sock_t fd, int tid) { + isc__netievent_tcplisten_t *ievent = NULL; + isc_nmsocket_t *csock = &sock->children[tid]; + + isc__nmsocket_init(csock, mgr, isc_nm_tcpsocket, iface); + csock->parent = sock; + csock->accept_cb = sock->accept_cb; + csock->accept_cbarg = sock->accept_cbarg; + csock->extrahandlesize = sock->extrahandlesize; + csock->backlog = sock->backlog; + csock->tid = tid; + /* + * We don't attach to quota, just assign - to avoid + * increasing quota unnecessarily. + */ + csock->pquota = sock->pquota; + isc_quota_cb_init(&csock->quotacb, quota_accept_cb, csock); + +#ifdef _WIN32 + UNUSED(fd); + csock->fd = isc__nm_tcp_lb_socket(mgr, iface->type.sa.sa_family); +#else + if (mgr->load_balance_sockets) { + UNUSED(fd); + csock->fd = isc__nm_tcp_lb_socket(mgr, + iface->type.sa.sa_family); + } else { + csock->fd = dup(fd); + } +#endif + REQUIRE(csock->fd >= 0); + + ievent = isc__nm_get_netievent_tcplisten(mgr, csock); + isc__nm_maybe_enqueue_ievent(&mgr->workers[tid], + (isc__netievent_t *)ievent); +} + +static void +enqueue_stoplistening(isc_nmsocket_t *sock) { + isc__netievent_tcpstop_t *ievent = + isc__nm_get_netievent_tcpstop(sock->mgr, sock); + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); +} + +isc_result_t +isc_nm_listentcp(isc_nm_t *mgr, isc_sockaddr_t *iface, + isc_nm_accept_cb_t accept_cb, void *accept_cbarg, + size_t extrahandlesize, int backlog, isc_quota_t *quota, + isc_nmsocket_t **sockp) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *sock = NULL; + size_t children_size = 0; + uv_os_sock_t fd = -1; + + REQUIRE(VALID_NM(mgr)); + + sock = isc_mem_get(mgr->mctx, sizeof(*sock)); + isc__nmsocket_init(sock, mgr, isc_nm_tcplistener, iface); + + atomic_init(&sock->rchildren, 0); +#if defined(WIN32) + sock->nchildren = 1; +#else + sock->nchildren = mgr->nworkers; +#endif + children_size = sock->nchildren * sizeof(sock->children[0]); + sock->children = isc_mem_get(mgr->mctx, children_size); + memset(sock->children, 0, children_size); + + sock->result = ISC_R_UNSET; + + sock->accept_cb = accept_cb; + sock->accept_cbarg = accept_cbarg; + sock->extrahandlesize = extrahandlesize; + sock->backlog = backlog; + sock->pquota = quota; + + sock->tid = 0; + sock->fd = -1; + +#ifndef _WIN32 + if (!mgr->load_balance_sockets) { + fd = isc__nm_tcp_lb_socket(mgr, iface->type.sa.sa_family); + } +#endif + + isc_barrier_init(&sock->startlistening, sock->nchildren); + + for (size_t i = 0; i < sock->nchildren; i++) { + if ((int)i == isc_nm_tid()) { + continue; + } + start_tcp_child(mgr, iface, sock, fd, i); + } + + if (isc__nm_in_netthread()) { + start_tcp_child(mgr, iface, sock, fd, isc_nm_tid()); + } + +#ifndef _WIN32 + if (!mgr->load_balance_sockets) { + isc__nm_closesocket(fd); + } +#endif + + LOCK(&sock->lock); + while (atomic_load(&sock->rchildren) != sock->nchildren) { + WAIT(&sock->cond, &sock->lock); + } + result = sock->result; + atomic_store(&sock->active, true); + UNLOCK(&sock->lock); + + INSIST(result != ISC_R_UNSET); + + if (result == ISC_R_SUCCESS) { + REQUIRE(atomic_load(&sock->rchildren) == sock->nchildren); + *sockp = sock; + } else { + atomic_store(&sock->active, false); + enqueue_stoplistening(sock); + isc_nmsocket_close(&sock); + } + + return (result); +} + +void +isc__nm_async_tcplisten(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_tcplisten_t *ievent = (isc__netievent_tcplisten_t *)ev0; + sa_family_t sa_family; + int r; + int flags = 0; + isc_nmsocket_t *sock = NULL; + isc_result_t result; + isc_nm_t *mgr; + + REQUIRE(VALID_NMSOCK(ievent->sock)); + REQUIRE(ievent->sock->tid == isc_nm_tid()); + REQUIRE(VALID_NMSOCK(ievent->sock->parent)); + + sock = ievent->sock; + sa_family = sock->iface.type.sa.sa_family; + mgr = sock->mgr; + + REQUIRE(sock->type == isc_nm_tcpsocket); + REQUIRE(sock->parent != NULL); + REQUIRE(sock->tid == isc_nm_tid()); + + /* TODO: set min mss */ + + r = uv_tcp_init(&worker->loop, &sock->uv_handle.tcp); + UV_RUNTIME_CHECK(uv_tcp_init, r); + + uv_handle_set_data(&sock->uv_handle.handle, sock); + /* This keeps the socket alive after everything else is gone */ + isc__nmsocket_attach(sock, &(isc_nmsocket_t *){ NULL }); + + r = uv_timer_init(&worker->loop, &sock->read_timer); + UV_RUNTIME_CHECK(uv_timer_init, r); + uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock); + + LOCK(&sock->parent->lock); + + r = uv_tcp_open(&sock->uv_handle.tcp, sock->fd); + if (r < 0) { + isc__nm_closesocket(sock->fd); + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_OPENFAIL]); + goto done; + } + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_OPEN]); + + if (sa_family == AF_INET6) { + flags = UV_TCP_IPV6ONLY; + } + +#ifdef _WIN32 + r = isc_uv_tcp_freebind(&sock->uv_handle.tcp, &sock->iface.type.sa, + flags); + if (r < 0) { + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_BINDFAIL]); + goto done; + } +#else + if (mgr->load_balance_sockets) { + r = isc_uv_tcp_freebind(&sock->uv_handle.tcp, + &sock->iface.type.sa, flags); + if (r < 0) { + isc__nm_incstats(sock->mgr, + sock->statsindex[STATID_BINDFAIL]); + goto done; + } + } else { + if (sock->parent->fd == -1) { + r = isc_uv_tcp_freebind(&sock->uv_handle.tcp, + &sock->iface.type.sa, flags); + if (r < 0) { + isc__nm_incstats(sock->mgr, STATID_BINDFAIL); + goto done; + } + sock->parent->uv_handle.tcp.flags = + sock->uv_handle.tcp.flags; + sock->parent->fd = sock->fd; + } else { + /* The socket is already bound, just copy the flags */ + sock->uv_handle.tcp.flags = + sock->parent->uv_handle.tcp.flags; + } + } +#endif + + /* + * The callback will run in the same thread uv_listen() was called + * from, so a race with tcp_connection_cb() isn't possible. + */ + r = uv_listen((uv_stream_t *)&sock->uv_handle.tcp, sock->backlog, + tcp_connection_cb); + if (r != 0) { + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_NETMGR, ISC_LOG_ERROR, + "uv_listen failed: %s", + isc_result_totext(isc__nm_uverr2result(r))); + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_BINDFAIL]); + goto done; + } + + atomic_store(&sock->listening, true); + +done: + result = isc__nm_uverr2result(r); + if (result != ISC_R_SUCCESS) { + sock->pquota = NULL; + } + + atomic_fetch_add(&sock->parent->rchildren, 1); + if (sock->parent->result == ISC_R_UNSET) { + sock->parent->result = result; + } + SIGNAL(&sock->parent->cond); + UNLOCK(&sock->parent->lock); + + isc_barrier_wait(&sock->parent->startlistening); +} + +static void +tcp_connection_cb(uv_stream_t *server, int status) { + isc_nmsocket_t *ssock = uv_handle_get_data((uv_handle_t *)server); + isc_result_t result; + isc_quota_t *quota = NULL; + + if (status != 0) { + result = isc__nm_uverr2result(status); + goto done; + } + + REQUIRE(VALID_NMSOCK(ssock)); + REQUIRE(ssock->tid == isc_nm_tid()); + + if (isc__nmsocket_closing(ssock)) { + result = ISC_R_CANCELED; + goto done; + } + + if (ssock->pquota != NULL) { + result = isc_quota_attach_cb(ssock->pquota, "a, + &ssock->quotacb); + if (result == ISC_R_QUOTA) { + isc__nm_incstats(ssock->mgr, + ssock->statsindex[STATID_ACCEPTFAIL]); + goto done; + } + } + + result = accept_connection(ssock, quota); +done: + isc__nm_accept_connection_log(result, can_log_tcp_quota()); +} + +void +isc__nm_tcp_stoplistening(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->type == isc_nm_tcplistener); + + if (!atomic_compare_exchange_strong(&sock->closing, &(bool){ false }, + true)) + { + UNREACHABLE(); + } + + if (!isc__nm_in_netthread()) { + enqueue_stoplistening(sock); + } else { + stop_tcp_parent(sock); + } +} + +void +isc__nm_async_tcpstop(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_tcpstop_t *ievent = (isc__netievent_tcpstop_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + + UNUSED(worker); + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + + if (sock->parent != NULL) { + stop_tcp_child(sock); + return; + } + + stop_tcp_parent(sock); +} + +void +isc__nm_tcp_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(result != ISC_R_SUCCESS); + + isc__nmsocket_timer_stop(sock); + isc__nm_stop_reading(sock); + + if (!sock->recv_read) { + goto destroy; + } + sock->recv_read = false; + + if (sock->recv_cb != NULL) { + isc__nm_uvreq_t *req = isc__nm_get_read_req(sock, NULL); + isc__nmsocket_clearcb(sock); + isc__nm_readcb(sock, req, result); + } + +destroy: + isc__nmsocket_prep_destroy(sock); + + /* + * We need to detach from quota after the read callback function had a + * chance to be executed. + */ + if (sock->quota != NULL) { + isc_quota_detach(&sock->quota); + } +} + +void +isc__nm_tcp_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg) { + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + + isc_nmsocket_t *sock = handle->sock; + isc__netievent_tcpstartread_t *ievent = NULL; + + REQUIRE(sock->type == isc_nm_tcpsocket); + REQUIRE(sock->statichandle == handle); + REQUIRE(sock->tid == isc_nm_tid()); + REQUIRE(!sock->recv_read); + + sock->recv_cb = cb; + sock->recv_cbarg = cbarg; + sock->recv_read = true; + if (sock->read_timeout == 0) { + sock->read_timeout = + (atomic_load(&sock->keepalive) + ? atomic_load(&sock->mgr->keepalive) + : atomic_load(&sock->mgr->idle)); + } + + ievent = isc__nm_get_netievent_tcpstartread(sock->mgr, sock); + + /* + * This MUST be done asynchronously, no matter which thread we're + * in. The callback function for isc_nm_read() often calls + * isc_nm_read() again; if we tried to do that synchronously + * we'd clash in processbuffer() and grow the stack indefinitely. + */ + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); + + return; +} + +void +isc__nm_async_tcpstartread(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_tcpstartread_t *ievent = + (isc__netievent_tcpstartread_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + isc_result_t result; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + UNUSED(worker); + + if (isc__nmsocket_closing(sock)) { + result = ISC_R_CANCELED; + } else { + result = isc__nm_start_reading(sock); + } + + if (result != ISC_R_SUCCESS) { + sock->reading = true; + isc__nm_tcp_failed_read_cb(sock, result); + return; + } + + isc__nmsocket_timer_start(sock); +} + +void +isc__nm_tcp_pauseread(isc_nmhandle_t *handle) { + isc__netievent_tcppauseread_t *ievent = NULL; + isc_nmsocket_t *sock = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + + sock = handle->sock; + + REQUIRE(VALID_NMSOCK(sock)); + + if (!atomic_compare_exchange_strong(&sock->readpaused, &(bool){ false }, + true)) + { + return; + } + + ievent = isc__nm_get_netievent_tcppauseread(sock->mgr, sock); + + isc__nm_maybe_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); + + return; +} + +void +isc__nm_async_tcppauseread(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_tcppauseread_t *ievent = + (isc__netievent_tcppauseread_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + UNUSED(worker); + + isc__nmsocket_timer_stop(sock); + isc__nm_stop_reading(sock); +} + +void +isc__nm_tcp_resumeread(isc_nmhandle_t *handle) { + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + + isc__netievent_tcpstartread_t *ievent = NULL; + isc_nmsocket_t *sock = handle->sock; + + REQUIRE(sock->tid == isc_nm_tid()); + + if (sock->recv_cb == NULL) { + /* We are no longer reading */ + return; + } + + if (!isc__nmsocket_active(sock)) { + sock->reading = true; + isc__nm_tcp_failed_read_cb(sock, ISC_R_CANCELED); + return; + } + + if (!atomic_compare_exchange_strong(&sock->readpaused, &(bool){ true }, + false)) + { + return; + } + + ievent = isc__nm_get_netievent_tcpstartread(sock->mgr, sock); + + isc__nm_maybe_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); +} + +void +isc__nm_tcp_read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { + isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)stream); + isc__nm_uvreq_t *req = NULL; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + REQUIRE(sock->reading); + REQUIRE(buf != NULL); + + if (isc__nmsocket_closing(sock)) { + isc__nm_tcp_failed_read_cb(sock, ISC_R_CANCELED); + goto free; + } + + if (nread < 0) { + if (nread != UV_EOF) { + isc__nm_incstats(sock->mgr, + sock->statsindex[STATID_RECVFAIL]); + } + + isc__nm_tcp_failed_read_cb(sock, isc__nm_uverr2result(nread)); + + goto free; + } + + req = isc__nm_get_read_req(sock, NULL); + + /* + * The callback will be called synchronously because the + * result is ISC_R_SUCCESS, so we don't need to retain + * the buffer + */ + req->uvbuf.base = buf->base; + req->uvbuf.len = nread; + + if (!atomic_load(&sock->client)) { + sock->read_timeout = + (atomic_load(&sock->keepalive) + ? atomic_load(&sock->mgr->keepalive) + : atomic_load(&sock->mgr->idle)); + } + + isc__nm_readcb(sock, req, ISC_R_SUCCESS); + + /* The readcb could have paused the reading */ + if (sock->reading) { + /* The timer will be updated */ + isc__nmsocket_timer_restart(sock); + } + +free: + if (nread < 0) { + /* + * The buffer may be a null buffer on error. + */ + if (buf->base == NULL && buf->len == 0) { + return; + } + } + + isc__nm_free_uvbuf(sock, buf); +} + +static void +quota_accept_cb(isc_quota_t *quota, void *sock0) { + isc_nmsocket_t *sock = (isc_nmsocket_t *)sock0; + isc__netievent_tcpaccept_t *ievent = NULL; + + REQUIRE(VALID_NMSOCK(sock)); + + /* + * Create a tcpaccept event and pass it using the async channel. + */ + ievent = isc__nm_get_netievent_tcpaccept(sock->mgr, sock, quota); + isc__nm_maybe_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); +} + +/* + * This is called after we get a quota_accept_cb() callback. + */ +void +isc__nm_async_tcpaccept(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_tcpaccept_t *ievent = (isc__netievent_tcpaccept_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + isc_result_t result; + + UNUSED(worker); + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + + result = accept_connection(sock, ievent->quota); + isc__nm_accept_connection_log(result, can_log_tcp_quota()); +} + +static isc_result_t +accept_connection(isc_nmsocket_t *ssock, isc_quota_t *quota) { + isc_nmsocket_t *csock = NULL; + isc__networker_t *worker = NULL; + int r; + isc_result_t result; + struct sockaddr_storage ss; + isc_sockaddr_t local; + isc_nmhandle_t *handle = NULL; + + REQUIRE(VALID_NMSOCK(ssock)); + REQUIRE(ssock->tid == isc_nm_tid()); + + if (isc__nmsocket_closing(ssock)) { + if (quota != NULL) { + isc_quota_detach("a); + } + return (ISC_R_CANCELED); + } + + csock = isc_mem_get(ssock->mgr->mctx, sizeof(isc_nmsocket_t)); + isc__nmsocket_init(csock, ssock->mgr, isc_nm_tcpsocket, &ssock->iface); + csock->tid = ssock->tid; + csock->extrahandlesize = ssock->extrahandlesize; + isc__nmsocket_attach(ssock, &csock->server); + csock->recv_cb = ssock->recv_cb; + csock->recv_cbarg = ssock->recv_cbarg; + csock->quota = quota; + csock->accepting = true; + + worker = &csock->mgr->workers[isc_nm_tid()]; + + r = uv_tcp_init(&worker->loop, &csock->uv_handle.tcp); + UV_RUNTIME_CHECK(uv_tcp_init, r); + uv_handle_set_data(&csock->uv_handle.handle, csock); + + r = uv_timer_init(&worker->loop, &csock->read_timer); + UV_RUNTIME_CHECK(uv_timer_init, r); + uv_handle_set_data((uv_handle_t *)&csock->read_timer, csock); + + r = uv_accept(&ssock->uv_handle.stream, &csock->uv_handle.stream); + if (r != 0) { + result = isc__nm_uverr2result(r); + goto failure; + } + + r = uv_tcp_getpeername(&csock->uv_handle.tcp, (struct sockaddr *)&ss, + &(int){ sizeof(ss) }); + if (r != 0) { + result = isc__nm_uverr2result(r); + goto failure; + } + + result = isc_sockaddr_fromsockaddr(&csock->peer, + (struct sockaddr *)&ss); + if (result != ISC_R_SUCCESS) { + goto failure; + } + + r = uv_tcp_getsockname(&csock->uv_handle.tcp, (struct sockaddr *)&ss, + &(int){ sizeof(ss) }); + if (r != 0) { + result = isc__nm_uverr2result(r); + goto failure; + } + + result = isc_sockaddr_fromsockaddr(&local, (struct sockaddr *)&ss); + if (result != ISC_R_SUCCESS) { + goto failure; + } + + handle = isc__nmhandle_get(csock, NULL, &local); + + result = ssock->accept_cb(handle, ISC_R_SUCCESS, ssock->accept_cbarg); + if (result != ISC_R_SUCCESS) { + isc_nmhandle_detach(&handle); + goto failure; + } + + csock->accepting = false; + + isc__nm_incstats(csock->mgr, csock->statsindex[STATID_ACCEPT]); + + csock->read_timeout = atomic_load(&csock->mgr->init); + + atomic_fetch_add(&ssock->parent->active_child_connections, 1); + + /* + * The acceptcb needs to attach to the handle if it wants to keep the + * connection alive + */ + isc_nmhandle_detach(&handle); + + /* + * sock is now attached to the handle. + */ + isc__nmsocket_detach(&csock); + + return (ISC_R_SUCCESS); + +failure: + atomic_store(&csock->active, false); + + failed_accept_cb(csock, result); + + isc__nmsocket_prep_destroy(csock); + + isc__nmsocket_detach(&csock); + + return (result); +} + +void +isc__nm_tcp_send(isc_nmhandle_t *handle, const isc_region_t *region, + isc_nm_cb_t cb, void *cbarg) { + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + + isc_nmsocket_t *sock = handle->sock; + isc__netievent_tcpsend_t *ievent = NULL; + isc__nm_uvreq_t *uvreq = NULL; + + REQUIRE(sock->type == isc_nm_tcpsocket); + + uvreq = isc__nm_uvreq_get(sock->mgr, sock); + uvreq->uvbuf.base = (char *)region->base; + uvreq->uvbuf.len = region->length; + + isc_nmhandle_attach(handle, &uvreq->handle); + + uvreq->cb.send = cb; + uvreq->cbarg = cbarg; + + ievent = isc__nm_get_netievent_tcpsend(sock->mgr, sock, uvreq); + isc__nm_maybe_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); + + return; +} + +static void +tcp_send_cb(uv_write_t *req, int status) { + isc__nm_uvreq_t *uvreq = (isc__nm_uvreq_t *)req->data; + isc_nmsocket_t *sock = NULL; + + REQUIRE(VALID_UVREQ(uvreq)); + REQUIRE(VALID_NMSOCK(uvreq->sock)); + + sock = uvreq->sock; + + isc_nm_timer_stop(uvreq->timer); + isc_nm_timer_detach(&uvreq->timer); + + if (status < 0) { + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_SENDFAIL]); + isc__nm_failed_send_cb(sock, uvreq, + isc__nm_uverr2result(status)); + return; + } + + isc__nm_sendcb(sock, uvreq, ISC_R_SUCCESS, false); +} + +/* + * Handle 'tcpsend' async event - send a packet on the socket + */ +void +isc__nm_async_tcpsend(isc__networker_t *worker, isc__netievent_t *ev0) { + isc_result_t result; + isc__netievent_tcpsend_t *ievent = (isc__netievent_tcpsend_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + isc__nm_uvreq_t *uvreq = ievent->req; + + REQUIRE(sock->type == isc_nm_tcpsocket); + REQUIRE(sock->tid == isc_nm_tid()); + UNUSED(worker); + + if (sock->write_timeout == 0) { + sock->write_timeout = + (atomic_load(&sock->keepalive) + ? atomic_load(&sock->mgr->keepalive) + : atomic_load(&sock->mgr->idle)); + } + + result = tcp_send_direct(sock, uvreq); + if (result != ISC_R_SUCCESS) { + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_SENDFAIL]); + isc__nm_failed_send_cb(sock, uvreq, result); + } +} + +static isc_result_t +tcp_send_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(VALID_UVREQ(req)); + REQUIRE(sock->tid == isc_nm_tid()); + REQUIRE(sock->type == isc_nm_tcpsocket); + + int r; + + if (isc__nmsocket_closing(sock)) { + return (ISC_R_CANCELED); + } + + r = uv_write(&req->uv_req.write, &sock->uv_handle.stream, &req->uvbuf, + 1, tcp_send_cb); + if (r < 0) { + return (isc__nm_uverr2result(r)); + } + + isc_nm_timer_create(req->handle, isc__nmsocket_writetimeout_cb, req, + &req->timer); + if (sock->write_timeout > 0) { + isc_nm_timer_start(req->timer, sock->write_timeout); + } + + return (ISC_R_SUCCESS); +} + +static void +tcp_stop_cb(uv_handle_t *handle) { + isc_nmsocket_t *sock = uv_handle_get_data(handle); + uv_handle_set_data(handle, NULL); + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + REQUIRE(atomic_load(&sock->closing)); + + if (!atomic_compare_exchange_strong(&sock->closed, &(bool){ false }, + true)) + { + UNREACHABLE(); + } + + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_CLOSE]); + + atomic_store(&sock->listening, false); + + isc__nmsocket_detach(&sock); +} + +static void +tcp_close_sock(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + REQUIRE(atomic_load(&sock->closing)); + + if (!atomic_compare_exchange_strong(&sock->closed, &(bool){ false }, + true)) + { + UNREACHABLE(); + } + + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_CLOSE]); + + if (sock->server != NULL) { + isc__nmsocket_detach(&sock->server); + } + + atomic_store(&sock->connected, false); + + isc__nmsocket_prep_destroy(sock); +} + +static void +tcp_close_cb(uv_handle_t *handle) { + isc_nmsocket_t *sock = uv_handle_get_data(handle); + uv_handle_set_data(handle, NULL); + + tcp_close_sock(sock); +} + +static void +read_timer_close_cb(uv_handle_t *handle) { + isc_nmsocket_t *sock = uv_handle_get_data(handle); + uv_handle_set_data(handle, NULL); + + if (sock->parent) { + uv_close(&sock->uv_handle.handle, tcp_stop_cb); + } else if (uv_is_closing(&sock->uv_handle.handle)) { + tcp_close_sock(sock); + } else { + uv_close(&sock->uv_handle.handle, tcp_close_cb); + } +} + +static void +stop_tcp_child(isc_nmsocket_t *sock) { + REQUIRE(sock->type == isc_nm_tcpsocket); + REQUIRE(sock->tid == isc_nm_tid()); + + if (!atomic_compare_exchange_strong(&sock->closing, &(bool){ false }, + true)) + { + return; + } + + tcp_close_direct(sock); + + atomic_fetch_sub(&sock->parent->rchildren, 1); + + isc_barrier_wait(&sock->parent->stoplistening); +} + +static void +stop_tcp_parent(isc_nmsocket_t *sock) { + isc_nmsocket_t *csock = NULL; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + REQUIRE(sock->type == isc_nm_tcplistener); + + isc_barrier_init(&sock->stoplistening, sock->nchildren); + + for (size_t i = 0; i < sock->nchildren; i++) { + csock = &sock->children[i]; + REQUIRE(VALID_NMSOCK(csock)); + + if ((int)i == isc_nm_tid()) { + /* + * We need to schedule closing the other sockets first + */ + continue; + } + + atomic_store(&csock->active, false); + enqueue_stoplistening(csock); + } + + csock = &sock->children[isc_nm_tid()]; + atomic_store(&csock->active, false); + stop_tcp_child(csock); + + atomic_store(&sock->closed, true); + isc__nmsocket_prep_destroy(sock); +} + +static void +tcp_close_direct(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + REQUIRE(atomic_load(&sock->closing)); + + if (sock->server != NULL) { + REQUIRE(VALID_NMSOCK(sock->server)); + REQUIRE(VALID_NMSOCK(sock->server->parent)); + if (sock->server->parent != NULL) { + atomic_fetch_sub( + &sock->server->parent->active_child_connections, + 1); + } + } + + if (sock->quota != NULL) { + isc_quota_detach(&sock->quota); + } + + isc__nmsocket_timer_stop(sock); + isc__nm_stop_reading(sock); + + uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock); + uv_close((uv_handle_t *)&sock->read_timer, read_timer_close_cb); +} + +void +isc__nm_tcp_close(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->type == isc_nm_tcpsocket); + REQUIRE(!isc__nmsocket_active(sock)); + + if (!atomic_compare_exchange_strong(&sock->closing, &(bool){ false }, + true)) + { + return; + } + + if (sock->tid == isc_nm_tid()) { + tcp_close_direct(sock); + } else { + /* + * We need to create an event and pass it using async channel + */ + isc__netievent_tcpclose_t *ievent = + isc__nm_get_netievent_tcpclose(sock->mgr, sock); + + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); + } +} + +void +isc__nm_async_tcpclose(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_tcpclose_t *ievent = (isc__netievent_tcpclose_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + + UNUSED(worker); + + tcp_close_direct(sock); +} + +static void +tcp_close_connect_cb(uv_handle_t *handle) { + isc_nmsocket_t *sock = uv_handle_get_data(handle); + + REQUIRE(VALID_NMSOCK(sock)); + + REQUIRE(isc__nm_in_netthread()); + REQUIRE(sock->tid == isc_nm_tid()); + + isc__nmsocket_prep_destroy(sock); + isc__nmsocket_detach(&sock); +} + +void +isc__nm_tcp_shutdown(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + REQUIRE(sock->type == isc_nm_tcpsocket); + + /* + * If the socket is active, mark it inactive and + * continue. If it isn't active, stop now. + */ + if (!isc__nmsocket_deactivate(sock)) { + return; + } + + if (sock->accepting) { + return; + } + + if (atomic_load(&sock->connecting)) { + isc_nmsocket_t *tsock = NULL; + isc__nmsocket_attach(sock, &tsock); + uv_close(&sock->uv_handle.handle, tcp_close_connect_cb); + return; + } + + if (sock->statichandle != NULL) { + isc__nm_tcp_failed_read_cb(sock, ISC_R_CANCELED); + return; + } + + /* + * Otherwise, we just send the socket to abyss... + */ + if (sock->parent == NULL) { + isc__nmsocket_prep_destroy(sock); + } +} + +void +isc__nm_tcp_cancelread(isc_nmhandle_t *handle) { + isc_nmsocket_t *sock = NULL; + isc__netievent_tcpcancel_t *ievent = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + + sock = handle->sock; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->type == isc_nm_tcpsocket); + + ievent = isc__nm_get_netievent_tcpcancel(sock->mgr, sock, handle); + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); +} + +void +isc__nm_async_tcpcancel(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_tcpcancel_t *ievent = (isc__netievent_tcpcancel_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + UNUSED(worker); + + uv_timer_stop(&sock->read_timer); + + isc__nm_tcp_failed_read_cb(sock, ISC_R_EOF); +} + +int_fast32_t +isc__nm_tcp_listener_nactive(isc_nmsocket_t *listener) { + int_fast32_t nactive; + + REQUIRE(VALID_NMSOCK(listener)); + + nactive = atomic_load(&listener->active_child_connections); + INSIST(nactive >= 0); + return (nactive); +} diff --git a/lib/isc/netmgr/tcpdns.c b/lib/isc/netmgr/tcpdns.c new file mode 100644 index 0000000..bd593eb --- /dev/null +++ b/lib/isc/netmgr/tcpdns.c @@ -0,0 +1,1505 @@ +/* + * 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. + */ + +#include <libgen.h> +#include <unistd.h> +#include <uv.h> + +#include <isc/atomic.h> +#include <isc/barrier.h> +#include <isc/buffer.h> +#include <isc/condition.h> +#include <isc/errno.h> +#include <isc/log.h> +#include <isc/magic.h> +#include <isc/mem.h> +#include <isc/netmgr.h> +#include <isc/quota.h> +#include <isc/random.h> +#include <isc/refcount.h> +#include <isc/region.h> +#include <isc/result.h> +#include <isc/sockaddr.h> +#include <isc/stdtime.h> +#include <isc/thread.h> +#include <isc/util.h> + +#include "netmgr-int.h" +#include "uv-compat.h" + +static atomic_uint_fast32_t last_tcpdnsquota_log = 0; + +static bool +can_log_tcpdns_quota(void) { + isc_stdtime_t now, last; + + isc_stdtime_get(&now); + last = atomic_exchange_relaxed(&last_tcpdnsquota_log, now); + if (now != last) { + return (true); + } + + return (false); +} + +static isc_result_t +tcpdns_connect_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req); + +static void +tcpdns_close_direct(isc_nmsocket_t *sock); + +static void +tcpdns_connect_cb(uv_connect_t *uvreq, int status); + +static void +tcpdns_connection_cb(uv_stream_t *server, int status); + +static void +tcpdns_close_cb(uv_handle_t *uvhandle); + +static isc_result_t +accept_connection(isc_nmsocket_t *ssock, isc_quota_t *quota); + +static void +quota_accept_cb(isc_quota_t *quota, void *sock0); + +static void +stop_tcpdns_parent(isc_nmsocket_t *sock); +static void +stop_tcpdns_child(isc_nmsocket_t *sock); + +static isc_result_t +tcpdns_connect_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req) { + isc__networker_t *worker = NULL; + isc_result_t result = ISC_R_UNSET; + int r; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(VALID_UVREQ(req)); + + REQUIRE(isc__nm_in_netthread()); + REQUIRE(sock->tid == isc_nm_tid()); + + worker = &sock->mgr->workers[sock->tid]; + + atomic_store(&sock->connecting, true); + + r = uv_tcp_init(&worker->loop, &sock->uv_handle.tcp); + UV_RUNTIME_CHECK(uv_tcp_init, r); + uv_handle_set_data(&sock->uv_handle.handle, sock); + + r = uv_timer_init(&worker->loop, &sock->read_timer); + UV_RUNTIME_CHECK(uv_timer_init, r); + uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock); + + if (isc__nm_closing(sock)) { + result = ISC_R_CANCELED; + goto error; + } + + r = uv_tcp_open(&sock->uv_handle.tcp, sock->fd); + if (r != 0) { + isc__nm_closesocket(sock->fd); + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_OPENFAIL]); + goto done; + } + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_OPEN]); + + if (req->local.length != 0) { + r = uv_tcp_bind(&sock->uv_handle.tcp, &req->local.type.sa, 0); + /* + * In case of shared socket UV_EINVAL will be returned and needs + * to be ignored + */ + if (r != 0 && r != UV_EINVAL) { + isc__nm_incstats(sock->mgr, + sock->statsindex[STATID_BINDFAIL]); + goto done; + } + } + + uv_handle_set_data(&req->uv_req.handle, req); + r = uv_tcp_connect(&req->uv_req.connect, &sock->uv_handle.tcp, + &req->peer.type.sa, tcpdns_connect_cb); + if (r != 0) { + isc__nm_incstats(sock->mgr, + sock->statsindex[STATID_CONNECTFAIL]); + goto done; + } + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_CONNECT]); + + uv_handle_set_data((uv_handle_t *)&sock->read_timer, + &req->uv_req.connect); + isc__nmsocket_timer_start(sock); + + atomic_store(&sock->connected, true); + +done: + result = isc__nm_uverr2result(r); +error: + LOCK(&sock->lock); + sock->result = result; + SIGNAL(&sock->cond); + if (!atomic_load(&sock->active)) { + WAIT(&sock->scond, &sock->lock); + } + INSIST(atomic_load(&sock->active)); + UNLOCK(&sock->lock); + + return (result); +} + +void +isc__nm_async_tcpdnsconnect(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_tcpdnsconnect_t *ievent = + (isc__netievent_tcpdnsconnect_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + isc__nm_uvreq_t *req = ievent->req; + isc_result_t result = ISC_R_SUCCESS; + + UNUSED(worker); + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->type == isc_nm_tcpdnssocket); + REQUIRE(sock->parent == NULL); + REQUIRE(sock->tid == isc_nm_tid()); + + result = tcpdns_connect_direct(sock, req); + if (result != ISC_R_SUCCESS) { + isc__nmsocket_clearcb(sock); + isc__nm_connectcb(sock, req, result, true); + atomic_store(&sock->active, false); + isc__nm_tcpdns_close(sock); + } + + /* + * The sock is now attached to the handle. + */ + isc__nmsocket_detach(&sock); +} + +static void +tcpdns_connect_cb(uv_connect_t *uvreq, int status) { + isc_result_t result; + isc__nm_uvreq_t *req = NULL; + isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)uvreq->handle); + struct sockaddr_storage ss; + int r; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + + isc__nmsocket_timer_stop(sock); + uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock); + + req = uv_handle_get_data((uv_handle_t *)uvreq); + + REQUIRE(VALID_UVREQ(req)); + REQUIRE(VALID_NMHANDLE(req->handle)); + + if (atomic_load(&sock->timedout)) { + result = ISC_R_TIMEDOUT; + goto error; + } + + if (isc__nmsocket_closing(sock)) { + /* Socket was closed midflight by isc__nm_tcpdns_shutdown() */ + result = ISC_R_CANCELED; + goto error; + } else if (status == UV_ETIMEDOUT) { + /* Timeout status code here indicates hard error */ + result = ISC_R_TIMEDOUT; + goto error; + } else if (status != 0) { + result = isc__nm_uverr2result(status); + goto error; + } + + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_CONNECT]); + r = uv_tcp_getpeername(&sock->uv_handle.tcp, (struct sockaddr *)&ss, + &(int){ sizeof(ss) }); + if (r != 0) { + result = isc__nm_uverr2result(r); + goto error; + } + + atomic_store(&sock->connecting, false); + + result = isc_sockaddr_fromsockaddr(&sock->peer, (struct sockaddr *)&ss); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + isc__nm_connectcb(sock, req, ISC_R_SUCCESS, false); + + return; + +error: + isc__nm_failed_connect_cb(sock, req, result, false); +} + +void +isc_nm_tcpdnsconnect(isc_nm_t *mgr, isc_sockaddr_t *local, isc_sockaddr_t *peer, + isc_nm_cb_t cb, void *cbarg, unsigned int timeout, + size_t extrahandlesize) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *sock = NULL; + isc__netievent_tcpdnsconnect_t *ievent = NULL; + isc__nm_uvreq_t *req = NULL; + sa_family_t sa_family; + + REQUIRE(VALID_NM(mgr)); + REQUIRE(local != NULL); + REQUIRE(peer != NULL); + + sa_family = peer->type.sa.sa_family; + + sock = isc_mem_get(mgr->mctx, sizeof(*sock)); + isc__nmsocket_init(sock, mgr, isc_nm_tcpdnssocket, local); + + sock->extrahandlesize = extrahandlesize; + sock->connect_timeout = timeout; + sock->result = ISC_R_UNSET; + atomic_init(&sock->client, true); + + req = isc__nm_uvreq_get(mgr, sock); + req->cb.connect = cb; + req->cbarg = cbarg; + req->peer = *peer; + req->local = *local; + req->handle = isc__nmhandle_get(sock, &req->peer, &sock->iface); + + result = isc__nm_socket(sa_family, SOCK_STREAM, 0, &sock->fd); + if (result != ISC_R_SUCCESS) { + if (isc__nm_in_netthread()) { + sock->tid = isc_nm_tid(); + } + isc__nmsocket_clearcb(sock); + isc__nm_connectcb(sock, req, result, true); + atomic_store(&sock->closed, true); + isc__nmsocket_detach(&sock); + return; + } + + /* 2 minute timeout */ + result = isc__nm_socket_connectiontimeout(sock->fd, 120 * 1000); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + ievent = isc__nm_get_netievent_tcpdnsconnect(mgr, sock, req); + + if (isc__nm_in_netthread()) { + atomic_store(&sock->active, true); + sock->tid = isc_nm_tid(); + isc__nm_async_tcpdnsconnect(&mgr->workers[sock->tid], + (isc__netievent_t *)ievent); + isc__nm_put_netievent_tcpdnsconnect(mgr, ievent); + } else { + atomic_init(&sock->active, false); + sock->tid = isc_random_uniform(mgr->nworkers); + isc__nm_enqueue_ievent(&mgr->workers[sock->tid], + (isc__netievent_t *)ievent); + } + + LOCK(&sock->lock); + while (sock->result == ISC_R_UNSET) { + WAIT(&sock->cond, &sock->lock); + } + atomic_store(&sock->active, true); + BROADCAST(&sock->scond); + UNLOCK(&sock->lock); +} + +static uv_os_sock_t +isc__nm_tcpdns_lb_socket(isc_nm_t *mgr, sa_family_t sa_family) { + isc_result_t result; + uv_os_sock_t sock; + + result = isc__nm_socket(sa_family, SOCK_STREAM, 0, &sock); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + (void)isc__nm_socket_incoming_cpu(sock); + + /* FIXME: set mss */ + + result = isc__nm_socket_reuse(sock); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + +#ifndef _WIN32 + if (mgr->load_balance_sockets) { + result = isc__nm_socket_reuse_lb(sock); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } +#endif + + return (sock); +} + +static void +enqueue_stoplistening(isc_nmsocket_t *sock) { + isc__netievent_tcpdnsstop_t *ievent = + isc__nm_get_netievent_tcpdnsstop(sock->mgr, sock); + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); +} + +static void +start_tcpdns_child(isc_nm_t *mgr, isc_sockaddr_t *iface, isc_nmsocket_t *sock, + uv_os_sock_t fd, int tid) { + isc__netievent_tcpdnslisten_t *ievent = NULL; + isc_nmsocket_t *csock = &sock->children[tid]; + + isc__nmsocket_init(csock, mgr, isc_nm_tcpdnssocket, iface); + csock->parent = sock; + csock->accept_cb = sock->accept_cb; + csock->accept_cbarg = sock->accept_cbarg; + csock->recv_cb = sock->recv_cb; + csock->recv_cbarg = sock->recv_cbarg; + csock->extrahandlesize = sock->extrahandlesize; + csock->backlog = sock->backlog; + csock->tid = tid; + /* + * We don't attach to quota, just assign - to avoid + * increasing quota unnecessarily. + */ + csock->pquota = sock->pquota; + isc_quota_cb_init(&csock->quotacb, quota_accept_cb, csock); + +#ifdef _WIN32 + UNUSED(fd); + csock->fd = isc__nm_tcpdns_lb_socket(mgr, iface->type.sa.sa_family); +#else + if (mgr->load_balance_sockets) { + UNUSED(fd); + csock->fd = isc__nm_tcpdns_lb_socket(mgr, + iface->type.sa.sa_family); + } else { + csock->fd = dup(fd); + } +#endif + REQUIRE(csock->fd >= 0); + + ievent = isc__nm_get_netievent_tcpdnslisten(mgr, csock); + isc__nm_maybe_enqueue_ievent(&mgr->workers[tid], + (isc__netievent_t *)ievent); +} +isc_result_t +isc_nm_listentcpdns(isc_nm_t *mgr, isc_sockaddr_t *iface, + isc_nm_recv_cb_t recv_cb, void *recv_cbarg, + isc_nm_accept_cb_t accept_cb, void *accept_cbarg, + size_t extrahandlesize, int backlog, isc_quota_t *quota, + isc_nmsocket_t **sockp) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *sock = NULL; + size_t children_size = 0; + uv_os_sock_t fd = -1; + + REQUIRE(VALID_NM(mgr)); + + sock = isc_mem_get(mgr->mctx, sizeof(*sock)); + isc__nmsocket_init(sock, mgr, isc_nm_tcpdnslistener, iface); + + atomic_init(&sock->rchildren, 0); +#if defined(WIN32) + sock->nchildren = 1; +#else + sock->nchildren = mgr->nworkers; +#endif + children_size = sock->nchildren * sizeof(sock->children[0]); + sock->children = isc_mem_get(mgr->mctx, children_size); + memset(sock->children, 0, children_size); + + sock->result = ISC_R_UNSET; + sock->accept_cb = accept_cb; + sock->accept_cbarg = accept_cbarg; + sock->recv_cb = recv_cb; + sock->recv_cbarg = recv_cbarg; + sock->extrahandlesize = extrahandlesize; + sock->backlog = backlog; + sock->pquota = quota; + + sock->tid = 0; + sock->fd = -1; + +#ifndef _WIN32 + if (!mgr->load_balance_sockets) { + fd = isc__nm_tcpdns_lb_socket(mgr, iface->type.sa.sa_family); + } +#endif + + isc_barrier_init(&sock->startlistening, sock->nchildren); + + for (size_t i = 0; i < sock->nchildren; i++) { + if ((int)i == isc_nm_tid()) { + continue; + } + start_tcpdns_child(mgr, iface, sock, fd, i); + } + + if (isc__nm_in_netthread()) { + start_tcpdns_child(mgr, iface, sock, fd, isc_nm_tid()); + } + +#ifndef _WIN32 + if (!mgr->load_balance_sockets) { + isc__nm_closesocket(fd); + } +#endif + + LOCK(&sock->lock); + while (atomic_load(&sock->rchildren) != sock->nchildren) { + WAIT(&sock->cond, &sock->lock); + } + result = sock->result; + atomic_store(&sock->active, true); + UNLOCK(&sock->lock); + + INSIST(result != ISC_R_UNSET); + + if (result == ISC_R_SUCCESS) { + REQUIRE(atomic_load(&sock->rchildren) == sock->nchildren); + *sockp = sock; + } else { + atomic_store(&sock->active, false); + enqueue_stoplistening(sock); + isc_nmsocket_close(&sock); + } + + return (result); +} + +void +isc__nm_async_tcpdnslisten(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_tcpdnslisten_t *ievent = + (isc__netievent_tcpdnslisten_t *)ev0; + sa_family_t sa_family; + int r; + int flags = 0; + isc_nmsocket_t *sock = NULL; + isc_result_t result = ISC_R_UNSET; + isc_nm_t *mgr = NULL; + + REQUIRE(VALID_NMSOCK(ievent->sock)); + REQUIRE(ievent->sock->tid == isc_nm_tid()); + REQUIRE(VALID_NMSOCK(ievent->sock->parent)); + + sock = ievent->sock; + sa_family = sock->iface.type.sa.sa_family; + mgr = sock->mgr; + + REQUIRE(sock->type == isc_nm_tcpdnssocket); + REQUIRE(sock->parent != NULL); + REQUIRE(sock->tid == isc_nm_tid()); + + /* TODO: set min mss */ + + r = uv_tcp_init(&worker->loop, &sock->uv_handle.tcp); + UV_RUNTIME_CHECK(uv_tcp_init, r); + uv_handle_set_data(&sock->uv_handle.handle, sock); + /* This keeps the socket alive after everything else is gone */ + isc__nmsocket_attach(sock, &(isc_nmsocket_t *){ NULL }); + + r = uv_timer_init(&worker->loop, &sock->read_timer); + UV_RUNTIME_CHECK(uv_timer_init, r); + uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock); + + LOCK(&sock->parent->lock); + + r = uv_tcp_open(&sock->uv_handle.tcp, sock->fd); + if (r < 0) { + isc__nm_closesocket(sock->fd); + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_OPENFAIL]); + goto done; + } + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_OPEN]); + + if (sa_family == AF_INET6) { + flags = UV_TCP_IPV6ONLY; + } + +#ifdef _WIN32 + r = isc_uv_tcp_freebind(&sock->uv_handle.tcp, &sock->iface.type.sa, + flags); + if (r < 0) { + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_BINDFAIL]); + goto done; + } +#else + if (mgr->load_balance_sockets) { + r = isc_uv_tcp_freebind(&sock->uv_handle.tcp, + &sock->iface.type.sa, flags); + if (r < 0) { + isc__nm_incstats(sock->mgr, + sock->statsindex[STATID_BINDFAIL]); + goto done; + } + } else { + if (sock->parent->fd == -1) { + r = isc_uv_tcp_freebind(&sock->uv_handle.tcp, + &sock->iface.type.sa, flags); + if (r < 0) { + isc__nm_incstats(sock->mgr, STATID_BINDFAIL); + goto done; + } + sock->parent->uv_handle.tcp.flags = + sock->uv_handle.tcp.flags; + sock->parent->fd = sock->fd; + } else { + /* The socket is already bound, just copy the flags */ + sock->uv_handle.tcp.flags = + sock->parent->uv_handle.tcp.flags; + } + } +#endif + + /* + * The callback will run in the same thread uv_listen() was called + * from, so a race with tcpdns_connection_cb() isn't possible. + */ + r = uv_listen((uv_stream_t *)&sock->uv_handle.tcp, sock->backlog, + tcpdns_connection_cb); + if (r != 0) { + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_NETMGR, ISC_LOG_ERROR, + "uv_listen failed: %s", + isc_result_totext(isc__nm_uverr2result(r))); + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_BINDFAIL]); + goto done; + } + + atomic_store(&sock->listening, true); + +done: + result = isc__nm_uverr2result(r); + if (result != ISC_R_SUCCESS) { + sock->pquota = NULL; + } + + atomic_fetch_add(&sock->parent->rchildren, 1); + if (sock->parent->result == ISC_R_UNSET) { + sock->parent->result = result; + } + SIGNAL(&sock->parent->cond); + UNLOCK(&sock->parent->lock); + + isc_barrier_wait(&sock->parent->startlistening); +} + +static void +tcpdns_connection_cb(uv_stream_t *server, int status) { + isc_nmsocket_t *ssock = uv_handle_get_data((uv_handle_t *)server); + isc_result_t result; + isc_quota_t *quota = NULL; + + if (status != 0) { + result = isc__nm_uverr2result(status); + goto done; + } + + REQUIRE(VALID_NMSOCK(ssock)); + REQUIRE(ssock->tid == isc_nm_tid()); + + if (isc__nmsocket_closing(ssock)) { + result = ISC_R_CANCELED; + goto done; + } + + if (ssock->pquota != NULL) { + result = isc_quota_attach_cb(ssock->pquota, "a, + &ssock->quotacb); + if (result == ISC_R_QUOTA) { + isc__nm_incstats(ssock->mgr, + ssock->statsindex[STATID_ACCEPTFAIL]); + goto done; + } + } + + result = accept_connection(ssock, quota); +done: + isc__nm_accept_connection_log(result, can_log_tcpdns_quota()); +} + +void +isc__nm_tcpdns_stoplistening(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->type == isc_nm_tcpdnslistener); + + if (!atomic_compare_exchange_strong(&sock->closing, &(bool){ false }, + true)) + { + UNREACHABLE(); + } + + if (!isc__nm_in_netthread()) { + enqueue_stoplistening(sock); + } else { + stop_tcpdns_parent(sock); + } +} + +void +isc__nm_async_tcpdnsstop(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_tcpdnsstop_t *ievent = + (isc__netievent_tcpdnsstop_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + + UNUSED(worker); + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + + if (sock->parent != NULL) { + stop_tcpdns_child(sock); + return; + } + + stop_tcpdns_parent(sock); +} + +void +isc__nm_tcpdns_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(result != ISC_R_SUCCESS); + + isc__nmsocket_timer_stop(sock); + isc__nm_stop_reading(sock); + + if (!sock->recv_read) { + goto destroy; + } + sock->recv_read = false; + + if (sock->recv_cb != NULL) { + isc__nm_uvreq_t *req = isc__nm_get_read_req(sock, NULL); + isc__nmsocket_clearcb(sock); + isc__nm_readcb(sock, req, result); + } + +destroy: + isc__nmsocket_prep_destroy(sock); + + /* + * We need to detach from quota after the read callback function had a + * chance to be executed. + */ + if (sock->quota != NULL) { + isc_quota_detach(&sock->quota); + } +} + +void +isc__nm_tcpdns_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg) { + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + + isc_nmsocket_t *sock = handle->sock; + isc__netievent_tcpdnsread_t *ievent = NULL; + + REQUIRE(sock->type == isc_nm_tcpdnssocket); + REQUIRE(sock->statichandle == handle); + REQUIRE(sock->tid == isc_nm_tid()); + REQUIRE(!sock->recv_read); + + sock->recv_cb = cb; + sock->recv_cbarg = cbarg; + sock->recv_read = true; + if (sock->read_timeout == 0) { + sock->read_timeout = + (atomic_load(&sock->keepalive) + ? atomic_load(&sock->mgr->keepalive) + : atomic_load(&sock->mgr->idle)); + } + + ievent = isc__nm_get_netievent_tcpdnsread(sock->mgr, sock); + + /* + * This MUST be done asynchronously, no matter which thread we're + * in. The callback function for isc_nm_read() often calls + * isc_nm_read() again; if we tried to do that synchronously + * we'd clash in processbuffer() and grow the stack indefinitely. + */ + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); + + return; +} + +void +isc__nm_async_tcpdnsread(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_tcpdnsread_t *ievent = + (isc__netievent_tcpdnsread_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + isc_result_t result; + + UNUSED(worker); + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + + if (isc__nmsocket_closing(sock)) { + result = ISC_R_CANCELED; + } else { + result = isc__nm_process_sock_buffer(sock); + } + + if (result != ISC_R_SUCCESS) { + sock->reading = true; + isc__nm_failed_read_cb(sock, result, false); + } +} + +/* + * Process a single packet from the incoming buffer. + * + * Return ISC_R_SUCCESS and attach 'handlep' to a handle if something + * was processed; return ISC_R_NOMORE if there isn't a full message + * to be processed. + * + * The caller will need to unreference the handle. + */ +isc_result_t +isc__nm_tcpdns_processbuffer(isc_nmsocket_t *sock) { + size_t len; + isc__nm_uvreq_t *req = NULL; + isc_nmhandle_t *handle = NULL; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + + if (isc__nmsocket_closing(sock)) { + return (ISC_R_CANCELED); + } + + /* + * If we don't even have the length yet, we can't do + * anything. + */ + if (sock->buf_len < 2) { + return (ISC_R_NOMORE); + } + + /* + * Process the first packet from the buffer, leaving + * the rest (if any) for later. + */ + len = ntohs(*(uint16_t *)sock->buf); + if (len > sock->buf_len - 2) { + return (ISC_R_NOMORE); + } + + req = isc__nm_get_read_req(sock, NULL); + REQUIRE(VALID_UVREQ(req)); + + /* + * We need to launch the resume_processing after the buffer has + * been consumed, thus we need to delay the detaching the handle. + */ + isc_nmhandle_attach(req->handle, &handle); + + /* + * The callback will be called synchronously because the + * result is ISC_R_SUCCESS, so we don't need to have + * the buffer on the heap + */ + req->uvbuf.base = (char *)sock->buf + 2; + req->uvbuf.len = len; + + /* + * If isc__nm_tcpdns_read() was called, it will be satisfied by single + * DNS message in the next call. + */ + sock->recv_read = false; + + /* + * The assertion failure here means that there's a errnoneous extra + * nmhandle detach happening in the callback and resume_processing gets + * called while we are still processing the buffer. + */ + REQUIRE(sock->processing == false); + sock->processing = true; + isc__nm_readcb(sock, req, ISC_R_SUCCESS); + sock->processing = false; + + len += 2; + sock->buf_len -= len; + if (sock->buf_len > 0) { + memmove(sock->buf, sock->buf + len, sock->buf_len); + } + + isc_nmhandle_detach(&handle); + + return (ISC_R_SUCCESS); +} + +void +isc__nm_tcpdns_read_cb(uv_stream_t *stream, ssize_t nread, + const uv_buf_t *buf) { + isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)stream); + uint8_t *base = NULL; + size_t len; + isc_result_t result; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + REQUIRE(sock->reading); + REQUIRE(buf != NULL); + + if (isc__nmsocket_closing(sock)) { + isc__nm_failed_read_cb(sock, ISC_R_CANCELED, true); + goto free; + } + + if (nread < 0) { + if (nread != UV_EOF) { + isc__nm_incstats(sock->mgr, + sock->statsindex[STATID_RECVFAIL]); + } + + isc__nm_failed_read_cb(sock, isc__nm_uverr2result(nread), true); + goto free; + } + + base = (uint8_t *)buf->base; + len = nread; + + /* + * FIXME: We can avoid the memmove here if we know we have received full + * packet; e.g. we should be smarter, a.s. there are just few situations + * + * The tcp_alloc_buf should be smarter and point the uv_read_start to + * the position where previous read has ended in the sock->buf, that way + * the data could be read directly into sock->buf. + */ + + if (sock->buf_len + len > sock->buf_size) { + isc__nm_alloc_dnsbuf(sock, sock->buf_len + len); + } + memmove(sock->buf + sock->buf_len, base, len); + sock->buf_len += len; + + if (!atomic_load(&sock->client)) { + sock->read_timeout = atomic_load(&sock->mgr->idle); + } + + result = isc__nm_process_sock_buffer(sock); + if (result != ISC_R_SUCCESS) { + isc__nm_failed_read_cb(sock, result, true); + } +free: + if (nread < 0) { + /* + * The buffer may be a null buffer on error. + */ + if (buf->base == NULL && buf->len == 0) { + return; + } + } + + isc__nm_free_uvbuf(sock, buf); +} + +static void +quota_accept_cb(isc_quota_t *quota, void *sock0) { + isc_nmsocket_t *sock = (isc_nmsocket_t *)sock0; + + REQUIRE(VALID_NMSOCK(sock)); + + /* + * Create a tcpdnsaccept event and pass it using the async channel. + */ + + isc__netievent_tcpdnsaccept_t *ievent = + isc__nm_get_netievent_tcpdnsaccept(sock->mgr, sock, quota); + isc__nm_maybe_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); +} + +/* + * This is called after we get a quota_accept_cb() callback. + */ +void +isc__nm_async_tcpdnsaccept(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_tcpdnsaccept_t *ievent = + (isc__netievent_tcpdnsaccept_t *)ev0; + isc_result_t result; + + UNUSED(worker); + + REQUIRE(VALID_NMSOCK(ievent->sock)); + REQUIRE(ievent->sock->tid == isc_nm_tid()); + + result = accept_connection(ievent->sock, ievent->quota); + isc__nm_accept_connection_log(result, can_log_tcpdns_quota()); +} + +static isc_result_t +accept_connection(isc_nmsocket_t *ssock, isc_quota_t *quota) { + isc_nmsocket_t *csock = NULL; + isc__networker_t *worker = NULL; + int r; + isc_result_t result; + struct sockaddr_storage peer_ss; + struct sockaddr_storage local_ss; + isc_sockaddr_t local; + isc_nmhandle_t *handle = NULL; + + REQUIRE(VALID_NMSOCK(ssock)); + REQUIRE(ssock->tid == isc_nm_tid()); + + if (isc__nmsocket_closing(ssock)) { + if (quota != NULL) { + isc_quota_detach("a); + } + return (ISC_R_CANCELED); + } + + REQUIRE(ssock->accept_cb != NULL); + + csock = isc_mem_get(ssock->mgr->mctx, sizeof(isc_nmsocket_t)); + isc__nmsocket_init(csock, ssock->mgr, isc_nm_tcpdnssocket, + &ssock->iface); + csock->tid = ssock->tid; + csock->extrahandlesize = ssock->extrahandlesize; + isc__nmsocket_attach(ssock, &csock->server); + csock->recv_cb = ssock->recv_cb; + csock->recv_cbarg = ssock->recv_cbarg; + csock->quota = quota; + csock->accepting = true; + + worker = &csock->mgr->workers[csock->tid]; + + r = uv_tcp_init(&worker->loop, &csock->uv_handle.tcp); + UV_RUNTIME_CHECK(uv_tcp_init, r); + uv_handle_set_data(&csock->uv_handle.handle, csock); + + r = uv_timer_init(&worker->loop, &csock->read_timer); + UV_RUNTIME_CHECK(uv_timer_init, r); + uv_handle_set_data((uv_handle_t *)&csock->read_timer, csock); + + r = uv_accept(&ssock->uv_handle.stream, &csock->uv_handle.stream); + if (r != 0) { + result = isc__nm_uverr2result(r); + goto failure; + } + + r = uv_tcp_getpeername(&csock->uv_handle.tcp, + (struct sockaddr *)&peer_ss, + &(int){ sizeof(peer_ss) }); + if (r != 0) { + result = isc__nm_uverr2result(r); + goto failure; + } + + result = isc_sockaddr_fromsockaddr(&csock->peer, + (struct sockaddr *)&peer_ss); + if (result != ISC_R_SUCCESS) { + goto failure; + } + + r = uv_tcp_getsockname(&csock->uv_handle.tcp, + (struct sockaddr *)&local_ss, + &(int){ sizeof(local_ss) }); + if (r != 0) { + result = isc__nm_uverr2result(r); + goto failure; + } + + result = isc_sockaddr_fromsockaddr(&local, + (struct sockaddr *)&local_ss); + if (result != ISC_R_SUCCESS) { + goto failure; + } + + /* + * The handle will be either detached on acceptcb failure or in the + * readcb. + */ + handle = isc__nmhandle_get(csock, NULL, &local); + + result = ssock->accept_cb(handle, ISC_R_SUCCESS, ssock->accept_cbarg); + if (result != ISC_R_SUCCESS) { + isc_nmhandle_detach(&handle); + goto failure; + } + + csock->accepting = false; + + isc__nm_incstats(csock->mgr, csock->statsindex[STATID_ACCEPT]); + + csock->read_timeout = atomic_load(&csock->mgr->init); + + csock->closehandle_cb = isc__nm_resume_processing; + + /* + * We need to keep the handle alive until we fail to read or connection + * is closed by the other side, it will be detached via + * prep_destroy()->tcpdns_close_direct(). + */ + isc_nmhandle_attach(handle, &csock->recv_handle); + result = isc__nm_process_sock_buffer(csock); + if (result != ISC_R_SUCCESS) { + isc_nmhandle_detach(&csock->recv_handle); + isc_nmhandle_detach(&handle); + goto failure; + } + + /* + * The initial timer has been set, update the read timeout for the next + * reads. + */ + csock->read_timeout = (atomic_load(&csock->keepalive) + ? atomic_load(&csock->mgr->keepalive) + : atomic_load(&csock->mgr->idle)); + + isc_nmhandle_detach(&handle); + + /* + * sock is now attached to the handle. + */ + isc__nmsocket_detach(&csock); + + return (ISC_R_SUCCESS); + +failure: + + atomic_store(&csock->active, false); + + isc__nm_failed_accept_cb(csock, result); + + isc__nmsocket_prep_destroy(csock); + + isc__nmsocket_detach(&csock); + + return (result); +} + +void +isc__nm_tcpdns_send(isc_nmhandle_t *handle, isc_region_t *region, + isc_nm_cb_t cb, void *cbarg) { + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + + isc_nmsocket_t *sock = handle->sock; + isc__netievent_tcpdnssend_t *ievent = NULL; + isc__nm_uvreq_t *uvreq = NULL; + + REQUIRE(sock->type == isc_nm_tcpdnssocket); + + uvreq = isc__nm_uvreq_get(sock->mgr, sock); + *(uint16_t *)uvreq->tcplen = htons(region->length); + uvreq->uvbuf.base = (char *)region->base; + uvreq->uvbuf.len = region->length; + + isc_nmhandle_attach(handle, &uvreq->handle); + + uvreq->cb.send = cb; + uvreq->cbarg = cbarg; + + ievent = isc__nm_get_netievent_tcpdnssend(sock->mgr, sock, uvreq); + isc__nm_maybe_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); + + return; +} + +static void +tcpdns_send_cb(uv_write_t *req, int status) { + isc__nm_uvreq_t *uvreq = (isc__nm_uvreq_t *)req->data; + isc_nmsocket_t *sock = NULL; + + REQUIRE(VALID_UVREQ(uvreq)); + REQUIRE(VALID_NMSOCK(uvreq->sock)); + + sock = uvreq->sock; + + isc_nm_timer_stop(uvreq->timer); + isc_nm_timer_detach(&uvreq->timer); + + if (status < 0) { + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_SENDFAIL]); + isc__nm_failed_send_cb(sock, uvreq, + isc__nm_uverr2result(status)); + return; + } + + isc__nm_sendcb(sock, uvreq, ISC_R_SUCCESS, false); +} + +/* + * Handle 'tcpsend' async event - send a packet on the socket + */ +void +isc__nm_async_tcpdnssend(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_tcpdnssend_t *ievent = + (isc__netievent_tcpdnssend_t *)ev0; + + REQUIRE(VALID_UVREQ(ievent->req)); + REQUIRE(VALID_NMSOCK(ievent->sock)); + REQUIRE(ievent->sock->type == isc_nm_tcpdnssocket); + REQUIRE(ievent->sock->tid == isc_nm_tid()); + + isc_result_t result; + isc_nmsocket_t *sock = ievent->sock; + isc__nm_uvreq_t *uvreq = ievent->req; + + if (sock->write_timeout == 0) { + sock->write_timeout = + (atomic_load(&sock->keepalive) + ? atomic_load(&sock->mgr->keepalive) + : atomic_load(&sock->mgr->idle)); + } + + uv_buf_t bufs[2] = { { .base = uvreq->tcplen, .len = 2 }, + { .base = uvreq->uvbuf.base, + .len = uvreq->uvbuf.len } }; + int nbufs = 2; + int r; + + UNUSED(worker); + + if (isc__nmsocket_closing(sock)) { + result = ISC_R_CANCELED; + goto fail; + } + + r = uv_try_write(&sock->uv_handle.stream, bufs, nbufs); + + if (r == (int)(bufs[0].len + bufs[1].len)) { + /* Wrote everything */ + isc__nm_sendcb(sock, uvreq, ISC_R_SUCCESS, true); + return; + } + + if (r == 1) { + /* Partial write of DNSMSG length */ + bufs[0].base = uvreq->tcplen + 1; + bufs[0].len = 1; + } else if (r > 0) { + /* Partial write of DNSMSG */ + nbufs = 1; + bufs[0].base = uvreq->uvbuf.base + (r - 2); + bufs[0].len = uvreq->uvbuf.len - (r - 2); + } else if (r == UV_ENOSYS || r == UV_EAGAIN) { + /* uv_try_write not supported, send asynchronously */ + } else { + /* error sending data */ + result = isc__nm_uverr2result(r); + goto fail; + } + + r = uv_write(&uvreq->uv_req.write, &sock->uv_handle.stream, bufs, nbufs, + tcpdns_send_cb); + if (r < 0) { + result = isc__nm_uverr2result(r); + goto fail; + } + + isc_nm_timer_create(uvreq->handle, isc__nmsocket_writetimeout_cb, uvreq, + &uvreq->timer); + if (sock->write_timeout > 0) { + isc_nm_timer_start(uvreq->timer, sock->write_timeout); + } + + return; +fail: + if (result != ISC_R_SUCCESS) { + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_SENDFAIL]); + isc__nm_failed_send_cb(sock, uvreq, result); + } +} + +static void +tcpdns_stop_cb(uv_handle_t *handle) { + isc_nmsocket_t *sock = uv_handle_get_data(handle); + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + REQUIRE(atomic_load(&sock->closing)); + + uv_handle_set_data(handle, NULL); + + if (!atomic_compare_exchange_strong(&sock->closed, &(bool){ false }, + true)) + { + UNREACHABLE(); + } + + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_CLOSE]); + + atomic_store(&sock->listening, false); + + isc__nmsocket_detach(&sock); +} + +static void +tcpdns_close_sock(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + REQUIRE(atomic_load(&sock->closing)); + + if (!atomic_compare_exchange_strong(&sock->closed, &(bool){ false }, + true)) + { + UNREACHABLE(); + } + + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_CLOSE]); + + if (sock->server != NULL) { + isc__nmsocket_detach(&sock->server); + } + + atomic_store(&sock->connected, false); + + isc__nmsocket_prep_destroy(sock); +} + +static void +tcpdns_close_cb(uv_handle_t *handle) { + isc_nmsocket_t *sock = uv_handle_get_data(handle); + + uv_handle_set_data(handle, NULL); + + tcpdns_close_sock(sock); +} + +static void +read_timer_close_cb(uv_handle_t *timer) { + isc_nmsocket_t *sock = uv_handle_get_data(timer); + uv_handle_set_data(timer, NULL); + + REQUIRE(VALID_NMSOCK(sock)); + + if (sock->parent) { + uv_close(&sock->uv_handle.handle, tcpdns_stop_cb); + } else if (uv_is_closing(&sock->uv_handle.handle)) { + tcpdns_close_sock(sock); + } else { + uv_close(&sock->uv_handle.handle, tcpdns_close_cb); + } +} + +static void +stop_tcpdns_child(isc_nmsocket_t *sock) { + REQUIRE(sock->type == isc_nm_tcpdnssocket); + REQUIRE(sock->tid == isc_nm_tid()); + + if (!atomic_compare_exchange_strong(&sock->closing, &(bool){ false }, + true)) + { + return; + } + + tcpdns_close_direct(sock); + + atomic_fetch_sub(&sock->parent->rchildren, 1); + + isc_barrier_wait(&sock->parent->stoplistening); +} + +static void +stop_tcpdns_parent(isc_nmsocket_t *sock) { + isc_nmsocket_t *csock = NULL; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + REQUIRE(sock->type == isc_nm_tcpdnslistener); + + isc_barrier_init(&sock->stoplistening, sock->nchildren); + + for (size_t i = 0; i < sock->nchildren; i++) { + csock = &sock->children[i]; + REQUIRE(VALID_NMSOCK(csock)); + + if ((int)i == isc_nm_tid()) { + /* + * We need to schedule closing the other sockets first + */ + continue; + } + + atomic_store(&csock->active, false); + enqueue_stoplistening(csock); + } + + csock = &sock->children[isc_nm_tid()]; + atomic_store(&csock->active, false); + stop_tcpdns_child(csock); + + atomic_store(&sock->closed, true); + isc__nmsocket_prep_destroy(sock); +} + +static void +tcpdns_close_direct(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + REQUIRE(atomic_load(&sock->closing)); + + if (sock->quota != NULL) { + isc_quota_detach(&sock->quota); + } + + if (sock->recv_handle != NULL) { + isc_nmhandle_detach(&sock->recv_handle); + } + + isc__nmsocket_timer_stop(sock); + isc__nm_stop_reading(sock); + + uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock); + uv_close((uv_handle_t *)&sock->read_timer, read_timer_close_cb); +} + +void +isc__nm_tcpdns_close(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->type == isc_nm_tcpdnssocket); + REQUIRE(!isc__nmsocket_active(sock)); + + if (!atomic_compare_exchange_strong(&sock->closing, &(bool){ false }, + true)) + { + return; + } + + if (sock->tid == isc_nm_tid()) { + tcpdns_close_direct(sock); + } else { + /* + * We need to create an event and pass it using async channel + */ + isc__netievent_tcpdnsclose_t *ievent = + isc__nm_get_netievent_tcpdnsclose(sock->mgr, sock); + + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); + } +} + +void +isc__nm_async_tcpdnsclose(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_tcpdnsclose_t *ievent = + (isc__netievent_tcpdnsclose_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + + UNUSED(worker); + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + + tcpdns_close_direct(sock); +} + +static void +tcpdns_close_connect_cb(uv_handle_t *handle) { + isc_nmsocket_t *sock = uv_handle_get_data(handle); + + REQUIRE(VALID_NMSOCK(sock)); + + REQUIRE(isc__nm_in_netthread()); + REQUIRE(sock->tid == isc_nm_tid()); + + isc__nmsocket_prep_destroy(sock); + isc__nmsocket_detach(&sock); +} + +void +isc__nm_tcpdns_shutdown(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + REQUIRE(sock->type == isc_nm_tcpdnssocket); + + /* + * If the socket is active, mark it inactive and + * continue. If it isn't active, stop now. + */ + if (!isc__nmsocket_deactivate(sock)) { + return; + } + + if (sock->accepting) { + return; + } + + if (atomic_load(&sock->connecting)) { + isc_nmsocket_t *tsock = NULL; + isc__nmsocket_attach(sock, &tsock); + uv_close(&sock->uv_handle.handle, tcpdns_close_connect_cb); + return; + } + + if (sock->statichandle != NULL) { + isc__nm_failed_read_cb(sock, ISC_R_CANCELED, false); + return; + } + + /* + * Otherwise, we just send the socket to abyss... + */ + if (sock->parent == NULL) { + isc__nmsocket_prep_destroy(sock); + } +} + +void +isc__nm_tcpdns_cancelread(isc_nmhandle_t *handle) { + isc_nmsocket_t *sock = NULL; + isc__netievent_tcpdnscancel_t *ievent = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + + sock = handle->sock; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->type == isc_nm_tcpdnssocket); + + ievent = isc__nm_get_netievent_tcpdnscancel(sock->mgr, sock, handle); + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); +} + +void +isc__nm_async_tcpdnscancel(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_tcpdnscancel_t *ievent = + (isc__netievent_tcpdnscancel_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + + UNUSED(worker); + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + + isc__nm_failed_read_cb(sock, ISC_R_EOF, false); +} + +void +isc_nm_tcpdns_sequential(isc_nmhandle_t *handle) { + isc_nmsocket_t *sock = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + REQUIRE(handle->sock->type == isc_nm_tcpdnssocket); + + sock = handle->sock; + + /* + * We don't want pipelining on this connection. That means + * that we need to pause after reading each request, and + * resume only after the request has been processed. This + * is done in resume_processing(), which is the socket's + * closehandle_cb callback, called whenever a handle + * is released. + */ + + isc__nmsocket_timer_stop(sock); + isc__nm_stop_reading(sock); + atomic_store(&sock->sequential, true); +} diff --git a/lib/isc/netmgr/udp.c b/lib/isc/netmgr/udp.c new file mode 100644 index 0000000..00f9d40 --- /dev/null +++ b/lib/isc/netmgr/udp.c @@ -0,0 +1,1211 @@ +/* + * 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. + */ + +#include <unistd.h> +#include <uv.h> + +#include <isc/atomic.h> +#include <isc/barrier.h> +#include <isc/buffer.h> +#include <isc/condition.h> +#include <isc/errno.h> +#include <isc/magic.h> +#include <isc/mem.h> +#include <isc/netmgr.h> +#include <isc/random.h> +#include <isc/refcount.h> +#include <isc/region.h> +#include <isc/result.h> +#include <isc/sockaddr.h> +#include <isc/thread.h> +#include <isc/util.h> + +#include "netmgr-int.h" +#include "uv-compat.h" + +static isc_result_t +udp_send_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req, + isc_sockaddr_t *peer); + +static void +udp_recv_cb(uv_udp_t *handle, ssize_t nrecv, const uv_buf_t *buf, + const struct sockaddr *addr, unsigned flags); + +static void +udp_send_cb(uv_udp_send_t *req, int status); + +static void +udp_close_cb(uv_handle_t *handle); + +static void +read_timer_close_cb(uv_handle_t *handle); + +static void +udp_close_direct(isc_nmsocket_t *sock); + +static void +stop_udp_parent(isc_nmsocket_t *sock); +static void +stop_udp_child(isc_nmsocket_t *sock); + +static uv_os_sock_t +isc__nm_udp_lb_socket(isc_nm_t *mgr, sa_family_t sa_family) { + isc_result_t result; + uv_os_sock_t sock; + + result = isc__nm_socket(sa_family, SOCK_DGRAM, 0, &sock); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + (void)isc__nm_socket_incoming_cpu(sock); + (void)isc__nm_socket_disable_pmtud(sock, sa_family); + + result = isc__nm_socket_reuse(sock); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + +#ifndef _WIN32 + if (mgr->load_balance_sockets) { + result = isc__nm_socket_reuse_lb(sock); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } +#endif + + return (sock); +} + +static void +start_udp_child(isc_nm_t *mgr, isc_sockaddr_t *iface, isc_nmsocket_t *sock, + uv_os_sock_t fd, int tid) { + isc_nmsocket_t *csock; + isc__netievent_udplisten_t *ievent = NULL; + + csock = &sock->children[tid]; + + isc__nmsocket_init(csock, mgr, isc_nm_udpsocket, iface); + csock->parent = sock; + csock->iface = sock->iface; + csock->reading = true; + csock->recv_cb = sock->recv_cb; + csock->recv_cbarg = sock->recv_cbarg; + csock->extrahandlesize = sock->extrahandlesize; + csock->tid = tid; + +#ifdef _WIN32 + UNUSED(fd); + csock->fd = isc__nm_udp_lb_socket(mgr, iface->type.sa.sa_family); +#else + if (mgr->load_balance_sockets) { + UNUSED(fd); + csock->fd = isc__nm_udp_lb_socket(mgr, + iface->type.sa.sa_family); + } else { + csock->fd = dup(fd); + } +#endif + REQUIRE(csock->fd >= 0); + + ievent = isc__nm_get_netievent_udplisten(mgr, csock); + isc__nm_maybe_enqueue_ievent(&mgr->workers[tid], + (isc__netievent_t *)ievent); +} + +static void +enqueue_stoplistening(isc_nmsocket_t *sock) { + isc__netievent_udpstop_t *ievent = + isc__nm_get_netievent_udpstop(sock->mgr, sock); + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); +} + +isc_result_t +isc_nm_listenudp(isc_nm_t *mgr, isc_sockaddr_t *iface, isc_nm_recv_cb_t cb, + void *cbarg, size_t extrahandlesize, isc_nmsocket_t **sockp) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *sock = NULL; + size_t children_size = 0; + REQUIRE(VALID_NM(mgr)); + uv_os_sock_t fd = -1; + + /* + * We are creating mgr->nworkers duplicated sockets, one + * socket for each worker thread. + */ + sock = isc_mem_get(mgr->mctx, sizeof(isc_nmsocket_t)); + isc__nmsocket_init(sock, mgr, isc_nm_udplistener, iface); + + atomic_init(&sock->rchildren, 0); +#if defined(WIN32) + sock->nchildren = 1; +#else + sock->nchildren = mgr->nworkers; +#endif + + children_size = sock->nchildren * sizeof(sock->children[0]); + sock->children = isc_mem_get(mgr->mctx, children_size); + memset(sock->children, 0, children_size); + + sock->recv_cb = cb; + sock->recv_cbarg = cbarg; + sock->extrahandlesize = extrahandlesize; + sock->result = ISC_R_UNSET; + + sock->tid = 0; + sock->fd = -1; + +#ifndef _WIN32 + if (!mgr->load_balance_sockets) { + fd = isc__nm_udp_lb_socket(mgr, iface->type.sa.sa_family); + } +#endif + + isc_barrier_init(&sock->startlistening, sock->nchildren); + + for (size_t i = 0; i < sock->nchildren; i++) { + if ((int)i == isc_nm_tid()) { + continue; + } + start_udp_child(mgr, iface, sock, fd, i); + } + + if (isc__nm_in_netthread()) { + start_udp_child(mgr, iface, sock, fd, isc_nm_tid()); + } + +#ifndef _WIN32 + if (!mgr->load_balance_sockets) { + isc__nm_closesocket(fd); + } +#endif + + LOCK(&sock->lock); + while (atomic_load(&sock->rchildren) != sock->nchildren) { + WAIT(&sock->cond, &sock->lock); + } + result = sock->result; + atomic_store(&sock->active, true); + UNLOCK(&sock->lock); + + INSIST(result != ISC_R_UNSET); + + if (result == ISC_R_SUCCESS) { + REQUIRE(atomic_load(&sock->rchildren) == sock->nchildren); + *sockp = sock; + } else { + atomic_store(&sock->active, false); + enqueue_stoplistening(sock); + isc_nmsocket_close(&sock); + } + + return (result); +} + +/* + * Asynchronous 'udplisten' call handler: start listening on a UDP socket. + */ +void +isc__nm_async_udplisten(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_udplisten_t *ievent = (isc__netievent_udplisten_t *)ev0; + isc_nmsocket_t *sock = NULL; + int r, uv_bind_flags = 0; + int uv_init_flags = 0; + sa_family_t sa_family; + isc_result_t result = ISC_R_UNSET; + isc_nm_t *mgr = NULL; + + REQUIRE(VALID_NMSOCK(ievent->sock)); + REQUIRE(ievent->sock->tid == isc_nm_tid()); + REQUIRE(VALID_NMSOCK(ievent->sock->parent)); + + sock = ievent->sock; + sa_family = sock->iface.type.sa.sa_family; + mgr = sock->mgr; + + REQUIRE(sock->type == isc_nm_udpsocket); + REQUIRE(sock->parent != NULL); + REQUIRE(sock->tid == isc_nm_tid()); + +#if HAVE_DECL_UV_UDP_RECVMMSG + uv_init_flags |= UV_UDP_RECVMMSG; +#endif + r = uv_udp_init_ex(&worker->loop, &sock->uv_handle.udp, uv_init_flags); + UV_RUNTIME_CHECK(uv_udp_init_ex, r); + uv_handle_set_data(&sock->uv_handle.handle, sock); + /* This keeps the socket alive after everything else is gone */ + isc__nmsocket_attach(sock, &(isc_nmsocket_t *){ NULL }); + + r = uv_timer_init(&worker->loop, &sock->read_timer); + UV_RUNTIME_CHECK(uv_timer_init, r); + uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock); + + LOCK(&sock->parent->lock); + + r = uv_udp_open(&sock->uv_handle.udp, sock->fd); + if (r < 0) { + isc__nm_closesocket(sock->fd); + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_OPENFAIL]); + goto done; + } + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_OPEN]); + + if (sa_family == AF_INET6) { + uv_bind_flags |= UV_UDP_IPV6ONLY; + } + +#ifdef _WIN32 + r = isc_uv_udp_freebind(&sock->uv_handle.udp, + &sock->parent->iface.type.sa, uv_bind_flags); + if (r < 0) { + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_BINDFAIL]); + goto done; + } +#else + if (mgr->load_balance_sockets) { + r = isc_uv_udp_freebind(&sock->uv_handle.udp, + &sock->parent->iface.type.sa, + uv_bind_flags); + if (r < 0) { + isc__nm_incstats(sock->mgr, + sock->statsindex[STATID_BINDFAIL]); + goto done; + } + } else { + if (sock->parent->fd == -1) { + /* This thread is first, bind the socket */ + r = isc_uv_udp_freebind(&sock->uv_handle.udp, + &sock->parent->iface.type.sa, + uv_bind_flags); + if (r < 0) { + isc__nm_incstats(sock->mgr, STATID_BINDFAIL); + goto done; + } + sock->parent->uv_handle.udp.flags = + sock->uv_handle.udp.flags; + sock->parent->fd = sock->fd; + } else { + /* The socket is already bound, just copy the flags */ + sock->uv_handle.udp.flags = + sock->parent->uv_handle.udp.flags; + } + } +#endif + +#ifdef ISC_RECV_BUFFER_SIZE + uv_recv_buffer_size(&sock->uv_handle.handle, + &(int){ ISC_RECV_BUFFER_SIZE }); +#endif +#ifdef ISC_SEND_BUFFER_SIZE + uv_send_buffer_size(&sock->uv_handle.handle, + &(int){ ISC_SEND_BUFFER_SIZE }); +#endif + r = uv_udp_recv_start(&sock->uv_handle.udp, isc__nm_alloc_cb, + udp_recv_cb); + if (r != 0) { + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_BINDFAIL]); + goto done; + } + + atomic_store(&sock->listening, true); + +done: + result = isc__nm_uverr2result(r); + atomic_fetch_add(&sock->parent->rchildren, 1); + if (sock->parent->result == ISC_R_UNSET) { + sock->parent->result = result; + } + SIGNAL(&sock->parent->cond); + UNLOCK(&sock->parent->lock); + + isc_barrier_wait(&sock->parent->startlistening); +} + +void +isc__nm_udp_stoplistening(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->type == isc_nm_udplistener); + + if (!atomic_compare_exchange_strong(&sock->closing, &(bool){ false }, + true)) + { + UNREACHABLE(); + } + + if (!isc__nm_in_netthread()) { + enqueue_stoplistening(sock); + } else { + stop_udp_parent(sock); + } +} + +/* + * Asynchronous 'udpstop' call handler: stop listening on a UDP socket. + */ +void +isc__nm_async_udpstop(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_udpstop_t *ievent = (isc__netievent_udpstop_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + + UNUSED(worker); + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + + if (sock->parent != NULL) { + stop_udp_child(sock); + return; + } + + stop_udp_parent(sock); +} + +/* + * udp_recv_cb handles incoming UDP packet from uv. The buffer here is + * reused for a series of packets, so we need to allocate a new one. + * This new one can be reused to send the response then. + */ +static void +udp_recv_cb(uv_udp_t *handle, ssize_t nrecv, const uv_buf_t *buf, + const struct sockaddr *addr, unsigned flags) { + isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)handle); + isc__nm_uvreq_t *req = NULL; + uint32_t maxudp; + isc_sockaddr_t sockaddr; + isc_result_t result; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + REQUIRE(sock->reading); + + /* + * When using recvmmsg(2), if no errors occur, there will be a final + * callback with nrecv set to 0, addr set to NULL and the buffer + * pointing at the initially allocated data with the UV_UDP_MMSG_CHUNK + * flag cleared and the UV_UDP_MMSG_FREE flag set. + */ +#if HAVE_DECL_UV_UDP_MMSG_FREE + if ((flags & UV_UDP_MMSG_FREE) == UV_UDP_MMSG_FREE) { + INSIST(nrecv == 0); + INSIST(addr == NULL); + goto free; + } +#else + UNUSED(flags); +#endif + + /* + * - If we're simulating a firewall blocking UDP packets + * bigger than 'maxudp' bytes for testing purposes. + */ + maxudp = atomic_load(&sock->mgr->maxudp); + if ((maxudp != 0 && (uint32_t)nrecv > maxudp)) { + /* + * We need to keep the read_cb intact in case, so the + * readtimeout_cb can trigger and not crash because of + * missing read_req. + */ + goto free; + } + + /* + * - If addr == NULL, in which case it's the end of stream; + * we can free the buffer and bail. + */ + if (addr == NULL) { + isc__nm_failed_read_cb(sock, ISC_R_EOF, false); + goto free; + } + + /* + * - If the socket is no longer active. + */ + if (!isc__nmsocket_active(sock)) { + isc__nm_failed_read_cb(sock, ISC_R_CANCELED, false); + goto free; + } + + if (nrecv < 0) { + isc__nm_failed_read_cb(sock, isc__nm_uverr2result(nrecv), + false); + goto free; + } + + result = isc_sockaddr_fromsockaddr(&sockaddr, addr); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + req = isc__nm_get_read_req(sock, &sockaddr); + + /* + * The callback will be called synchronously, because result is + * ISC_R_SUCCESS, so we are ok of passing the buf directly. + */ + req->uvbuf.base = buf->base; + req->uvbuf.len = nrecv; + + sock->recv_read = false; + + REQUIRE(!sock->processing); + sock->processing = true; + isc__nm_readcb(sock, req, ISC_R_SUCCESS); + sock->processing = false; + +free: +#if HAVE_DECL_UV_UDP_MMSG_CHUNK + /* + * When using recvmmsg(2), chunks will have the UV_UDP_MMSG_CHUNK flag + * set, those must not be freed. + */ + if ((flags & UV_UDP_MMSG_CHUNK) == UV_UDP_MMSG_CHUNK) { + return; + } +#endif + + /* + * When using recvmmsg(2), if a UDP socket error occurs, nrecv will be < + * 0. In either scenario, the callee can now safely free the provided + * buffer. + */ + if (nrecv < 0) { + /* + * The buffer may be a null buffer on error. + */ + if (buf->base == NULL && buf->len == 0) { + return; + } + } + + isc__nm_free_uvbuf(sock, buf); +} + +/* + * Send the data in 'region' to a peer via a UDP socket. We try to find + * a proper sibling/child socket so that we won't have to jump to + * another thread. + */ +void +isc__nm_udp_send(isc_nmhandle_t *handle, const isc_region_t *region, + isc_nm_cb_t cb, void *cbarg) { + isc_nmsocket_t *sock = handle->sock; + isc_nmsocket_t *rsock = NULL; + isc_sockaddr_t *peer = &handle->peer; + isc__nm_uvreq_t *uvreq = NULL; + uint32_t maxudp = atomic_load(&sock->mgr->maxudp); + int ntid; + + INSIST(sock->type == isc_nm_udpsocket); + + /* + * We're simulating a firewall blocking UDP packets bigger than + * 'maxudp' bytes, for testing purposes. + * + * The client would ordinarily have unreferenced the handle + * in the callback, but that won't happen in this case, so + * we need to do so here. + */ + if (maxudp != 0 && region->length > maxudp) { + isc_nmhandle_detach(&handle); + return; + } + + if (atomic_load(&sock->client)) { + /* + * When we are sending from the client socket, we directly use + * the socket provided. + */ + rsock = sock; + goto send; + } else { + /* + * When we are sending from the server socket, we either use the + * socket associated with the network thread we are in, or we + * use the thread from the socket associated with the handle. + */ + INSIST(sock->parent != NULL); + +#if defined(WIN32) + /* On Windows, we have only a single listening listener */ + rsock = sock; +#else + if (isc__nm_in_netthread()) { + ntid = isc_nm_tid(); + } else { + ntid = sock->tid; + } + rsock = &sock->parent->children[ntid]; +#endif + } + +send: + uvreq = isc__nm_uvreq_get(rsock->mgr, rsock); + uvreq->uvbuf.base = (char *)region->base; + uvreq->uvbuf.len = region->length; + + isc_nmhandle_attach(handle, &uvreq->handle); + + uvreq->cb.send = cb; + uvreq->cbarg = cbarg; + + if (isc_nm_tid() == rsock->tid) { + REQUIRE(rsock->tid == isc_nm_tid()); + isc__netievent_udpsend_t ievent = { .sock = rsock, + .req = uvreq, + .peer = *peer }; + + isc__nm_async_udpsend(NULL, (isc__netievent_t *)&ievent); + } else { + isc__netievent_udpsend_t *ievent = + isc__nm_get_netievent_udpsend(sock->mgr, rsock); + ievent->peer = *peer; + ievent->req = uvreq; + + isc__nm_enqueue_ievent(&sock->mgr->workers[rsock->tid], + (isc__netievent_t *)ievent); + } +} + +/* + * Asynchronous 'udpsend' event handler: send a packet on a UDP socket. + */ +void +isc__nm_async_udpsend(isc__networker_t *worker, isc__netievent_t *ev0) { + isc_result_t result; + isc__netievent_udpsend_t *ievent = (isc__netievent_udpsend_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + isc__nm_uvreq_t *uvreq = ievent->req; + + REQUIRE(sock->type == isc_nm_udpsocket); + REQUIRE(sock->tid == isc_nm_tid()); + UNUSED(worker); + + if (isc__nmsocket_closing(sock)) { + isc__nm_failed_send_cb(sock, uvreq, ISC_R_CANCELED); + return; + } + + result = udp_send_direct(sock, uvreq, &ievent->peer); + if (result != ISC_R_SUCCESS) { + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_SENDFAIL]); + isc__nm_failed_send_cb(sock, uvreq, result); + } +} + +static void +udp_send_cb(uv_udp_send_t *req, int status) { + isc_result_t result = ISC_R_SUCCESS; + isc__nm_uvreq_t *uvreq = uv_handle_get_data((uv_handle_t *)req); + isc_nmsocket_t *sock = NULL; + + REQUIRE(VALID_UVREQ(uvreq)); + REQUIRE(VALID_NMHANDLE(uvreq->handle)); + + sock = uvreq->sock; + + REQUIRE(sock->tid == isc_nm_tid()); + + if (status < 0) { + result = isc__nm_uverr2result(status); + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_SENDFAIL]); + } + + isc__nm_sendcb(sock, uvreq, result, false); +} + +/* + * udp_send_direct sends buf to a peer on a socket. Sock has to be in + * the same thread as the callee. + */ +static isc_result_t +udp_send_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req, + isc_sockaddr_t *peer) { + const struct sockaddr *sa = &peer->type.sa; + int r; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(VALID_UVREQ(req)); + REQUIRE(sock->tid == isc_nm_tid()); + REQUIRE(sock->type == isc_nm_udpsocket); + + if (isc__nmsocket_closing(sock)) { + return (ISC_R_CANCELED); + } + +#if UV_VERSION_HEX >= UV_VERSION(1, 27, 0) + /* + * If we used uv_udp_connect() (and not the shim version for + * older versions of libuv), then the peer address has to be + * set to NULL or else uv_udp_send() could fail or assert, + * depending on the libuv version. + */ + if (atomic_load(&sock->connected)) { + sa = NULL; + } +#endif + + r = uv_udp_send(&req->uv_req.udp_send, &sock->uv_handle.udp, + &req->uvbuf, 1, sa, udp_send_cb); + if (r < 0) { + return (isc__nm_uverr2result(r)); + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +udp_connect_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req) { + isc__networker_t *worker = NULL; + int uv_bind_flags = UV_UDP_REUSEADDR; + isc_result_t result = ISC_R_UNSET; + int tries = 3; + int r; + + REQUIRE(isc__nm_in_netthread()); + REQUIRE(sock->tid == isc_nm_tid()); + + worker = &sock->mgr->workers[isc_nm_tid()]; + + atomic_store(&sock->connecting, true); + + r = uv_udp_init(&worker->loop, &sock->uv_handle.udp); + UV_RUNTIME_CHECK(uv_udp_init, r); + uv_handle_set_data(&sock->uv_handle.handle, sock); + + r = uv_timer_init(&worker->loop, &sock->read_timer); + UV_RUNTIME_CHECK(uv_timer_init, r); + uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock); + + r = uv_udp_open(&sock->uv_handle.udp, sock->fd); + if (r != 0) { + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_OPENFAIL]); + goto done; + } + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_OPEN]); + + if (sock->iface.type.sa.sa_family == AF_INET6) { + uv_bind_flags |= UV_UDP_IPV6ONLY; + } + + r = uv_udp_bind(&sock->uv_handle.udp, &sock->iface.type.sa, + uv_bind_flags); + if (r != 0) { + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_BINDFAIL]); + goto done; + } + +#ifdef ISC_RECV_BUFFER_SIZE + uv_recv_buffer_size(&sock->uv_handle.handle, + &(int){ ISC_RECV_BUFFER_SIZE }); +#endif +#ifdef ISC_SEND_BUFFER_SIZE + uv_send_buffer_size(&sock->uv_handle.handle, + &(int){ ISC_SEND_BUFFER_SIZE }); +#endif + + /* + * On FreeBSD the UDP connect() call sometimes results in a + * spurious transient EADDRINUSE. Try a few more times before + * giving up. + */ + do { + r = isc_uv_udp_connect(&sock->uv_handle.udp, + &req->peer.type.sa); + } while (r == UV_EADDRINUSE && --tries > 0); + if (r != 0) { + isc__nm_incstats(sock->mgr, + sock->statsindex[STATID_CONNECTFAIL]); + goto done; + } + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_CONNECT]); + + atomic_store(&sock->connecting, false); + atomic_store(&sock->connected, true); + +done: + result = isc__nm_uverr2result(r); + + LOCK(&sock->lock); + sock->result = result; + SIGNAL(&sock->cond); + if (!atomic_load(&sock->active)) { + WAIT(&sock->scond, &sock->lock); + } + INSIST(atomic_load(&sock->active)); + UNLOCK(&sock->lock); + + return (result); +} + +/* + * Asynchronous 'udpconnect' call handler: open a new UDP socket and + * call the 'open' callback with a handle. + */ +void +isc__nm_async_udpconnect(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_udpconnect_t *ievent = + (isc__netievent_udpconnect_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + isc__nm_uvreq_t *req = ievent->req; + isc_result_t result; + + UNUSED(worker); + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->type == isc_nm_udpsocket); + REQUIRE(sock->parent == NULL); + REQUIRE(sock->tid == isc_nm_tid()); + + result = udp_connect_direct(sock, req); + if (result != ISC_R_SUCCESS) { + atomic_store(&sock->active, false); + isc__nm_udp_close(sock); + isc__nm_connectcb(sock, req, result, true); + } else { + /* + * The callback has to be called after the socket has been + * initialized + */ + isc__nm_connectcb(sock, req, ISC_R_SUCCESS, true); + } + + /* + * The sock is now attached to the handle. + */ + isc__nmsocket_detach(&sock); +} + +void +isc_nm_udpconnect(isc_nm_t *mgr, isc_sockaddr_t *local, isc_sockaddr_t *peer, + isc_nm_cb_t cb, void *cbarg, unsigned int timeout, + size_t extrahandlesize) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *sock = NULL; + isc__netievent_udpconnect_t *event = NULL; + isc__nm_uvreq_t *req = NULL; + sa_family_t sa_family; + + REQUIRE(VALID_NM(mgr)); + REQUIRE(local != NULL); + REQUIRE(peer != NULL); + + sa_family = peer->type.sa.sa_family; + + sock = isc_mem_get(mgr->mctx, sizeof(isc_nmsocket_t)); + isc__nmsocket_init(sock, mgr, isc_nm_udpsocket, local); + + sock->connect_cb = cb; + sock->connect_cbarg = cbarg; + sock->read_timeout = timeout; + sock->extrahandlesize = extrahandlesize; + sock->peer = *peer; + sock->result = ISC_R_UNSET; + atomic_init(&sock->client, true); + + req = isc__nm_uvreq_get(mgr, sock); + req->cb.connect = cb; + req->cbarg = cbarg; + req->peer = *peer; + req->local = *local; + req->handle = isc__nmhandle_get(sock, &req->peer, &sock->iface); + + result = isc__nm_socket(sa_family, SOCK_DGRAM, 0, &sock->fd); + if (result != ISC_R_SUCCESS) { + if (isc__nm_in_netthread()) { + sock->tid = isc_nm_tid(); + } + isc__nmsocket_clearcb(sock); + isc__nm_connectcb(sock, req, result, true); + atomic_store(&sock->closed, true); + isc__nmsocket_detach(&sock); + return; + } + + result = isc__nm_socket_reuse(sock->fd); + RUNTIME_CHECK(result == ISC_R_SUCCESS || + result == ISC_R_NOTIMPLEMENTED); + + result = isc__nm_socket_reuse_lb(sock->fd); + RUNTIME_CHECK(result == ISC_R_SUCCESS || + result == ISC_R_NOTIMPLEMENTED); + + (void)isc__nm_socket_incoming_cpu(sock->fd); + + (void)isc__nm_socket_disable_pmtud(sock->fd, sa_family); + + event = isc__nm_get_netievent_udpconnect(mgr, sock, req); + + if (isc__nm_in_netthread()) { + atomic_store(&sock->active, true); + sock->tid = isc_nm_tid(); + isc__nm_async_udpconnect(&mgr->workers[sock->tid], + (isc__netievent_t *)event); + isc__nm_put_netievent_udpconnect(mgr, event); + } else { + atomic_init(&sock->active, false); + sock->tid = isc_random_uniform(mgr->nworkers); + isc__nm_enqueue_ievent(&mgr->workers[sock->tid], + (isc__netievent_t *)event); + } + LOCK(&sock->lock); + while (sock->result == ISC_R_UNSET) { + WAIT(&sock->cond, &sock->lock); + } + atomic_store(&sock->active, true); + BROADCAST(&sock->scond); + UNLOCK(&sock->lock); +} + +void +isc__nm_udp_read_cb(uv_udp_t *handle, ssize_t nrecv, const uv_buf_t *buf, + const struct sockaddr *addr, unsigned flags) { + isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)handle); + REQUIRE(VALID_NMSOCK(sock)); + + udp_recv_cb(handle, nrecv, buf, addr, flags); + /* + * If a caller calls isc_nm_read() on a listening socket, we can + * get here, but we MUST NOT stop reading from the listener + * socket. The only difference between listener and connected + * sockets is that the former has sock->parent set and later + * does not. + */ + if (!sock->parent) { + isc__nm_stop_reading(sock); + } +} + +void +isc__nm_udp_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(result != ISC_R_SUCCESS); + + if (atomic_load(&sock->client)) { + isc__nmsocket_timer_stop(sock); + isc__nm_stop_reading(sock); + + if (!sock->recv_read) { + goto destroy; + } + sock->recv_read = false; + + if (sock->recv_cb != NULL) { + isc__nm_uvreq_t *req = isc__nm_get_read_req(sock, NULL); + isc__nmsocket_clearcb(sock); + isc__nm_readcb(sock, req, result); + } + + destroy: + isc__nmsocket_prep_destroy(sock); + return; + } + + /* + * For UDP server socket, we don't have child socket via + * "accept", so we: + * - we continue to read + * - we don't clear the callbacks + * - we don't destroy it (only stoplistening could do that) + */ + if (!sock->recv_read) { + return; + } + sock->recv_read = false; + + if (sock->recv_cb != NULL) { + isc__nm_uvreq_t *req = isc__nm_get_read_req(sock, NULL); + isc__nm_readcb(sock, req, result); + } +} + +/* + * Asynchronous 'udpread' call handler: start or resume reading on a + * socket; pause reading and call the 'recv' callback after each + * datagram. + */ +void +isc__nm_async_udpread(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_udpread_t *ievent = (isc__netievent_udpread_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + isc_result_t result; + + UNUSED(worker); + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + + if (isc__nmsocket_closing(sock)) { + result = ISC_R_CANCELED; + } else { + result = isc__nm_start_reading(sock); + } + + if (result != ISC_R_SUCCESS) { + sock->reading = true; + isc__nm_failed_read_cb(sock, result, false); + return; + } + + isc__nmsocket_timer_start(sock); +} + +void +isc__nm_udp_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg) { + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + + isc_nmsocket_t *sock = handle->sock; + + REQUIRE(sock->type == isc_nm_udpsocket); + REQUIRE(sock->statichandle == handle); + REQUIRE(sock->tid == isc_nm_tid()); + REQUIRE(!sock->recv_read); + + sock->recv_cb = cb; + sock->recv_cbarg = cbarg; + sock->recv_read = true; + + if (!sock->reading && sock->tid == isc_nm_tid()) { + isc__netievent_udpread_t ievent = { .sock = sock }; + isc__nm_async_udpread(NULL, (isc__netievent_t *)&ievent); + } else { + isc__netievent_udpread_t *ievent = + isc__nm_get_netievent_udpread(sock->mgr, sock); + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); + } +} + +static void +udp_stop_cb(uv_handle_t *handle) { + isc_nmsocket_t *sock = uv_handle_get_data(handle); + uv_handle_set_data(handle, NULL); + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + REQUIRE(atomic_load(&sock->closing)); + + if (!atomic_compare_exchange_strong(&sock->closed, &(bool){ false }, + true)) + { + UNREACHABLE(); + } + + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_CLOSE]); + + atomic_store(&sock->listening, false); + + isc__nmsocket_detach(&sock); +} + +static void +udp_close_cb(uv_handle_t *handle) { + isc_nmsocket_t *sock = uv_handle_get_data(handle); + uv_handle_set_data(handle, NULL); + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + REQUIRE(atomic_load(&sock->closing)); + + if (!atomic_compare_exchange_strong(&sock->closed, &(bool){ false }, + true)) + { + UNREACHABLE(); + } + + isc__nm_incstats(sock->mgr, sock->statsindex[STATID_CLOSE]); + + if (sock->server != NULL) { + isc__nmsocket_detach(&sock->server); + } + + atomic_store(&sock->connected, false); + atomic_store(&sock->listening, false); + + isc__nmsocket_prep_destroy(sock); +} + +static void +read_timer_close_cb(uv_handle_t *handle) { + isc_nmsocket_t *sock = uv_handle_get_data(handle); + uv_handle_set_data(handle, NULL); + + if (sock->parent) { + uv_close(&sock->uv_handle.handle, udp_stop_cb); + } else { + uv_close(&sock->uv_handle.handle, udp_close_cb); + } +} + +static void +stop_udp_child(isc_nmsocket_t *sock) { + REQUIRE(sock->type == isc_nm_udpsocket); + REQUIRE(sock->tid == isc_nm_tid()); + + if (!atomic_compare_exchange_strong(&sock->closing, &(bool){ false }, + true)) + { + return; + } + + udp_close_direct(sock); + + atomic_fetch_sub(&sock->parent->rchildren, 1); + + isc_barrier_wait(&sock->parent->stoplistening); +} + +static void +stop_udp_parent(isc_nmsocket_t *sock) { + isc_nmsocket_t *csock = NULL; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + REQUIRE(sock->type == isc_nm_udplistener); + + isc_barrier_init(&sock->stoplistening, sock->nchildren); + + for (size_t i = 0; i < sock->nchildren; i++) { + csock = &sock->children[i]; + REQUIRE(VALID_NMSOCK(csock)); + + if ((int)i == isc_nm_tid()) { + /* + * We need to schedule closing the other sockets first + */ + continue; + } + + atomic_store(&csock->active, false); + enqueue_stoplistening(csock); + } + + csock = &sock->children[isc_nm_tid()]; + atomic_store(&csock->active, false); + stop_udp_child(csock); + + atomic_store(&sock->closed, true); + isc__nmsocket_prep_destroy(sock); +} + +static void +udp_close_direct(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + + uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock); + uv_close((uv_handle_t *)&sock->read_timer, read_timer_close_cb); +} + +void +isc__nm_async_udpclose(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_udpclose_t *ievent = (isc__netievent_udpclose_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + UNUSED(worker); + + udp_close_direct(sock); +} + +void +isc__nm_udp_close(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->type == isc_nm_udpsocket); + REQUIRE(!isc__nmsocket_active(sock)); + + if (!atomic_compare_exchange_strong(&sock->closing, &(bool){ false }, + true)) + { + return; + } + + if (sock->tid == isc_nm_tid()) { + udp_close_direct(sock); + } else { + isc__netievent_udpclose_t *ievent = + isc__nm_get_netievent_udpclose(sock->mgr, sock); + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); + } +} + +void +isc__nm_udp_shutdown(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + REQUIRE(sock->type == isc_nm_udpsocket); + + /* + * If the socket is active, mark it inactive and + * continue. If it isn't active, stop now. + */ + if (!isc__nmsocket_deactivate(sock)) { + return; + } + + /* + * If the socket is connecting, the cancel will happen in the + * async_udpconnect() due socket being inactive now. + */ + if (atomic_load(&sock->connecting)) { + return; + } + + /* + * When the client detaches the last handle, the + * sock->statichandle would be NULL, in that case, nobody is + * interested in the callback. + */ + if (sock->statichandle != NULL) { + isc__nm_failed_read_cb(sock, ISC_R_CANCELED, false); + return; + } + + /* + * Otherwise, we just send the socket to abyss... + */ + if (sock->parent == NULL) { + isc__nmsocket_prep_destroy(sock); + } +} + +void +isc__nm_udp_cancelread(isc_nmhandle_t *handle) { + isc_nmsocket_t *sock = NULL; + isc__netievent_udpcancel_t *ievent = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + + sock = handle->sock; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->type == isc_nm_udpsocket); + + ievent = isc__nm_get_netievent_udpcancel(sock->mgr, sock, handle); + + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); +} + +void +isc__nm_async_udpcancel(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_udpcancel_t *ievent = (isc__netievent_udpcancel_t *)ev0; + isc_nmsocket_t *sock = NULL; + + UNUSED(worker); + + REQUIRE(VALID_NMSOCK(ievent->sock)); + + sock = ievent->sock; + + REQUIRE(sock->tid == isc_nm_tid()); + REQUIRE(atomic_load(&sock->client)); + + isc__nm_failed_read_cb(sock, ISC_R_EOF, false); +} diff --git a/lib/isc/netmgr/uv-compat.c b/lib/isc/netmgr/uv-compat.c new file mode 100644 index 0000000..a1fc309 --- /dev/null +++ b/lib/isc/netmgr/uv-compat.c @@ -0,0 +1,152 @@ +/* + * 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. + */ + +#include "uv-compat.h" +#include <unistd.h> + +#include <isc/util.h> + +#include "netmgr-int.h" + +#if UV_VERSION_HEX < UV_VERSION(1, 27, 0) +int +isc_uv_udp_connect(uv_udp_t *handle, const struct sockaddr *addr) { + int err = 0; + + do { + int addrlen = (addr->sa_family == AF_INET) + ? sizeof(struct sockaddr_in) + : sizeof(struct sockaddr_in6); +#ifdef WIN32 + err = connect(handle->socket, addr, addrlen); +#else /* WIN32 */ + err = connect(handle->io_watcher.fd, addr, addrlen); +#endif /* WIN32 */ + } while (err == -1 && errno == EINTR); + + if (err) { +#ifdef WIN32 + return (uv_translate_sys_error(err)); +#else /* WIN32 */ +#if UV_VERSION_HEX >= UV_VERSION(1, 10, 0) + return (uv_translate_sys_error(errno)); +#else + return (-errno); +#endif /* UV_VERSION_HEX >= UV_VERSION(1, 10, 0) */ +#endif /* WIN32 */ + } + + return (0); +} +#endif /* UV_VERSION_HEX < UV_VERSION(1, 27, 0) */ + +#if UV_VERSION_HEX < UV_VERSION(1, 32, 0) +int +uv_tcp_close_reset(uv_tcp_t *handle, uv_close_cb close_cb) { + if (setsockopt(handle->io_watcher.fd, SOL_SOCKET, SO_LINGER, + &(struct linger){ 1, 0 }, sizeof(struct linger)) == -1) + { +#if UV_VERSION_HEX >= UV_VERSION(1, 10, 0) + return (uv_translate_sys_error(errno)); +#else + return (-errno); +#endif /* UV_VERSION_HEX >= UV_VERSION(1, 10, 0) */ + } + + uv_close((uv_handle_t *)handle, close_cb); + return (0); +} +#endif /* UV_VERSION_HEX < UV_VERSION(1, 32, 0) */ + +int +isc_uv_udp_freebind(uv_udp_t *handle, const struct sockaddr *addr, + unsigned int flags) { + int r; + uv_os_sock_t fd; + + r = uv_fileno((const uv_handle_t *)handle, (uv_os_fd_t *)&fd); + if (r < 0) { + return (r); + } + +#if defined(WIN32) + REQUIRE(fd != INVALID_SOCKET); +#endif + + r = uv_udp_bind(handle, addr, flags); + if (r == UV_EADDRNOTAVAIL && + isc__nm_socket_freebind(fd, addr->sa_family) == ISC_R_SUCCESS) + { + /* + * Retry binding with IP_FREEBIND (or equivalent option) if the + * address is not available. This helps with IPv6 tentative + * addresses which are reported by the route socket, although + * named is not yet able to properly bind to them. + */ + r = uv_udp_bind(handle, addr, flags); + } + + return (r); +} + +static int +isc__uv_tcp_bind_now(uv_tcp_t *handle, const struct sockaddr *addr, + unsigned int flags) { + int r; + struct sockaddr_storage sname; + int snamelen = sizeof(sname); + + r = uv_tcp_bind(handle, addr, flags); + if (r < 0) { + return (r); + } + + /* + * uv_tcp_bind() uses a delayed error, initially returning + * success even if bind() fails. By calling uv_tcp_getsockname() + * here we can find out whether the bind() call was successful. + */ + r = uv_tcp_getsockname(handle, (struct sockaddr *)&sname, &snamelen); + if (r < 0) { + return (r); + } + + return (0); +} + +int +isc_uv_tcp_freebind(uv_tcp_t *handle, const struct sockaddr *addr, + unsigned int flags) { + int r; + uv_os_sock_t fd; + + r = uv_fileno((const uv_handle_t *)handle, (uv_os_fd_t *)&fd); + if (r < 0) { + return (r); + } + + r = isc__uv_tcp_bind_now(handle, addr, flags); + if (r == UV_EADDRNOTAVAIL && + isc__nm_socket_freebind(fd, addr->sa_family) == ISC_R_SUCCESS) + { + /* + * Retry binding with IP_FREEBIND (or equivalent option) if the + * address is not available. This helps with IPv6 tentative + * addresses which are reported by the route socket, although + * named is not yet able to properly bind to them. + */ + r = isc__uv_tcp_bind_now(handle, addr, flags); + } + + return (r); +} diff --git a/lib/isc/netmgr/uv-compat.h b/lib/isc/netmgr/uv-compat.h new file mode 100644 index 0000000..3a10387 --- /dev/null +++ b/lib/isc/netmgr/uv-compat.h @@ -0,0 +1,126 @@ +/* + * 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. + */ + +#pragma once +#include <uv.h> + +/* + * These functions were introduced in newer libuv, but we still + * want BIND9 compile on older ones so we emulate them. + * They're inline to avoid conflicts when running with a newer + * library version. + */ + +#define UV_VERSION(major, minor, patch) ((major << 16) | (minor << 8) | (patch)) + +/* + * Copied verbatim from libuv/src/version.c + */ + +#define UV_STRINGIFY(v) UV_STRINGIFY_HELPER(v) +#define UV_STRINGIFY_HELPER(v) #v + +#define UV_VERSION_STRING_BASE \ + UV_STRINGIFY(UV_VERSION_MAJOR) \ + "." UV_STRINGIFY(UV_VERSION_MINOR) "." UV_STRINGIFY(UV_VERSION_PATCH) + +#if UV_VERSION_IS_RELEASE +#define UV_VERSION_STRING UV_VERSION_STRING_BASE +#else +#define UV_VERSION_STRING UV_VERSION_STRING_BASE "-" UV_VERSION_SUFFIX +#endif + +#if !defined(UV__ERR) +#define UV__ERR(x) (-(x)) +#endif + +#if UV_VERSION_HEX < UV_VERSION(1, 19, 0) +static inline void * +uv_handle_get_data(const uv_handle_t *handle) { + return (handle->data); +} + +static inline void +uv_handle_set_data(uv_handle_t *handle, void *data) { + handle->data = data; +} + +static inline void * +uv_req_get_data(const uv_req_t *req) { + return (req->data); +} + +static inline void +uv_req_set_data(uv_req_t *req, void *data) { + req->data = data; +} +#endif /* UV_VERSION_HEX < UV_VERSION(1, 19, 0) */ + +#if UV_VERSION_HEX < UV_VERSION(1, 32, 0) +int +uv_tcp_close_reset(uv_tcp_t *handle, uv_close_cb close_cb); +#endif + +#if UV_VERSION_HEX < UV_VERSION(1, 34, 0) +#define uv_sleep(msec) usleep(msec * 1000) +#endif /* UV_VERSION_HEX < UV_VERSION(1, 34, 0) */ + +#if UV_VERSION_HEX < UV_VERSION(1, 27, 0) +int +isc_uv_udp_connect(uv_udp_t *handle, const struct sockaddr *addr); +/*%< + * Associate the UDP handle to a remote address and port, so every message sent + * by this handle is automatically sent to that destination. + * + * NOTE: This is just a limited shim for uv_udp_connect() as it requires the + * handle to be bound. + */ +#else /* UV_VERSION_HEX < UV_VERSION(1, 27, 0) */ +#define isc_uv_udp_connect uv_udp_connect +#endif /* UV_VERSION_HEX < UV_VERSION(1, 27, 0) */ + +#if UV_VERSION_HEX < UV_VERSION(1, 12, 0) +#include <stdlib.h> +#include <string.h> + +static inline int +uv_os_getenv(const char *name, char *buffer, size_t *size) { + size_t len; + char *buf = getenv(name); + + if (buf == NULL) { + return (UV_ENOENT); + } + + len = strlen(buf) + 1; + if (len > *size) { + *size = len; + return (UV_ENOBUFS); + } + + *size = len; + memmove(buffer, buf, len); + + return (0); +} + +#define uv_os_setenv(name, value) setenv(name, value, 0) +#endif /* UV_VERSION_HEX < UV_VERSION(1, 12, 0) */ + +int +isc_uv_udp_freebind(uv_udp_t *handle, const struct sockaddr *addr, + unsigned int flags); + +int +isc_uv_tcp_freebind(uv_tcp_t *handle, const struct sockaddr *addr, + unsigned int flags); diff --git a/lib/isc/netmgr/uverr2result.c b/lib/isc/netmgr/uverr2result.c new file mode 100644 index 0000000..5ce953d --- /dev/null +++ b/lib/isc/netmgr/uverr2result.c @@ -0,0 +1,104 @@ +/* + * 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. + */ + +#include <stdbool.h> +#include <uv.h> + +#include <isc/result.h> +#include <isc/strerr.h> +#include <isc/string.h> +#include <isc/util.h> + +#include "netmgr-int.h" + +/*% + * Convert a libuv error value into an isc_result_t. The + * list of supported error values is not complete; new users + * of this function should add any expected errors that are + * not already there. + */ +isc_result_t +isc___nm_uverr2result(int uverr, bool dolog, const char *file, + unsigned int line, const char *func) { + switch (uverr) { + case 0: + return (ISC_R_SUCCESS); + case UV_ENOTDIR: + case UV_ELOOP: + case UV_EINVAL: /* XXX sometimes this is not for files */ + case UV_ENAMETOOLONG: + case UV_EBADF: + return (ISC_R_INVALIDFILE); + case UV_ENOENT: + return (ISC_R_FILENOTFOUND); + case UV_EAGAIN: + return (ISC_R_NOCONN); + case UV_EACCES: + case UV_EPERM: + return (ISC_R_NOPERM); + case UV_EEXIST: + return (ISC_R_FILEEXISTS); + case UV_EIO: + return (ISC_R_IOERROR); + case UV_ENOMEM: + return (ISC_R_NOMEMORY); + case UV_ENFILE: + case UV_EMFILE: + return (ISC_R_TOOMANYOPENFILES); + case UV_ENOSPC: + return (ISC_R_DISCFULL); + case UV_EPIPE: + case UV_ECONNRESET: + case UV_ECONNABORTED: + return (ISC_R_CONNECTIONRESET); + case UV_ENOTCONN: + return (ISC_R_NOTCONNECTED); + case UV_ETIMEDOUT: + return (ISC_R_TIMEDOUT); + case UV_ENOBUFS: + return (ISC_R_NORESOURCES); + case UV_EAFNOSUPPORT: + return (ISC_R_FAMILYNOSUPPORT); + case UV_ENETDOWN: + return (ISC_R_NETDOWN); + case UV_EHOSTDOWN: + return (ISC_R_HOSTDOWN); + case UV_ENETUNREACH: + return (ISC_R_NETUNREACH); + case UV_EHOSTUNREACH: + return (ISC_R_HOSTUNREACH); + case UV_EADDRINUSE: + return (ISC_R_ADDRINUSE); + case UV_EADDRNOTAVAIL: + return (ISC_R_ADDRNOTAVAIL); + case UV_ECONNREFUSED: + return (ISC_R_CONNREFUSED); + case UV_ECANCELED: + return (ISC_R_CANCELED); + case UV_EOF: + return (ISC_R_EOF); + case UV_EMSGSIZE: + return (ISC_R_MAXSIZE); + case UV_ENOTSUP: + return (ISC_R_FAMILYNOSUPPORT); + default: + if (dolog) { + UNEXPECTED_ERROR( + file, line, + "unable to convert libuv " + "error code in %s to isc_result: %d: %s", + func, uverr, uv_strerror(uverr)); + } + return (ISC_R_UNEXPECTED); + } +} diff --git a/lib/isc/netmgr_p.h b/lib/isc/netmgr_p.h new file mode 100644 index 0000000..73171a9 --- /dev/null +++ b/lib/isc/netmgr_p.h @@ -0,0 +1,38 @@ +/* + * 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. + */ + +#pragma once + +#include <isc/mem.h> +#include <isc/result.h> + +void +isc__netmgr_create(isc_mem_t *mctx, uint32_t workers, isc_nm_t **netgmrp); +/*%< + * Creates a new network manager with 'workers' worker threads, + * and starts it running. + */ + +void +isc__netmgr_destroy(isc_nm_t **netmgrp); +/*%< + * Similar to isc_nm_detach(), but actively waits for all other references + * to be gone before returning. + */ + +void +isc__netmgr_shutdown(isc_nm_t *mgr); +/*%< + * Shut down all active connections, freeing associated resources; + * prevent new connections from being established. + */ diff --git a/lib/isc/netscope.c b/lib/isc/netscope.c new file mode 100644 index 0000000..69c3e9a --- /dev/null +++ b/lib/isc/netscope.c @@ -0,0 +1,76 @@ +/* + * 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. + */ + +/*! \file */ + +#include <inttypes.h> +#include <stdlib.h> + +#include <isc/net.h> +#include <isc/netscope.h> +#include <isc/result.h> +#include <isc/string.h> +#include <isc/util.h> + +isc_result_t +isc_netscope_pton(int af, char *scopename, void *addr, uint32_t *zoneid) { + char *ep; +#ifdef HAVE_IF_NAMETOINDEX + unsigned int ifid; + struct in6_addr *in6; +#endif /* ifdef HAVE_IF_NAMETOINDEX */ + uint32_t zone = 0; + uint64_t llz; + +#ifndef HAVE_IF_NAMETOINDEX + UNUSED(addr); +#endif + + /* at this moment, we only support AF_INET6 */ + if (af != AF_INET6) { + return (ISC_R_FAILURE); + } + + /* + * Basically, "names" are more stable than numeric IDs in terms + * of renumbering, and are more preferred. However, since there + * is no standard naming convention and APIs to deal with the + * names. Thus, we only handle the case of link-local + * addresses, for which we use interface names as link names, + * assuming one to one mapping between interfaces and links. + */ +#ifdef HAVE_IF_NAMETOINDEX + in6 = (struct in6_addr *)addr; + if (IN6_IS_ADDR_LINKLOCAL(in6) && + (ifid = if_nametoindex((const char *)scopename)) != 0) + { + zone = (uint32_t)ifid; + } else { +#endif /* ifdef HAVE_IF_NAMETOINDEX */ + llz = strtoull(scopename, &ep, 10); + if (ep == scopename) { + return (ISC_R_FAILURE); + } + + /* check overflow */ + zone = (uint32_t)(llz & 0xffffffffUL); + if (zone != llz) { + return (ISC_R_FAILURE); + } +#ifdef HAVE_IF_NAMETOINDEX + } +#endif /* ifdef HAVE_IF_NAMETOINDEX */ + + *zoneid = zone; + return (ISC_R_SUCCESS); +} diff --git a/lib/isc/nonce.c b/lib/isc/nonce.c new file mode 100644 index 0000000..4c2baff --- /dev/null +++ b/lib/isc/nonce.c @@ -0,0 +1,21 @@ +/* + * 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. + */ + +#include <isc/nonce.h> + +#include "entropy_private.h" + +void +isc_nonce_buf(void *buf, size_t buflen) { + isc_entropy_get(buf, buflen); +} diff --git a/lib/isc/openssl_shim.c b/lib/isc/openssl_shim.c new file mode 100644 index 0000000..3baa04a --- /dev/null +++ b/lib/isc/openssl_shim.c @@ -0,0 +1,231 @@ +/* + * 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. + */ + +#include <inttypes.h> +#include <stdlib.h> +#include <string.h> + +#include <openssl/crypto.h> +#include <openssl/engine.h> +#include <openssl/evp.h> +#include <openssl/hmac.h> +#include <openssl/opensslv.h> +#include <openssl/ssl.h> + +#include "openssl_shim.h" + +#if !HAVE_CRYPTO_ZALLOC +void * +CRYPTO_zalloc(size_t num, const char *file, int line) { + void *ret = CRYPTO_malloc(num, file, line); + if (ret != NULL) { + memset(ret, 0, num); + } + return (ret); +} +#endif /* if !HAVE_CRYPTO_ZALLOC */ + +#if !HAVE_EVP_CIPHER_CTX_NEW +EVP_CIPHER_CTX * +EVP_CIPHER_CTX_new(void) { + EVP_CIPHER_CTX *ctx = OPENSSL_zalloc(sizeof(*ctx)); + return (ctx); +} +#endif /* if !HAVE_EVP_CIPHER_CTX_NEW */ + +#if !HAVE_EVP_CIPHER_CTX_FREE +void +EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *ctx) { + if (ctx != NULL) { + EVP_CIPHER_CTX_cleanup(ctx); + OPENSSL_free(ctx); + } +} +#endif /* if !HAVE_EVP_CIPHER_CTX_FREE */ + +#if !HAVE_EVP_MD_CTX_NEW +EVP_MD_CTX * +EVP_MD_CTX_new(void) { + EVP_MD_CTX *ctx = OPENSSL_malloc(sizeof(*ctx)); + if (ctx != NULL) { + memset(ctx, 0, sizeof(*ctx)); + } + return (ctx); +} +#endif /* if !HAVE_EVP_MD_CTX_NEW */ + +#if !HAVE_EVP_MD_CTX_FREE +void +EVP_MD_CTX_free(EVP_MD_CTX *ctx) { + if (ctx != NULL) { + EVP_MD_CTX_cleanup(ctx); + OPENSSL_free(ctx); + } +} +#endif /* if !HAVE_EVP_MD_CTX_FREE */ + +#if !HAVE_EVP_MD_CTX_RESET +int +EVP_MD_CTX_reset(EVP_MD_CTX *ctx) { + return (EVP_MD_CTX_cleanup(ctx)); +} +#endif /* if !HAVE_EVP_MD_CTX_RESET */ + +#if !HAVE_HMAC_CTX_NEW +HMAC_CTX * +HMAC_CTX_new(void) { + HMAC_CTX *ctx = OPENSSL_zalloc(sizeof(*ctx)); + if (ctx != NULL) { + if (!HMAC_CTX_reset(ctx)) { + HMAC_CTX_free(ctx); + return (NULL); + } + } + return (ctx); +} +#endif /* if !HAVE_HMAC_CTX_NEW */ + +#if !HAVE_HMAC_CTX_FREE +void +HMAC_CTX_free(HMAC_CTX *ctx) { + if (ctx != NULL) { + HMAC_CTX_cleanup(ctx); + OPENSSL_free(ctx); + } +} +#endif /* if !HAVE_HMAC_CTX_FREE */ + +#if !HAVE_HMAC_CTX_RESET +int +HMAC_CTX_reset(HMAC_CTX *ctx) { + HMAC_CTX_cleanup(ctx); + return (1); +} +#endif /* if !HAVE_HMAC_CTX_RESET */ + +#if !HAVE_HMAC_CTX_GET_MD +const EVP_MD * +HMAC_CTX_get_md(const HMAC_CTX *ctx) { + return (ctx->md); +} +#endif /* if !HAVE_HMAC_CTX_GET_MD */ + +#if !HAVE_SSL_READ_EX +int +SSL_read_ex(SSL *ssl, void *buf, size_t num, size_t *readbytes) { + int rv = SSL_read(ssl, buf, num); + if (rv > 0) { + *readbytes = rv; + rv = 1; + } + + return (rv); +} +#endif + +#if !HAVE_SSL_PEEK_EX +int +SSL_peek_ex(SSL *ssl, void *buf, size_t num, size_t *readbytes) { + int rv = SSL_peek(ssl, buf, num); + if (rv > 0) { + *readbytes = rv; + rv = 1; + } + + return (rv); +} +#endif + +#if !HAVE_SSL_WRITE_EX +int +SSL_write_ex(SSL *ssl, const void *buf, size_t num, size_t *written) { + int rv = SSL_write(ssl, buf, num); + if (rv > 0) { + *written = rv; + rv = 1; + } + + return (rv); +} +#endif + +#if !HAVE_BIO_READ_EX +int +BIO_read_ex(BIO *b, void *data, size_t dlen, size_t *readbytes) { + int rv = BIO_read(b, data, dlen); + if (rv > 0) { + *readbytes = rv; + rv = 1; + } + + return (rv); +} +#endif + +#if !HAVE_BIO_WRITE_EX +int +BIO_write_ex(BIO *b, const void *data, size_t dlen, size_t *written) { + int rv = BIO_write(b, data, dlen); + if (rv > 0) { + *written = rv; + rv = 1; + } + + return (rv); +} +#endif + +#if !HAVE_OPENSSL_INIT_CRYPTO +int +OPENSSL_init_crypto(uint64_t opts, const void *settings) { + (void)settings; + + if ((opts & OPENSSL_INIT_NO_LOAD_CRYPTO_STRINGS) == 0) { + ERR_load_crypto_strings(); + } + + if ((opts & (OPENSSL_INIT_NO_ADD_ALL_CIPHERS | + OPENSSL_INIT_NO_ADD_ALL_CIPHERS)) == 0) + { + OpenSSL_add_all_algorithms(); + } else if ((opts & OPENSSL_INIT_NO_ADD_ALL_CIPHERS) == 0) { + OpenSSL_add_all_digests(); + } else if ((opts & OPENSSL_INIT_NO_ADD_ALL_CIPHERS) == 0) { + OpenSSL_add_all_ciphers(); + } + + return (1); +} +#endif + +#if !HAVE_OPENSSL_INIT_SSL +int +OPENSSL_init_ssl(uint64_t opts, const void *settings) { + OPENSSL_init_crypto(opts, settings); + + SSL_library_init(); + + if ((opts & OPENSSL_INIT_NO_LOAD_SSL_STRINGS) == 0) { + SSL_load_error_strings(); + } + + return (1); +} +#endif + +#if !HAVE_OPENSSL_CLEANUP +void +OPENSSL_cleanup(void) { + return; +} +#endif diff --git a/lib/isc/openssl_shim.h b/lib/isc/openssl_shim.h new file mode 100644 index 0000000..9c8a63a --- /dev/null +++ b/lib/isc/openssl_shim.h @@ -0,0 +1,135 @@ +/* + * 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. + */ + +#pragma once + +#include <openssl/crypto.h> +#include <openssl/engine.h> +#include <openssl/evp.h> +#include <openssl/hmac.h> +#include <openssl/opensslv.h> +#include <openssl/ssl.h> + +#if !HAVE_CRYPTO_ZALLOC +void * +CRYPTO_zalloc(size_t num, const char *file, int line); +#endif /* if !HAVE_CRYPTO_ZALLOC */ + +#if !defined(OPENSSL_zalloc) +#define OPENSSL_zalloc(num) CRYPTO_zalloc(num, __FILE__, __LINE__) +#endif + +#if !HAVE_EVP_CIPHER_CTX_NEW +EVP_CIPHER_CTX * +EVP_CIPHER_CTX_new(void); +#endif /* if !HAVE_EVP_CIPHER_CTX_NEW */ + +#if !HAVE_EVP_CIPHER_CTX_FREE +void +EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *ctx); +#endif /* if !HAVE_EVP_CIPHER_CTX_FREE */ + +#if !HAVE_EVP_MD_CTX_NEW +EVP_MD_CTX * +EVP_MD_CTX_new(void); +#endif /* if !HAVE_EVP_MD_CTX_NEW */ + +#if !HAVE_EVP_MD_CTX_FREE +void +EVP_MD_CTX_free(EVP_MD_CTX *ctx); +#endif /* if !HAVE_EVP_MD_CTX_FREE */ + +#if !HAVE_EVP_MD_CTX_RESET +int +EVP_MD_CTX_reset(EVP_MD_CTX *ctx); +#endif /* if !HAVE_EVP_MD_CTX_RESET */ + +#if !HAVE_HMAC_CTX_NEW +HMAC_CTX * +HMAC_CTX_new(void); +#endif /* if !HAVE_HMAC_CTX_NEW */ + +#if !HAVE_HMAC_CTX_FREE +void +HMAC_CTX_free(HMAC_CTX *ctx); +#endif /* if !HAVE_HMAC_CTX_FREE */ + +#if !HAVE_HMAC_CTX_RESET +int +HMAC_CTX_reset(HMAC_CTX *ctx); +#endif /* if !HAVE_HMAC_CTX_RESET */ + +#if !HAVE_HMAC_CTX_GET_MD +const EVP_MD * +HMAC_CTX_get_md(const HMAC_CTX *ctx); +#endif /* if !HAVE_HMAC_CTX_GET_MD */ + +#if !HAVE_SSL_READ_EX +int +SSL_read_ex(SSL *ssl, void *buf, size_t num, size_t *readbytes); +#endif + +#if !HAVE_SSL_PEEK_EX +int +SSL_peek_ex(SSL *ssl, void *buf, size_t num, size_t *readbytes); +#endif + +#if !HAVE_SSL_WRITE_EX +int +SSL_write_ex(SSL *ssl, const void *buf, size_t num, size_t *written); +#endif + +#if !HAVE_BIO_READ_EX +int +BIO_read_ex(BIO *b, void *data, size_t dlen, size_t *readbytes); +#endif + +#if !HAVE_BIO_WRITE_EX +int +BIO_write_ex(BIO *b, const void *data, size_t dlen, size_t *written); +#endif + +#if !HAVE_OPENSSL_INIT_CRYPTO + +#define OPENSSL_INIT_NO_LOAD_CRYPTO_STRINGS 0x00000001L +#define OPENSSL_INIT_LOAD_CRYPTO_STRINGS 0x00000002L +#define OPENSSL_INIT_ADD_ALL_CIPHERS 0x00000004L +#define OPENSSL_INIT_ADD_ALL_DIGESTS 0x00000008L +#define OPENSSL_INIT_NO_ADD_ALL_CIPHERS 0x00000010L +#define OPENSSL_INIT_NO_ADD_ALL_DIGESTS 0x00000020L + +int +OPENSSL_init_crypto(uint64_t opts, const void *settings); +#endif + +#if !HAVE_OPENSSL_INIT_SSL +#define OPENSSL_INIT_NO_LOAD_SSL_STRINGS 0x00100000L +#define OPENSSL_INIT_LOAD_SSL_STRINGS 0x00200000L + +int +OPENSSL_init_ssl(uint64_t opts, const void *settings); + +#endif + +#if !HAVE_OPENSSL_CLEANUP +void +OPENSSL_cleanup(void); +#endif + +#if !HAVE_TLS_SERVER_METHOD +#define TLS_server_method SSLv23_server_method +#endif + +#if !HAVE_TLS_CLIENT_METHOD +#define TLS_client_method SSLv23_client_method +#endif diff --git a/lib/isc/parseint.c b/lib/isc/parseint.c new file mode 100644 index 0000000..da6c281 --- /dev/null +++ b/lib/isc/parseint.c @@ -0,0 +1,79 @@ +/* + * 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. + */ + +/*! \file */ + +#include <ctype.h> +#include <errno.h> +#include <inttypes.h> +#include <limits.h> +#include <stdlib.h> + +#include <isc/parseint.h> +#include <isc/result.h> + +isc_result_t +isc_parse_uint32(uint32_t *uip, const char *string, int base) { + unsigned long n; + uint32_t r; + char *e; + if (!isalnum((unsigned char)(string[0]))) { + return (ISC_R_BADNUMBER); + } + errno = 0; + n = strtoul(string, &e, base); + if (*e != '\0') { + return (ISC_R_BADNUMBER); + } + /* + * Where long is 64 bits we need to convert to 32 bits then test for + * equality. This is a no-op on 32 bit machines and a good compiler + * will optimise it away. + */ + r = (uint32_t)n; + if ((n == ULONG_MAX && errno == ERANGE) || (n != (unsigned long)r)) { + return (ISC_R_RANGE); + } + *uip = r; + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_parse_uint16(uint16_t *uip, const char *string, int base) { + uint32_t val; + isc_result_t result; + result = isc_parse_uint32(&val, string, base); + if (result != ISC_R_SUCCESS) { + return (result); + } + if (val > 0xFFFF) { + return (ISC_R_RANGE); + } + *uip = (uint16_t)val; + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_parse_uint8(uint8_t *uip, const char *string, int base) { + uint32_t val; + isc_result_t result; + result = isc_parse_uint32(&val, string, base); + if (result != ISC_R_SUCCESS) { + return (result); + } + if (val > 0xFF) { + return (ISC_R_RANGE); + } + *uip = (uint8_t)val; + return (ISC_R_SUCCESS); +} diff --git a/lib/isc/pk11.c b/lib/isc/pk11.c new file mode 100644 index 0000000..6d77314 --- /dev/null +++ b/lib/isc/pk11.c @@ -0,0 +1,1112 @@ +/* + * 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. + */ + +#include <errno.h> +#include <inttypes.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <isc/log.h> +#include <isc/mem.h> +#include <isc/once.h> +#include <isc/platform.h> +#include <isc/print.h> +#include <isc/stdio.h> +#include <isc/strerr.h> +#include <isc/string.h> +#include <isc/thread.h> +#include <isc/util.h> + +#include <pk11/internal.h> +#include <pk11/pk11.h> +#include <pk11/result.h> +#include <pk11/site.h> +#include <pkcs11/pkcs11.h> + +#include <dst/result.h> + +/* was 32 octets, Petr Spacek suggested 1024, SoftHSMv2 uses 256... */ +#ifndef PINLEN +#define PINLEN 256 +#endif /* ifndef PINLEN */ + +#ifndef PK11_NO_LOGERR +#define PK11_NO_LOGERR 1 +#endif /* ifndef PK11_NO_LOGERR */ + +LIBISC_EXTERNAL_DATA bool pk11_verbose_init = false; + +static isc_once_t once = ISC_ONCE_INIT; +static isc_mem_t *pk11_mctx = NULL; +static int32_t allocsize = 0; +static bool initialized = false; + +typedef struct pk11_session pk11_session_t; +typedef struct pk11_token pk11_token_t; +typedef ISC_LIST(pk11_session_t) pk11_sessionlist_t; + +struct pk11_session { + unsigned int magic; + CK_SESSION_HANDLE session; + ISC_LINK(pk11_session_t) link; + pk11_token_t *token; +}; + +struct pk11_token { + unsigned int magic; + unsigned int operations; + ISC_LINK(pk11_token_t) link; + CK_SLOT_ID slotid; + pk11_sessionlist_t sessions; + bool logged; + char name[32]; + char manuf[32]; + char model[16]; + char serial[16]; + char pin[PINLEN + 1]; +}; +static ISC_LIST(pk11_token_t) tokens; + +static pk11_token_t *best_rsa_token; +static pk11_token_t *best_ecdsa_token; +static pk11_token_t *best_eddsa_token; + +static isc_result_t +free_all_sessions(void); +static isc_result_t +free_session_list(pk11_sessionlist_t *slist); +static isc_result_t +setup_session(pk11_session_t *sp, pk11_token_t *token, bool rw); +static void +scan_slots(void); +static isc_result_t +token_login(pk11_session_t *sp); +static char * +percent_decode(char *x, size_t *len); +static bool +pk11strcmp(const char *x, size_t lenx, const char *y, size_t leny); +static CK_ATTRIBUTE * +push_attribute(pk11_object_t *obj, isc_mem_t *mctx, size_t len); + +static isc_mutex_t alloclock; +static isc_mutex_t sessionlock; + +static pk11_sessionlist_t actives; + +static CK_C_INITIALIZE_ARGS pk11_init_args = { + NULL_PTR, /* CreateMutex */ + NULL_PTR, /* DestroyMutex */ + NULL_PTR, /* LockMutex */ + NULL_PTR, /* UnlockMutex */ + CKF_OS_LOCKING_OK, /* flags */ + NULL_PTR, /* pReserved */ +}; + +#ifndef PK11_LIB_LOCATION +#define PK11_LIB_LOCATION "unknown_provider" +#endif /* ifndef PK11_LIB_LOCATION */ + +#ifndef WIN32 +static const char *lib_name = PK11_LIB_LOCATION; +#else /* ifndef WIN32 */ +static const char *lib_name = PK11_LIB_LOCATION ".dll"; +#endif /* ifndef WIN32 */ + +void +pk11_set_lib_name(const char *name) { + lib_name = name; +} + +const char * +pk11_get_lib_name(void) { + return (lib_name); +} + +static void +initialize(void) { + char *pk11_provider; + + isc_mutex_init(&alloclock); + isc_mutex_init(&sessionlock); + + pk11_provider = getenv("PKCS11_PROVIDER"); + if (pk11_provider != NULL) { + lib_name = pk11_provider; + } +} + +void * +pk11_mem_get(size_t size) { + void *ptr; + + LOCK(&alloclock); + if (pk11_mctx != NULL) { + ptr = isc_mem_get(pk11_mctx, size); + } else { + ptr = malloc(size); + if (ptr == NULL && size != 0) { + char strbuf[ISC_STRERRORSIZE]; + strerror_r(errno, strbuf, sizeof(strbuf)); + isc_error_fatal(__FILE__, __LINE__, "malloc failed: %s", + strbuf); + } + } + UNLOCK(&alloclock); + + if (ptr != NULL) { + memset(ptr, 0, size); + } + return (ptr); +} + +void +pk11_mem_put(void *ptr, size_t size) { + if (ptr != NULL) { + memset(ptr, 0, size); + } + LOCK(&alloclock); + if (pk11_mctx != NULL) { + isc_mem_put(pk11_mctx, ptr, size); + } else { + if (ptr != NULL) { + allocsize -= (int)size; + } + free(ptr); + } + UNLOCK(&alloclock); +} + +isc_result_t +pk11_initialize(isc_mem_t *mctx, const char *engine) { + isc_result_t result = ISC_R_SUCCESS; + CK_RV rv; + + RUNTIME_CHECK(isc_once_do(&once, initialize) == ISC_R_SUCCESS); + + LOCK(&sessionlock); + LOCK(&alloclock); + if ((mctx != NULL) && (pk11_mctx == NULL) && (allocsize == 0)) { + isc_mem_attach(mctx, &pk11_mctx); + } + UNLOCK(&alloclock); + if (initialized) { + goto unlock; + } else { + initialized = true; + } + + ISC_LIST_INIT(tokens); + ISC_LIST_INIT(actives); + + if (engine != NULL) { + lib_name = engine; + } + + /* Initialize the CRYPTOKI library */ + rv = pkcs_C_Initialize((CK_VOID_PTR)&pk11_init_args); + + if (rv == 0xfe) { + result = PK11_R_NOPROVIDER; + fprintf(stderr, "Can't load PKCS#11 provider: %s\n", + pk11_get_load_error_message()); + goto unlock; + } + if (rv != CKR_OK) { + result = PK11_R_INITFAILED; + goto unlock; + } + + scan_slots(); +unlock: + UNLOCK(&sessionlock); + return (result); +} + +isc_result_t +pk11_finalize(void) { + pk11_token_t *token, *next; + isc_result_t ret; + + ret = free_all_sessions(); + (void)pkcs_C_Finalize(NULL_PTR); + token = ISC_LIST_HEAD(tokens); + while (token != NULL) { + next = ISC_LIST_NEXT(token, link); + ISC_LIST_UNLINK(tokens, token, link); + if (token == best_rsa_token) { + best_rsa_token = NULL; + } + if (token == best_ecdsa_token) { + best_ecdsa_token = NULL; + } + if (token == best_eddsa_token) { + best_eddsa_token = NULL; + } + pk11_mem_put(token, sizeof(*token)); + token = next; + } + if (pk11_mctx != NULL) { + isc_mem_detach(&pk11_mctx); + } + initialized = false; + return (ret); +} + +isc_result_t +pk11_get_session(pk11_context_t *ctx, pk11_optype_t optype, bool need_services, + bool rw, bool logon, const char *pin, CK_SLOT_ID slot) { + pk11_token_t *token = NULL; + pk11_sessionlist_t *freelist; + pk11_session_t *sp; + isc_result_t ret; + UNUSED(need_services); + + memset(ctx, 0, sizeof(pk11_context_t)); + ctx->handle = NULL; + ctx->session = CK_INVALID_HANDLE; + + ret = pk11_initialize(NULL, NULL); + if (ret != ISC_R_SUCCESS) { + return (ret); + } + + LOCK(&sessionlock); + /* wait for initialization to finish */ + UNLOCK(&sessionlock); + + switch (optype) { + case OP_ANY: + for (token = ISC_LIST_HEAD(tokens); token != NULL; + token = ISC_LIST_NEXT(token, link)) + { + if (token->slotid == slot) { + break; + } + } + break; + default: + for (token = ISC_LIST_HEAD(tokens); token != NULL; + token = ISC_LIST_NEXT(token, link)) + { + if (token->slotid == slot) { + break; + } + } + break; + } + if (token == NULL) { + return (ISC_R_NOTFOUND); + } + + /* Override the token's PIN */ + if (logon && pin != NULL && *pin != '\0') { + if (strlen(pin) > PINLEN) { + return (ISC_R_RANGE); + } + /* + * We want to zero out the old pin before + * overwriting with a new one. + */ + memset(token->pin, 0, sizeof(token->pin)); + strlcpy(token->pin, pin, sizeof(token->pin)); + } + + freelist = &token->sessions; + + LOCK(&sessionlock); + sp = ISC_LIST_HEAD(*freelist); + if (sp != NULL) { + ISC_LIST_UNLINK(*freelist, sp, link); + ISC_LIST_APPEND(actives, sp, link); + UNLOCK(&sessionlock); + if (logon) { + ret = token_login(sp); + } + ctx->handle = sp; + ctx->session = sp->session; + return (ret); + } + UNLOCK(&sessionlock); + + sp = pk11_mem_get(sizeof(*sp)); + sp->magic = SES_MAGIC; + sp->token = token; + sp->session = CK_INVALID_HANDLE; + ISC_LINK_INIT(sp, link); + ret = setup_session(sp, token, rw); + if ((ret == ISC_R_SUCCESS) && logon) { + ret = token_login(sp); + } + LOCK(&sessionlock); + ISC_LIST_APPEND(actives, sp, link); + UNLOCK(&sessionlock); + ctx->handle = sp; + ctx->session = sp->session; + return (ret); +} + +void +pk11_return_session(pk11_context_t *ctx) { + pk11_session_t *sp = (pk11_session_t *)ctx->handle; + + if (sp == NULL) { + return; + } + ctx->handle = NULL; + ctx->session = CK_INVALID_HANDLE; + + LOCK(&sessionlock); + ISC_LIST_UNLINK(actives, sp, link); + UNLOCK(&sessionlock); + if (sp->session == CK_INVALID_HANDLE) { + pk11_mem_put(sp, sizeof(*sp)); + return; + } + + LOCK(&sessionlock); + ISC_LIST_APPEND(sp->token->sessions, sp, link); + UNLOCK(&sessionlock); +} + +static isc_result_t +free_all_sessions(void) { + pk11_token_t *token; + isc_result_t ret = ISC_R_SUCCESS; + isc_result_t oret; + + for (token = ISC_LIST_HEAD(tokens); token != NULL; + token = ISC_LIST_NEXT(token, link)) + { + oret = free_session_list(&token->sessions); + if (oret != ISC_R_SUCCESS) { + ret = oret; + } + } + if (!ISC_LIST_EMPTY(actives)) { + ret = ISC_R_ADDRINUSE; + oret = free_session_list(&actives); + if (oret != ISC_R_SUCCESS) { + ret = oret; + } + } + return (ret); +} + +static isc_result_t +free_session_list(pk11_sessionlist_t *slist) { + pk11_session_t *sp; + CK_RV rv; + isc_result_t ret; + + ret = ISC_R_SUCCESS; + LOCK(&sessionlock); + while (!ISC_LIST_EMPTY(*slist)) { + sp = ISC_LIST_HEAD(*slist); + ISC_LIST_UNLINK(*slist, sp, link); + UNLOCK(&sessionlock); + if (sp->session != CK_INVALID_HANDLE) { + rv = pkcs_C_CloseSession(sp->session); + if (rv != CKR_OK) { + ret = DST_R_CRYPTOFAILURE; + } + } + LOCK(&sessionlock); + pk11_mem_put(sp, sizeof(*sp)); + } + UNLOCK(&sessionlock); + + return (ret); +} + +static isc_result_t +setup_session(pk11_session_t *sp, pk11_token_t *token, bool rw) { + CK_RV rv; + CK_FLAGS flags = CKF_SERIAL_SESSION; + + if (rw) { + flags += CKF_RW_SESSION; + } + + rv = pkcs_C_OpenSession(token->slotid, flags, NULL_PTR, NULL_PTR, + &sp->session); + if (rv != CKR_OK) { + return (DST_R_CRYPTOFAILURE); + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +token_login(pk11_session_t *sp) { + CK_RV rv; + pk11_token_t *token = sp->token; + isc_result_t ret = ISC_R_SUCCESS; + + LOCK(&sessionlock); + if (!token->logged) { + rv = pkcs_C_Login(sp->session, CKU_USER, + (CK_UTF8CHAR_PTR)token->pin, + (CK_ULONG)strlen(token->pin)); + if (rv != CKR_OK) { +#if PK11_NO_LOGERR + pk11_error_fatalcheck(__FILE__, __LINE__, + "pkcs_C_Login", rv); +#else /* if PK11_NO_LOGERR */ + ret = ISC_R_NOPERM; +#endif /* if PK11_NO_LOGERR */ + } else { + token->logged = true; + } + } + UNLOCK(&sessionlock); + return (ret); +} + +#define PK11_TRACE(fmt) \ + if (pk11_verbose_init) \ + fprintf(stderr, fmt) +#define PK11_TRACE1(fmt, arg) \ + if (pk11_verbose_init) \ + fprintf(stderr, fmt, arg) +#define PK11_TRACE2(fmt, arg1, arg2) \ + if (pk11_verbose_init) \ + fprintf(stderr, fmt, arg1, arg2) +#define PK11_TRACEM(mech) \ + if (pk11_verbose_init) \ + fprintf(stderr, #mech ": 0x%lx\n", rv) + +static void +scan_slots(void) { + CK_MECHANISM_INFO mechInfo; + CK_TOKEN_INFO tokenInfo; + CK_RV rv; + CK_SLOT_ID slot; + CK_SLOT_ID_PTR slotList; + CK_ULONG slotCount; + pk11_token_t *token; + unsigned int i; + bool bad; + + slotCount = 0; + PK11_FATALCHECK(pkcs_C_GetSlotList, (CK_FALSE, NULL_PTR, &slotCount)); + PK11_TRACE1("slotCount=%lu\n", slotCount); + /* it's not an error if we didn't find any providers */ + if (slotCount == 0) { + return; + } + slotList = pk11_mem_get(sizeof(CK_SLOT_ID) * slotCount); + PK11_FATALCHECK(pkcs_C_GetSlotList, (CK_FALSE, slotList, &slotCount)); + + for (i = 0; i < slotCount; i++) { + slot = slotList[i]; + PK11_TRACE2("slot#%u=0x%lx\n", i, slot); + + rv = pkcs_C_GetTokenInfo(slot, &tokenInfo); + if (rv != CKR_OK) { + continue; + } + token = pk11_mem_get(sizeof(*token)); + token->magic = TOK_MAGIC; + token->slotid = slot; + ISC_LINK_INIT(token, link); + ISC_LIST_INIT(token->sessions); + memmove(token->name, tokenInfo.label, 32); + memmove(token->manuf, tokenInfo.manufacturerID, 32); + memmove(token->model, tokenInfo.model, 16); + memmove(token->serial, tokenInfo.serialNumber, 16); + ISC_LIST_APPEND(tokens, token, link); + + /* Check for RSA support */ + bad = false; + rv = pkcs_C_GetMechanismInfo(slot, CKM_RSA_PKCS_KEY_PAIR_GEN, + &mechInfo); + if ((rv != CKR_OK) || + ((mechInfo.flags & CKF_GENERATE_KEY_PAIR) == 0)) + { + bad = true; + PK11_TRACEM(CKM_RSA_PKCS_KEY_PAIR_GEN); + } + rv = pkcs_C_GetMechanismInfo(slot, CKM_MD5_RSA_PKCS, &mechInfo); + if ((rv != CKR_OK) || ((mechInfo.flags & CKF_SIGN) == 0) || + ((mechInfo.flags & CKF_VERIFY) == 0)) + { + bad = true; + PK11_TRACEM(CKM_MD5_RSA_PKCS); + } + rv = pkcs_C_GetMechanismInfo(slot, CKM_SHA1_RSA_PKCS, + &mechInfo); + if ((rv != CKR_OK) || ((mechInfo.flags & CKF_SIGN) == 0) || + ((mechInfo.flags & CKF_VERIFY) == 0)) + { + bad = true; + PK11_TRACEM(CKM_SHA1_RSA_PKCS); + } + rv = pkcs_C_GetMechanismInfo(slot, CKM_SHA256_RSA_PKCS, + &mechInfo); + if ((rv != CKR_OK) || ((mechInfo.flags & CKF_SIGN) == 0) || + ((mechInfo.flags & CKF_VERIFY) == 0)) + { + bad = true; + PK11_TRACEM(CKM_SHA256_RSA_PKCS); + } + rv = pkcs_C_GetMechanismInfo(slot, CKM_SHA512_RSA_PKCS, + &mechInfo); + if ((rv != CKR_OK) || ((mechInfo.flags & CKF_SIGN) == 0) || + ((mechInfo.flags & CKF_VERIFY) == 0)) + { + bad = true; + PK11_TRACEM(CKM_SHA512_RSA_PKCS); + } + rv = pkcs_C_GetMechanismInfo(slot, CKM_RSA_PKCS, &mechInfo); + if ((rv != CKR_OK) || ((mechInfo.flags & CKF_SIGN) == 0) || + ((mechInfo.flags & CKF_VERIFY) == 0)) + { + bad = true; + PK11_TRACEM(CKM_RSA_PKCS); + } + if (!bad) { + token->operations |= 1 << OP_RSA; + if (best_rsa_token == NULL) { + best_rsa_token = token; + } + } + + /* Check for ECDSA support */ + bad = false; + rv = pkcs_C_GetMechanismInfo(slot, CKM_EC_KEY_PAIR_GEN, + &mechInfo); + if ((rv != CKR_OK) || + ((mechInfo.flags & CKF_GENERATE_KEY_PAIR) == 0)) + { + bad = true; + PK11_TRACEM(CKM_EC_KEY_PAIR_GEN); + } + rv = pkcs_C_GetMechanismInfo(slot, CKM_ECDSA, &mechInfo); + if ((rv != CKR_OK) || ((mechInfo.flags & CKF_SIGN) == 0) || + ((mechInfo.flags & CKF_VERIFY) == 0)) + { + bad = true; + PK11_TRACEM(CKM_ECDSA); + } + if (!bad) { + token->operations |= 1 << OP_ECDSA; + if (best_ecdsa_token == NULL) { + best_ecdsa_token = token; + } + } + + /* Check for EDDSA support */ + bad = false; + rv = pkcs_C_GetMechanismInfo(slot, CKM_EC_EDWARDS_KEY_PAIR_GEN, + &mechInfo); + if ((rv != CKR_OK) || + ((mechInfo.flags & CKF_GENERATE_KEY_PAIR) == 0)) + { + bad = true; + PK11_TRACEM(CKM_EC_EDWARDS_KEY_PAIR_GEN); + } + rv = pkcs_C_GetMechanismInfo(slot, CKM_EDDSA, &mechInfo); + if ((rv != CKR_OK) || ((mechInfo.flags & CKF_SIGN) == 0) || + ((mechInfo.flags & CKF_VERIFY) == 0)) + { + bad = true; + PK11_TRACEM(CKM_EDDSA); + } + if (!bad) { + token->operations |= 1 << OP_EDDSA; + if (best_eddsa_token == NULL) { + best_eddsa_token = token; + } + } + } + + if (slotList != NULL) { + pk11_mem_put(slotList, sizeof(CK_SLOT_ID) * slotCount); + } +} + +CK_SLOT_ID +pk11_get_best_token(pk11_optype_t optype) { + pk11_token_t *token = NULL; + + switch (optype) { + case OP_RSA: + token = best_rsa_token; + break; + case OP_ECDSA: + token = best_ecdsa_token; + break; + case OP_EDDSA: + token = best_eddsa_token; + break; + default: + break; + } + if (token == NULL) { + return (0); + } + return (token->slotid); +} + +isc_result_t +pk11_numbits(CK_BYTE_PTR data, unsigned int bytecnt, unsigned int *bits) { + unsigned int bitcnt, i; + CK_BYTE top; + + if (bytecnt == 0) { + *bits = 0; + return (ISC_R_SUCCESS); + } + bitcnt = bytecnt * 8; + for (i = 0; i < bytecnt; i++) { + top = data[i]; + if (top == 0) { + bitcnt -= 8; + continue; + } + if (top & 0x80) { + *bits = bitcnt; + return (ISC_R_SUCCESS); + } + if (top & 0x40) { + *bits = bitcnt - 1; + return (ISC_R_SUCCESS); + } + if (top & 0x20) { + *bits = bitcnt - 2; + return (ISC_R_SUCCESS); + } + if (top & 0x10) { + *bits = bitcnt - 3; + return (ISC_R_SUCCESS); + } + if (top & 0x08) { + *bits = bitcnt - 4; + return (ISC_R_SUCCESS); + } + if (top & 0x04) { + *bits = bitcnt - 5; + return (ISC_R_SUCCESS); + } + if (top & 0x02) { + *bits = bitcnt - 6; + return (ISC_R_SUCCESS); + } + if (top & 0x01) { + *bits = bitcnt - 7; + return (ISC_R_SUCCESS); + } + break; + } + return (ISC_R_RANGE); +} + +CK_ATTRIBUTE * +pk11_attribute_first(const pk11_object_t *obj) { + return (obj->repr); +} + +CK_ATTRIBUTE * +pk11_attribute_next(const pk11_object_t *obj, CK_ATTRIBUTE *attr) { + CK_ATTRIBUTE *next; + + next = attr + 1; + if ((next - obj->repr) >= obj->attrcnt) { + return (NULL); + } + return (next); +} + +CK_ATTRIBUTE * +pk11_attribute_bytype(const pk11_object_t *obj, CK_ATTRIBUTE_TYPE type) { + CK_ATTRIBUTE *attr; + + for (attr = pk11_attribute_first(obj); attr != NULL; + attr = pk11_attribute_next(obj, attr)) + { + if (attr->type == type) { + return (attr); + } + } + return (NULL); +} + +static char * +percent_decode(char *x, size_t *len) { + char *p, *c; + unsigned char v = 0; + + INSIST(len != NULL); + + for (p = c = x; p[0] != '\0'; p++, c++) { + switch (p[0]) { + case '%': + switch (p[1]) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + v = (p[1] - '0') << 4; + break; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + v = (p[1] - 'A' + 10) << 4; + break; + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + v = (p[1] - 'a' + 10) << 4; + break; + default: + return (NULL); + } + switch (p[2]) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + v |= (p[2] - '0') & 0x0f; + break; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + v = (p[2] - 'A' + 10) & 0x0f; + break; + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + v = (p[2] - 'a' + 10) & 0x0f; + break; + default: + return (NULL); + } + p += 2; + *c = (char)v; + (*len)++; + break; + default: + *c = *p; + (*len)++; + } + } + return (x); +} + +static bool +pk11strcmp(const char *x, size_t lenx, const char *y, size_t leny) { + char buf[32]; + + INSIST((leny == 32) || (leny == 16)); + + memset(buf, ' ', 32); + if (lenx > leny) { + lenx = leny; + } + memmove(buf, x, lenx); + return (memcmp(buf, y, leny) == 0); +} + +static CK_ATTRIBUTE * +push_attribute(pk11_object_t *obj, isc_mem_t *mctx, size_t len) { + CK_ATTRIBUTE *old = obj->repr; + CK_ATTRIBUTE *attr; + CK_BYTE cnt = obj->attrcnt; + + REQUIRE(old != NULL || cnt == 0); + + obj->repr = isc_mem_get(mctx, (cnt + 1) * sizeof(*attr)); + memset(obj->repr, 0, (cnt + 1) * sizeof(*attr)); + if (old != NULL) { + memmove(obj->repr, old, cnt * sizeof(*attr)); + } + attr = obj->repr + cnt; + attr->ulValueLen = (CK_ULONG)len; + attr->pValue = isc_mem_get(mctx, len); + memset(attr->pValue, 0, len); + if (old != NULL) { + memset(old, 0, cnt * sizeof(*attr)); + isc_mem_put(mctx, old, cnt * sizeof(*attr)); + } + obj->attrcnt++; + return (attr); +} + +#define DST_RET(a) \ + { \ + ret = a; \ + goto err; \ + } + +isc_result_t +pk11_parse_uri(pk11_object_t *obj, const char *label, isc_mem_t *mctx, + pk11_optype_t optype) { + CK_ATTRIBUTE *attr; + pk11_token_t *token = NULL; + char *uri, *p, *a, *na, *v; + size_t len, l; + FILE *stream = NULL; + char pin[PINLEN + 1]; + bool gotpin = false; + isc_result_t ret; + + /* get values to work on */ + len = strlen(label) + 1; + uri = isc_mem_get(mctx, len); + memmove(uri, label, len); + + /* get the URI scheme */ + p = strchr(uri, ':'); + if (p == NULL) { + DST_RET(PK11_R_NOPROVIDER); + } + *p++ = '\0'; + if (strcmp(uri, "pkcs11") != 0) { + DST_RET(PK11_R_NOPROVIDER); + } + + /* get attributes */ + for (na = p; na != NULL;) { + a = na; + p = strchr(a, ';'); + if (p == NULL) { + /* last attribute */ + na = NULL; + } else { + *p++ = '\0'; + na = p; + } + p = strchr(a, '='); + if (p != NULL) { + *p++ = '\0'; + v = p; + } else { + v = a; + } + l = 0; + v = percent_decode(v, &l); + if (v == NULL) { + DST_RET(PK11_R_NOPROVIDER); + } + if ((a == v) || (strcmp(a, "object") == 0)) { + /* object: CKA_LABEL */ + attr = pk11_attribute_bytype(obj, CKA_LABEL); + if (attr != NULL) { + DST_RET(PK11_R_NOPROVIDER); + } + attr = push_attribute(obj, mctx, l); + if (attr == NULL) { + DST_RET(ISC_R_NOMEMORY); + } + attr->type = CKA_LABEL; + memmove(attr->pValue, v, l); + } else if (strcmp(a, "token") == 0) { + /* token: CK_TOKEN_INFO label */ + if (token == NULL) { + for (token = ISC_LIST_HEAD(tokens); + token != NULL; + token = ISC_LIST_NEXT(token, link)) + { + if (pk11strcmp(v, l, token->name, 32)) { + break; + } + } + } + } else if (strcmp(a, "manufacturer") == 0) { + /* manufacturer: CK_TOKEN_INFO manufacturerID */ + if (token == NULL) { + for (token = ISC_LIST_HEAD(tokens); + token != NULL; + token = ISC_LIST_NEXT(token, link)) + { + if (pk11strcmp(v, l, token->manuf, 32)) + { + break; + } + } + } + } else if (strcmp(a, "serial") == 0) { + /* serial: CK_TOKEN_INFO serialNumber */ + if (token == NULL) { + for (token = ISC_LIST_HEAD(tokens); + token != NULL; + token = ISC_LIST_NEXT(token, link)) + { + if (pk11strcmp(v, l, token->serial, 16)) + { + break; + } + } + } + } else if (strcmp(a, "model") == 0) { + /* model: CK_TOKEN_INFO model */ + if (token == NULL) { + for (token = ISC_LIST_HEAD(tokens); + token != NULL; + token = ISC_LIST_NEXT(token, link)) + { + if (pk11strcmp(v, l, token->model, 16)) + { + break; + } + } + } + } else if (strcmp(a, "library-manufacturer") == 0) { + /* ignored */ + } else if (strcmp(a, "library-description") == 0) { + /* ignored */ + } else if (strcmp(a, "library-version") == 0) { + /* ignored */ + } else if (strcmp(a, "object-type") == 0) { + /* object-type: CKA_CLASS */ + /* only private makes sense */ + if (strcmp(v, "private") != 0) { + DST_RET(PK11_R_NOPROVIDER); + } + } else if (strcmp(a, "id") == 0) { + /* id: CKA_ID */ + attr = pk11_attribute_bytype(obj, CKA_ID); + if (attr != NULL) { + DST_RET(PK11_R_NOPROVIDER); + } + attr = push_attribute(obj, mctx, l); + if (attr == NULL) { + DST_RET(ISC_R_NOMEMORY); + } + attr->type = CKA_ID; + memmove(attr->pValue, v, l); + } else if (strcmp(a, "pin-source") == 0) { + /* pin-source: PIN */ + ret = isc_stdio_open(v, "r", &stream); + if (ret != ISC_R_SUCCESS) { + goto err; + } + memset(pin, 0, PINLEN + 1); + ret = isc_stdio_read(pin, 1, PINLEN + 1, stream, &l); + if ((ret != ISC_R_SUCCESS) && (ret != ISC_R_EOF)) { + goto err; + } + if (l > PINLEN) { + DST_RET(ISC_R_RANGE); + } + ret = isc_stdio_close(stream); + stream = NULL; + if (ret != ISC_R_SUCCESS) { + goto err; + } + gotpin = true; + } else { + DST_RET(PK11_R_NOPROVIDER); + } + } + + if ((pk11_attribute_bytype(obj, CKA_LABEL) == NULL) && + (pk11_attribute_bytype(obj, CKA_ID) == NULL)) + { + DST_RET(ISC_R_NOTFOUND); + } + + if (token == NULL) { + if (optype == OP_RSA) { + token = best_rsa_token; + } else if (optype == OP_ECDSA) { + token = best_ecdsa_token; + } else if (optype == OP_EDDSA) { + token = best_eddsa_token; + } + } + if (token == NULL) { + DST_RET(ISC_R_NOTFOUND); + } + obj->slot = token->slotid; + if (gotpin) { + memmove(token->pin, pin, PINLEN + 1); + obj->reqlogon = true; + } + + ret = ISC_R_SUCCESS; + +err: + if (stream != NULL) { + (void)isc_stdio_close(stream); + } + isc_mem_put(mctx, uri, len); + return (ret); +} + +void +pk11_error_fatalcheck(const char *file, int line, const char *funcname, + CK_RV rv) { + isc_error_fatal(file, line, "%s: Error = 0x%.8lX\n", funcname, rv); +} + +void +pk11_dump_tokens(void) { + pk11_token_t *token; + bool first; + + printf("DEFAULTS\n"); + printf("\tbest_rsa_token=%p\n", best_rsa_token); + printf("\tbest_ecdsa_token=%p\n", best_ecdsa_token); + printf("\tbest_eddsa_token=%p\n", best_eddsa_token); + + for (token = ISC_LIST_HEAD(tokens); token != NULL; + token = ISC_LIST_NEXT(token, link)) + { + printf("\nTOKEN\n"); + printf("\taddress=%p\n", token); + printf("\tslotID=%lu\n", token->slotid); + printf("\tlabel=%.32s\n", token->name); + printf("\tmanufacturerID=%.32s\n", token->manuf); + printf("\tmodel=%.16s\n", token->model); + printf("\tserialNumber=%.16s\n", token->serial); + printf("\tsupported operations=0x%x (", token->operations); + first = true; + if (token->operations & (1 << OP_RSA)) { + first = false; + printf("RSA"); + } + if (token->operations & (1 << OP_ECDSA)) { + if (!first) { + printf(","); + } + printf("EC"); + } + printf(")\n"); + } +} diff --git a/lib/isc/pk11_result.c b/lib/isc/pk11_result.c new file mode 100644 index 0000000..73966fa --- /dev/null +++ b/lib/isc/pk11_result.c @@ -0,0 +1,73 @@ +/* + * 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. + */ + +#include <stddef.h> + +#include <isc/once.h> +#include <isc/util.h> + +#include <pk11/result.h> + +static const char *text[PK11_R_NRESULTS] = { + "PKCS#11 initialization failed", /*%< 0 */ + "no PKCS#11 provider", /*%< 1 */ + "PKCS#11 no random service", /*%< 2 */ + "PKCS#11 no digist service", /*%< 3 */ + "PKCS#11 no AES service", /*%< 4 */ +}; + +static const char *ids[PK11_R_NRESULTS] = { + "PK11_R_INITFAILED", "PK11_R_NOPROVIDER", + "PK11_R_NORANDOMSERVICE", "PK11_R_NODIGESTSERVICE", + "PK11_R_NOAESSERVICE", +}; + +#define PK11_RESULT_RESULTSET 2 + +static isc_once_t once = ISC_ONCE_INIT; + +static void +initialize_action(void) { + isc_result_t result; + + result = isc_result_register(ISC_RESULTCLASS_PK11, PK11_R_NRESULTS, + text, PK11_RESULT_RESULTSET); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_result_register() failed: %u", result); + } + + result = isc_result_registerids(ISC_RESULTCLASS_PK11, PK11_R_NRESULTS, + ids, PK11_RESULT_RESULTSET); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_result_registerids() failed: %u", result); + } +} + +static void +initialize(void) { + RUNTIME_CHECK(isc_once_do(&once, initialize_action) == ISC_R_SUCCESS); +} + +const char * +pk11_result_totext(isc_result_t result) { + initialize(); + + return (isc_result_totext(result)); +} + +void +pk11_result_register(void) { + initialize(); +} diff --git a/lib/isc/pool.c b/lib/isc/pool.c new file mode 100644 index 0000000..5a5fb12 --- /dev/null +++ b/lib/isc/pool.c @@ -0,0 +1,163 @@ +/* + * 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. + */ + +/*! \file */ + +#include <string.h> + +#include <isc/mem.h> +#include <isc/pool.h> +#include <isc/random.h> +#include <isc/util.h> + +/*** + *** Types. + ***/ + +struct isc_pool { + isc_mem_t *mctx; + unsigned int count; + isc_pooldeallocator_t free; + isc_poolinitializer_t init; + void *initarg; + void **pool; +}; + +/*** + *** Functions. + ***/ + +static isc_result_t +alloc_pool(isc_mem_t *mctx, unsigned int count, isc_pool_t **poolp) { + isc_pool_t *pool; + + pool = isc_mem_get(mctx, sizeof(*pool)); + pool->count = count; + pool->free = NULL; + pool->init = NULL; + pool->initarg = NULL; + pool->mctx = NULL; + isc_mem_attach(mctx, &pool->mctx); + pool->pool = isc_mem_get(mctx, count * sizeof(void *)); + memset(pool->pool, 0, count * sizeof(void *)); + + *poolp = pool; + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_pool_create(isc_mem_t *mctx, unsigned int count, + isc_pooldeallocator_t release, isc_poolinitializer_t init, + void *initarg, isc_pool_t **poolp) { + isc_pool_t *pool = NULL; + isc_result_t result; + unsigned int i; + + INSIST(count > 0); + + /* Allocate the pool structure */ + result = alloc_pool(mctx, count, &pool); + if (result != ISC_R_SUCCESS) { + return (result); + } + + pool->free = release; + pool->init = init; + pool->initarg = initarg; + + /* Populate the pool */ + for (i = 0; i < count; i++) { + result = init(&pool->pool[i], initarg); + if (result != ISC_R_SUCCESS) { + isc_pool_destroy(&pool); + return (result); + } + } + + *poolp = pool; + return (ISC_R_SUCCESS); +} + +void * +isc_pool_get(isc_pool_t *pool) { + return (pool->pool[isc_random_uniform(pool->count)]); +} + +int +isc_pool_count(isc_pool_t *pool) { + REQUIRE(pool != NULL); + return (pool->count); +} + +isc_result_t +isc_pool_expand(isc_pool_t **sourcep, unsigned int count, + isc_pool_t **targetp) { + isc_result_t result; + isc_pool_t *pool; + + REQUIRE(sourcep != NULL && *sourcep != NULL); + REQUIRE(targetp != NULL && *targetp == NULL); + + pool = *sourcep; + *sourcep = NULL; + if (count > pool->count) { + isc_pool_t *newpool = NULL; + unsigned int i; + + /* Allocate a new pool structure */ + result = alloc_pool(pool->mctx, count, &newpool); + if (result != ISC_R_SUCCESS) { + return (result); + } + + newpool->free = pool->free; + newpool->init = pool->init; + newpool->initarg = pool->initarg; + + /* Populate the new entries */ + for (i = pool->count; i < count; i++) { + result = newpool->init(&newpool->pool[i], + newpool->initarg); + if (result != ISC_R_SUCCESS) { + isc_pool_destroy(&newpool); + return (result); + } + } + + /* Copy over the objects from the old pool */ + for (i = 0; i < pool->count; i++) { + newpool->pool[i] = pool->pool[i]; + pool->pool[i] = NULL; + } + + isc_pool_destroy(&pool); + pool = newpool; + } + + *targetp = pool; + return (ISC_R_SUCCESS); +} + +void +isc_pool_destroy(isc_pool_t **poolp) { + unsigned int i; + isc_pool_t *pool = *poolp; + *poolp = NULL; + for (i = 0; i < pool->count; i++) { + if (pool->free != NULL && pool->pool[i] != NULL) { + pool->free(&pool->pool[i]); + } + } + isc_mem_put(pool->mctx, pool->pool, pool->count * sizeof(void *)); + isc_mem_putanddetach(&pool->mctx, pool, sizeof(*pool)); +} diff --git a/lib/isc/portset.c b/lib/isc/portset.c new file mode 100644 index 0000000..52b96f5 --- /dev/null +++ b/lib/isc/portset.c @@ -0,0 +1,135 @@ +/* + * 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. + */ + +/*! \file */ + +#include <inttypes.h> +#include <stdbool.h> + +#include <isc/mem.h> +#include <isc/portset.h> +#include <isc/string.h> +#include <isc/types.h> +#include <isc/util.h> + +#define ISC_PORTSET_BUFSIZE (65536 / (sizeof(uint32_t) * 8)) + +/*% + * Internal representation of portset. It's an array of 32-bit integers, each + * bit corresponding to a single port in the ascending order. For example, + * the second most significant bit of buf[0] corresponds to port 1. + */ +struct isc_portset { + unsigned int nports; /*%< number of ports in the set */ + uint32_t buf[ISC_PORTSET_BUFSIZE]; +}; + +static bool +portset_isset(isc_portset_t *portset, in_port_t port) { + return ((portset->buf[port >> 5] & ((uint32_t)1 << (port & 31))) != 0); +} + +static void +portset_add(isc_portset_t *portset, in_port_t port) { + if (!portset_isset(portset, port)) { + portset->nports++; + portset->buf[port >> 5] |= ((uint32_t)1 << (port & 31)); + } +} + +static void +portset_remove(isc_portset_t *portset, in_port_t port) { + if (portset_isset(portset, port)) { + portset->nports--; + portset->buf[port >> 5] &= ~((uint32_t)1 << (port & 31)); + } +} + +isc_result_t +isc_portset_create(isc_mem_t *mctx, isc_portset_t **portsetp) { + isc_portset_t *portset; + + REQUIRE(portsetp != NULL && *portsetp == NULL); + + portset = isc_mem_get(mctx, sizeof(*portset)); + + /* Make the set 'empty' by default */ + memset(portset, 0, sizeof(*portset)); + *portsetp = portset; + + return (ISC_R_SUCCESS); +} + +void +isc_portset_destroy(isc_mem_t *mctx, isc_portset_t **portsetp) { + isc_portset_t *portset; + + REQUIRE(portsetp != NULL); + portset = *portsetp; + + isc_mem_put(mctx, portset, sizeof(*portset)); +} + +bool +isc_portset_isset(isc_portset_t *portset, in_port_t port) { + REQUIRE(portset != NULL); + + return (portset_isset(portset, port)); +} + +unsigned int +isc_portset_nports(isc_portset_t *portset) { + REQUIRE(portset != NULL); + + return (portset->nports); +} + +void +isc_portset_add(isc_portset_t *portset, in_port_t port) { + REQUIRE(portset != NULL); + + portset_add(portset, port); +} + +void +isc_portset_remove(isc_portset_t *portset, in_port_t port) { + portset_remove(portset, port); +} + +void +isc_portset_addrange(isc_portset_t *portset, in_port_t port_lo, + in_port_t port_hi) { + in_port_t p; + + REQUIRE(portset != NULL); + REQUIRE(port_lo <= port_hi); + + p = port_lo; + do { + portset_add(portset, p); + } while (p++ < port_hi); +} + +void +isc_portset_removerange(isc_portset_t *portset, in_port_t port_lo, + in_port_t port_hi) { + in_port_t p; + + REQUIRE(portset != NULL); + REQUIRE(port_lo <= port_hi); + + p = port_lo; + do { + portset_remove(portset, p); + } while (p++ < port_hi); +} diff --git a/lib/isc/pthreads/Makefile.in b/lib/isc/pthreads/Makefile.in new file mode 100644 index 0000000..c79e949 --- /dev/null +++ b/lib/isc/pthreads/Makefile.in @@ -0,0 +1,32 @@ +# 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. + +srcdir = @srcdir@ +VPATH = @srcdir@ +top_srcdir = @top_srcdir@ + +CINCLUDES = -I${srcdir}/include \ + -I${srcdir}/../unix/include \ + -I../include \ + -I${srcdir}/../include \ + -I${srcdir}/.. + +CDEFINES = +CWARNINGS = + +OBJS = condition.@O@ mutex.@O@ thread.@O@ + +SRCS = condition.c mutex.c thread.c + +SUBDIRS = include +TARGETS = ${OBJS} + +@BIND9_MAKE_RULES@ diff --git a/lib/isc/pthreads/condition.c b/lib/isc/pthreads/condition.c new file mode 100644 index 0000000..7657b18 --- /dev/null +++ b/lib/isc/pthreads/condition.c @@ -0,0 +1,72 @@ +/* + * 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. + */ + +/*! \file */ + +#include <errno.h> + +#include <isc/condition.h> +#include <isc/strerr.h> +#include <isc/string.h> +#include <isc/time.h> +#include <isc/util.h> + +isc_result_t +isc_condition_waituntil(isc_condition_t *c, isc_mutex_t *m, isc_time_t *t) { + int presult; + isc_result_t result; + struct timespec ts; + char strbuf[ISC_STRERRORSIZE]; + + REQUIRE(c != NULL && m != NULL && t != NULL); + + /* + * POSIX defines a timespec's tv_sec as time_t. + */ + result = isc_time_secondsastimet(t, &ts.tv_sec); + + /* + * If we have a range error ts.tv_sec is most probably a signed + * 32 bit value. Set ts.tv_sec to INT_MAX. This is a kludge. + */ + if (result == ISC_R_RANGE) { + ts.tv_sec = INT_MAX; + } else if (result != ISC_R_SUCCESS) { + return (result); + } + + /*! + * POSIX defines a timespec's tv_nsec as long. isc_time_nanoseconds + * ensures its return value is < 1 billion, which will fit in a long. + */ + ts.tv_nsec = (long)isc_time_nanoseconds(t); + + do { +#if ISC_MUTEX_PROFILE + presult = pthread_cond_timedwait(c, &m->mutex, &ts); +#else /* if ISC_MUTEX_PROFILE */ + presult = pthread_cond_timedwait(c, m, &ts); +#endif /* if ISC_MUTEX_PROFILE */ + if (presult == 0) { + return (ISC_R_SUCCESS); + } + if (presult == ETIMEDOUT) { + return (ISC_R_TIMEDOUT); + } + } while (presult == EINTR); + + strerror_r(presult, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "pthread_cond_timedwait() returned %s", strbuf); + return (ISC_R_UNEXPECTED); +} diff --git a/lib/isc/pthreads/include/.clang-format b/lib/isc/pthreads/include/.clang-format new file mode 120000 index 0000000..e919bba --- /dev/null +++ b/lib/isc/pthreads/include/.clang-format @@ -0,0 +1 @@ +../../../../.clang-format.headers
\ No newline at end of file diff --git a/lib/isc/pthreads/include/Makefile.in b/lib/isc/pthreads/include/Makefile.in new file mode 100644 index 0000000..60a0a67 --- /dev/null +++ b/lib/isc/pthreads/include/Makefile.in @@ -0,0 +1,19 @@ +# 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. + +srcdir = @srcdir@ +VPATH = @srcdir@ +top_srcdir = @top_srcdir@ + +SUBDIRS = isc +TARGETS = + +@BIND9_MAKE_RULES@ diff --git a/lib/isc/pthreads/include/isc/Makefile.in b/lib/isc/pthreads/include/isc/Makefile.in new file mode 100644 index 0000000..e92364d --- /dev/null +++ b/lib/isc/pthreads/include/isc/Makefile.in @@ -0,0 +1,36 @@ +# 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. + +srcdir = @srcdir@ +VPATH = @srcdir@ +top_srcdir = @top_srcdir@ + +VERSION=@BIND9_VERSION@ + +HEADERS = condition.h mutex.h once.h thread.h + +SUBDIRS = +TARGETS = + +@BIND9_MAKE_RULES@ + +installdirs: + $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${includedir}/isc + +install:: installdirs + for i in ${HEADERS}; do \ + ${INSTALL_DATA} $(srcdir)/$$i ${DESTDIR}${includedir}/isc || exit 1; \ + done + +uninstall:: + for i in ${HEADERS}; do \ + rm -f ${DESTDIR}${includedir}/isc/$$i || exit 1; \ + done diff --git a/lib/isc/pthreads/include/isc/condition.h b/lib/isc/pthreads/include/isc/condition.h new file mode 100644 index 0000000..8a79eb4 --- /dev/null +++ b/lib/isc/pthreads/include/isc/condition.h @@ -0,0 +1,64 @@ +/* + * 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. + */ + +#pragma once + +/*! \file */ + +#include <errno.h> + +#include <isc/error.h> +#include <isc/lang.h> +#include <isc/mutex.h> +#include <isc/result.h> +#include <isc/strerr.h> +#include <isc/string.h> +#include <isc/types.h> + +typedef pthread_cond_t isc_condition_t; + +#define isc_condition_init(cond) \ + if (pthread_cond_init(cond, NULL) != 0) { \ + char isc_condition_strbuf[ISC_STRERRORSIZE]; \ + strerror_r(errno, isc_condition_strbuf, \ + sizeof(isc_condition_strbuf)); \ + isc_error_fatal(__FILE__, __LINE__, \ + "pthread_cond_init failed: %s", \ + isc_condition_strbuf); \ + } + +#if ISC_MUTEX_PROFILE +#define isc_condition_wait(cp, mp) \ + ((pthread_cond_wait((cp), &((mp)->mutex)) == 0) ? ISC_R_SUCCESS \ + : ISC_R_UNEXPECTED) +#else /* if ISC_MUTEX_PROFILE */ +#define isc_condition_wait(cp, mp) \ + ((pthread_cond_wait((cp), (mp)) == 0) ? ISC_R_SUCCESS \ + : ISC_R_UNEXPECTED) +#endif /* if ISC_MUTEX_PROFILE */ + +#define isc_condition_signal(cp) \ + ((pthread_cond_signal((cp)) == 0) ? ISC_R_SUCCESS : ISC_R_UNEXPECTED) + +#define isc_condition_broadcast(cp) \ + ((pthread_cond_broadcast((cp)) == 0) ? ISC_R_SUCCESS : ISC_R_UNEXPECTED) + +#define isc_condition_destroy(cp) \ + ((pthread_cond_destroy((cp)) == 0) ? ISC_R_SUCCESS : ISC_R_UNEXPECTED) + +ISC_LANG_BEGINDECLS + +isc_result_t +isc_condition_waituntil(isc_condition_t *, isc_mutex_t *, isc_time_t *); + +ISC_LANG_ENDDECLS diff --git a/lib/isc/pthreads/include/isc/mutex.h b/lib/isc/pthreads/include/isc/mutex.h new file mode 100644 index 0000000..2390cd9 --- /dev/null +++ b/lib/isc/pthreads/include/isc/mutex.h @@ -0,0 +1,129 @@ +/* + * 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. + */ + +#ifndef ISC_MUTEX_H +#define ISC_MUTEX_H 1 + +/*! \file */ + +#include <pthread.h> +#include <stdio.h> + +#include <isc/lang.h> +#include <isc/result.h> /* for ISC_R_ codes */ + +ISC_LANG_BEGINDECLS + +/*! + * Supply mutex attributes that enable deadlock detection + * (helpful when debugging). This is system dependent and + * currently only supported on NetBSD. + */ +#if ISC_MUTEX_DEBUG && defined(__NetBSD__) && defined(PTHREAD_MUTEX_ERRORCHECK) +extern pthread_mutexattr_t isc__mutex_attrs; +#define ISC__MUTEX_ATTRS &isc__mutex_attrs +#else /* if ISC_MUTEX_DEBUG && defined(__NetBSD__) && \ + * defined(PTHREAD_MUTEX_ERRORCHECK) */ +#define ISC__MUTEX_ATTRS NULL +#endif /* if ISC_MUTEX_DEBUG && defined(__NetBSD__) && \ + * defined(PTHREAD_MUTEX_ERRORCHECK) */ + +/* XXX We could do fancier error handling... */ + +/*! + * Define ISC_MUTEX_PROFILE to turn on profiling of mutexes by line. When + * enabled, isc_mutex_stats() can be used to print a table showing the + * number of times each type of mutex was locked and the amount of time + * waiting to obtain the lock. + */ +#ifndef ISC_MUTEX_PROFILE +#define ISC_MUTEX_PROFILE 0 +#endif /* ifndef ISC_MUTEX_PROFILE */ + +#if ISC_MUTEX_PROFILE +typedef struct isc_mutexstats isc_mutexstats_t; + +typedef struct { + pthread_mutex_t mutex; /*%< The actual mutex. */ + isc_mutexstats_t *stats; /*%< Mutex statistics. */ +} isc_mutex_t; +#else /* if ISC_MUTEX_PROFILE */ +typedef pthread_mutex_t isc_mutex_t; +#endif /* if ISC_MUTEX_PROFILE */ + +#if ISC_MUTEX_PROFILE +#define isc_mutex_init(mp) isc_mutex_init_profile((mp), __FILE__, __LINE__) +#else /* if ISC_MUTEX_PROFILE */ +#if ISC_MUTEX_DEBUG && defined(PTHREAD_MUTEX_ERRORCHECK) +#define isc_mutex_init(mp) isc_mutex_init_errcheck((mp)) +#else /* if ISC_MUTEX_DEBUG && defined(PTHREAD_MUTEX_ERRORCHECK) */ +#define isc_mutex_init(mp) isc__mutex_init((mp), __FILE__, __LINE__) +void +isc__mutex_init(isc_mutex_t *mp, const char *file, unsigned int line); +#endif /* if ISC_MUTEX_DEBUG && defined(PTHREAD_MUTEX_ERRORCHECK) */ +#endif /* if ISC_MUTEX_PROFILE */ + +#if ISC_MUTEX_PROFILE +#define isc_mutex_lock(mp) isc_mutex_lock_profile((mp), __FILE__, __LINE__) +#else /* if ISC_MUTEX_PROFILE */ +#define isc_mutex_lock(mp) \ + ((pthread_mutex_lock((mp)) == 0) ? ISC_R_SUCCESS : ISC_R_UNEXPECTED) +#endif /* if ISC_MUTEX_PROFILE */ + +#if ISC_MUTEX_PROFILE +#define isc_mutex_unlock(mp) isc_mutex_unlock_profile((mp), __FILE__, __LINE__) +#else /* if ISC_MUTEX_PROFILE */ +#define isc_mutex_unlock(mp) \ + ((pthread_mutex_unlock((mp)) == 0) ? ISC_R_SUCCESS : ISC_R_UNEXPECTED) +#endif /* if ISC_MUTEX_PROFILE */ + +#if ISC_MUTEX_PROFILE +#define isc_mutex_trylock(mp) \ + ((pthread_mutex_trylock((&(mp)->mutex)) == 0) ? ISC_R_SUCCESS \ + : ISC_R_LOCKBUSY) +#else /* if ISC_MUTEX_PROFILE */ +#define isc_mutex_trylock(mp) \ + ((pthread_mutex_trylock((mp)) == 0) ? ISC_R_SUCCESS : ISC_R_LOCKBUSY) +#endif /* if ISC_MUTEX_PROFILE */ + +#if ISC_MUTEX_PROFILE +#define isc_mutex_destroy(mp) \ + RUNTIME_CHECK(pthread_mutex_destroy((&(mp)->mutex)) == 0) +#else /* if ISC_MUTEX_PROFILE */ +#define isc_mutex_destroy(mp) RUNTIME_CHECK(pthread_mutex_destroy((mp)) == 0) +#endif /* if ISC_MUTEX_PROFILE */ + +#if ISC_MUTEX_PROFILE +#define isc_mutex_stats(fp) isc_mutex_statsprofile(fp); +#else /* if ISC_MUTEX_PROFILE */ +#define isc_mutex_stats(fp) +#endif /* if ISC_MUTEX_PROFILE */ + +#if ISC_MUTEX_PROFILE + +void +isc_mutex_init_profile(isc_mutex_t *mp, const char *_file, int _line); +isc_result_t +isc_mutex_lock_profile(isc_mutex_t *mp, const char *_file, int _line); +isc_result_t +isc_mutex_unlock_profile(isc_mutex_t *mp, const char *_file, int _line); + +void +isc_mutex_statsprofile(FILE *fp); +#endif /* ISC_MUTEX_PROFILE */ + +void +isc_mutex_init_errcheck(isc_mutex_t *mp); + +ISC_LANG_ENDDECLS +#endif /* ISC_MUTEX_H */ diff --git a/lib/isc/pthreads/include/isc/once.h b/lib/isc/pthreads/include/isc/once.h new file mode 100644 index 0000000..481dba5 --- /dev/null +++ b/lib/isc/pthreads/include/isc/once.h @@ -0,0 +1,33 @@ +/* + * 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. + */ + +#ifndef ISC_ONCE_H +#define ISC_ONCE_H 1 + +/*! \file */ + +#include <pthread.h> + +#include <isc/platform.h> +#include <isc/result.h> + +typedef pthread_once_t isc_once_t; + +#define ISC_ONCE_INIT PTHREAD_ONCE_INIT + +/* XXX We could do fancier error handling... */ + +#define isc_once_do(op, f) \ + ((pthread_once((op), (f)) == 0) ? ISC_R_SUCCESS : ISC_R_UNEXPECTED) + +#endif /* ISC_ONCE_H */ diff --git a/lib/isc/pthreads/include/isc/thread.h b/lib/isc/pthreads/include/isc/thread.h new file mode 100644 index 0000000..6f83191 --- /dev/null +++ b/lib/isc/pthreads/include/isc/thread.h @@ -0,0 +1,74 @@ +/* + * 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. + */ + +#ifndef ISC_THREAD_H +#define ISC_THREAD_H 1 + +/*! \file */ + +#include <pthread.h> + +#if defined(HAVE_PTHREAD_NP_H) +#include <pthread_np.h> +#endif /* if defined(HAVE_PTHREAD_NP_H) */ + +#include <isc/lang.h> +#include <isc/result.h> + +#if defined(HAVE_THREAD_LOCAL) +#include <threads.h> +extern thread_local size_t isc_tid_v; +#elif defined(HAVE___THREAD) +extern __thread size_t isc_tid_v; +#endif /* if defined(HAVE_THREAD_LOCAL) */ + +ISC_LANG_BEGINDECLS + +typedef pthread_t isc_thread_t; +typedef void *isc_threadresult_t; +typedef void *isc_threadarg_t; +typedef isc_threadresult_t (*isc_threadfunc_t)(isc_threadarg_t); + +void +isc_thread_create(isc_threadfunc_t, isc_threadarg_t, isc_thread_t *); + +void +isc_thread_join(isc_thread_t thread, isc_threadresult_t *result); + +void +isc_thread_yield(void); + +void +isc_thread_setname(isc_thread_t thread, const char *name); + +#define isc_thread_self (uintptr_t) pthread_self + +/*** + *** Thread-Local Storage + ***/ + +#if defined(HAVE_TLS) +#if defined(HAVE_THREAD_LOCAL) +#define ISC_THREAD_LOCAL static thread_local +#elif defined(HAVE___THREAD) +#define ISC_THREAD_LOCAL static __thread +#else /* if defined(HAVE_THREAD_LOCAL) */ +#error "Unknown method for defining a TLS variable!" +#endif /* if defined(HAVE_THREAD_LOCAL) */ +#else /* if defined(HAVE_TLS) */ +#error "Thread-local storage support is required!" +#endif /* if defined(HAVE_TLS) */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_THREAD_H */ diff --git a/lib/isc/pthreads/mutex.c b/lib/isc/pthreads/mutex.c new file mode 100644 index 0000000..aa3dc53 --- /dev/null +++ b/lib/isc/pthreads/mutex.c @@ -0,0 +1,302 @@ +/* + * 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. + */ + +/*! \file */ + +#include <errno.h> +#include <stdbool.h> +#include <stdio.h> +#include <sys/time.h> +#include <time.h> + +#include <isc/mutex.h> +#include <isc/once.h> +#include <isc/print.h> +#include <isc/strerr.h> +#include <isc/string.h> +#include <isc/util.h> + +#if ISC_MUTEX_PROFILE + +/*@{*/ +/*% Operations on timevals; adapted from FreeBSD's sys/time.h */ +#define timevalclear(tvp) ((tvp)->tv_sec = (tvp)->tv_usec = 0) +#define timevaladd(vvp, uvp) \ + do { \ + (vvp)->tv_sec += (uvp)->tv_sec; \ + (vvp)->tv_usec += (uvp)->tv_usec; \ + if ((vvp)->tv_usec >= 1000000) { \ + (vvp)->tv_sec++; \ + (vvp)->tv_usec -= 1000000; \ + } \ + } while (0) +#define timevalsub(vvp, uvp) \ + do { \ + (vvp)->tv_sec -= (uvp)->tv_sec; \ + (vvp)->tv_usec -= (uvp)->tv_usec; \ + if ((vvp)->tv_usec < 0) { \ + (vvp)->tv_sec--; \ + (vvp)->tv_usec += 1000000; \ + } \ + } while (0) + +/*@}*/ + +#define ISC_MUTEX_MAX_LOCKERS 32 + +typedef struct { + const char *file; + int line; + unsigned count; + struct timeval locked_total; + struct timeval wait_total; +} isc_mutexlocker_t; + +struct isc_mutexstats { + const char *file; /*%< File mutex was created in. */ + int line; /*%< Line mutex was created on. */ + unsigned count; + struct timeval lock_t; + struct timeval locked_total; + struct timeval wait_total; + isc_mutexlocker_t *cur_locker; + isc_mutexlocker_t lockers[ISC_MUTEX_MAX_LOCKERS]; +}; + +#ifndef ISC_MUTEX_PROFTABLESIZE +#define ISC_MUTEX_PROFTABLESIZE (1024 * 1024) +#endif /* ifndef ISC_MUTEX_PROFTABLESIZE */ +static isc_mutexstats_t stats[ISC_MUTEX_PROFTABLESIZE]; +static int stats_next = 0; +static bool stats_init = false; +static pthread_mutex_t statslock = PTHREAD_MUTEX_INITIALIZER; + +void +isc_mutex_init_profile(isc_mutex_t *mp, const char *file, int line) { + int i, err; + + err = pthread_mutex_init(&mp->mutex, NULL); + if (err != 0) { + strerror_r(err, strbuf, sizeof(strbuf)); + isc_error_fatal(file, line, "pthread_mutex_init failed: %s", + strbuf); + } + + RUNTIME_CHECK(pthread_mutex_lock(&statslock) == 0); + + if (!stats_init) { + stats_init = true; + } + + /* + * If all statistics entries have been used, give up and trigger an + * assertion failure. There would be no other way to deal with this + * because we'd like to keep record of all locks for the purpose of + * debugging and the number of necessary locks is unpredictable. + * If this failure is triggered while debugging, named should be + * rebuilt with an increased ISC_MUTEX_PROFTABLESIZE. + */ + RUNTIME_CHECK(stats_next < ISC_MUTEX_PROFTABLESIZE); + mp->stats = &stats[stats_next++]; + + RUNTIME_CHECK(pthread_mutex_unlock(&statslock) == 0); + + mp->stats->file = file; + mp->stats->line = line; + mp->stats->count = 0; + timevalclear(&mp->stats->locked_total); + timevalclear(&mp->stats->wait_total); + for (i = 0; i < ISC_MUTEX_MAX_LOCKERS; i++) { + mp->stats->lockers[i].file = NULL; + mp->stats->lockers[i].line = 0; + mp->stats->lockers[i].count = 0; + timevalclear(&mp->stats->lockers[i].locked_total); + timevalclear(&mp->stats->lockers[i].wait_total); + } +} + +isc_result_t +isc_mutex_lock_profile(isc_mutex_t *mp, const char *file, int line) { + struct timeval prelock_t; + struct timeval postlock_t; + isc_mutexlocker_t *locker = NULL; + int i; + + gettimeofday(&prelock_t, NULL); + + if (pthread_mutex_lock(&mp->mutex) != 0) { + return (ISC_R_UNEXPECTED); + } + + gettimeofday(&postlock_t, NULL); + mp->stats->lock_t = postlock_t; + + timevalsub(&postlock_t, &prelock_t); + + mp->stats->count++; + timevaladd(&mp->stats->wait_total, &postlock_t); + + for (i = 0; i < ISC_MUTEX_MAX_LOCKERS; i++) { + if (mp->stats->lockers[i].file == NULL) { + locker = &mp->stats->lockers[i]; + locker->file = file; + locker->line = line; + break; + } else if (mp->stats->lockers[i].file == file && + mp->stats->lockers[i].line == line) + { + locker = &mp->stats->lockers[i]; + break; + } + } + + if (locker != NULL) { + locker->count++; + timevaladd(&locker->wait_total, &postlock_t); + } + + mp->stats->cur_locker = locker; + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_mutex_unlock_profile(isc_mutex_t *mp, const char *file, int line) { + struct timeval unlock_t; + + UNUSED(file); + UNUSED(line); + + if (mp->stats->cur_locker != NULL) { + gettimeofday(&unlock_t, NULL); + timevalsub(&unlock_t, &mp->stats->lock_t); + timevaladd(&mp->stats->locked_total, &unlock_t); + timevaladd(&mp->stats->cur_locker->locked_total, &unlock_t); + mp->stats->cur_locker = NULL; + } + + return ((pthread_mutex_unlock((&mp->mutex)) == 0) ? ISC_R_SUCCESS + : ISC_R_UNEXPECTED); +} + +void +isc_mutex_statsprofile(FILE *fp) { + isc_mutexlocker_t *locker; + int i, j; + + fprintf(fp, "Mutex stats (in us)\n"); + for (i = 0; i < stats_next; i++) { + fprintf(fp, "%-12s %4d: %10u %lu.%06lu %lu.%06lu %5d\n", + stats[i].file, stats[i].line, stats[i].count, + stats[i].locked_total.tv_sec, + stats[i].locked_total.tv_usec, + stats[i].wait_total.tv_sec, stats[i].wait_total.tv_usec, + i); + for (j = 0; j < ISC_MUTEX_MAX_LOCKERS; j++) { + locker = &stats[i].lockers[j]; + if (locker->file == NULL) { + continue; + } + fprintf(fp, + " %-11s %4d: %10u %lu.%06lu %lu.%06lu %5d\n", + locker->file, locker->line, locker->count, + locker->locked_total.tv_sec, + locker->locked_total.tv_usec, + locker->wait_total.tv_sec, + locker->wait_total.tv_usec, i); + } + } +} + +#endif /* ISC_MUTEX_PROFILE */ + +#if ISC_MUTEX_DEBUG && defined(PTHREAD_MUTEX_ERRORCHECK) + +static bool errcheck_initialized = false; +static pthread_mutexattr_t errcheck; +static isc_once_t once_errcheck = ISC_ONCE_INIT; + +static void +initialize_errcheck(void) { + RUNTIME_CHECK(pthread_mutexattr_init(&errcheck) == 0); + RUNTIME_CHECK(pthread_mutexattr_settype(&errcheck, + PTHREAD_MUTEX_ERRORCHECK) == 0); + errcheck_initialized = true; +} + +void +isc_mutex_init_errcheck(isc_mutex_t *mp) { + isc_result_t result; + int err; + + result = isc_once_do(&once_errcheck, initialize_errcheck); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + err = pthread_mutex_init(mp, &errcheck); + if (err != 0) { + strerror_r(err, strbuf, sizeof(strbuf)); + isc_error_fatal(file, line, "pthread_mutex_init failed: %s", + strbuf); + } +} +#endif /* if ISC_MUTEX_DEBUG && defined(PTHREAD_MUTEX_ERRORCHECK) */ + +#if ISC_MUTEX_DEBUG && defined(__NetBSD__) && defined(PTHREAD_MUTEX_ERRORCHECK) +pthread_mutexattr_t isc__mutex_attrs = { + PTHREAD_MUTEX_ERRORCHECK, /* m_type */ + 0 /* m_flags, which appears to be unused. */ +}; +#endif /* if ISC_MUTEX_DEBUG && defined(__NetBSD__) && \ + * defined(PTHREAD_MUTEX_ERRORCHECK) */ + +#if !(ISC_MUTEX_DEBUG && defined(PTHREAD_MUTEX_ERRORCHECK)) && \ + !ISC_MUTEX_PROFILE + +#ifdef HAVE_PTHREAD_MUTEX_ADAPTIVE_NP +static bool attr_initialized = false; +static pthread_mutexattr_t attr; +static isc_once_t once_attr = ISC_ONCE_INIT; +#endif /* HAVE_PTHREAD_MUTEX_ADAPTIVE_NP */ + +#ifdef HAVE_PTHREAD_MUTEX_ADAPTIVE_NP +static void +initialize_attr(void) { + RUNTIME_CHECK(pthread_mutexattr_init(&attr) == 0); + RUNTIME_CHECK(pthread_mutexattr_settype( + &attr, PTHREAD_MUTEX_ADAPTIVE_NP) == 0); + attr_initialized = true; +} +#endif /* HAVE_PTHREAD_MUTEX_ADAPTIVE_NP */ + +void +isc__mutex_init(isc_mutex_t *mp, const char *file, unsigned int line) { + int err; + +#ifdef HAVE_PTHREAD_MUTEX_ADAPTIVE_NP + isc_result_t result = ISC_R_SUCCESS; + result = isc_once_do(&once_attr, initialize_attr); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + err = pthread_mutex_init(mp, &attr); +#else /* HAVE_PTHREAD_MUTEX_ADAPTIVE_NP */ + err = pthread_mutex_init(mp, ISC__MUTEX_ATTRS); +#endif /* HAVE_PTHREAD_MUTEX_ADAPTIVE_NP */ + if (err != 0) { + char strbuf[ISC_STRERRORSIZE]; + strerror_r(err, strbuf, sizeof(strbuf)); + isc_error_fatal(file, line, "pthread_mutex_init failed: %s", + strbuf); + } +} +#endif /* if !(ISC_MUTEX_DEBUG && defined(PTHREAD_MUTEX_ERRORCHECK)) && \ + * !ISC_MUTEX_PROFILE */ diff --git a/lib/isc/pthreads/thread.c b/lib/isc/pthreads/thread.c new file mode 100644 index 0000000..4c7380c --- /dev/null +++ b/lib/isc/pthreads/thread.c @@ -0,0 +1,129 @@ +/* + * 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. + */ + +/*! \file */ + +#if defined(HAVE_SCHED_H) +#include <sched.h> +#endif /* if defined(HAVE_SCHED_H) */ + +#if defined(HAVE_CPUSET_H) +#include <sys/cpuset.h> +#include <sys/param.h> +#endif /* if defined(HAVE_CPUSET_H) */ + +#if defined(HAVE_SYS_PROCSET_H) +#include <sys/processor.h> +#include <sys/procset.h> +#include <sys/types.h> +#endif /* if defined(HAVE_SYS_PROCSET_H) */ + +#include <isc/strerr.h> +#include <isc/thread.h> +#include <isc/util.h> + +#include "trampoline_p.h" + +#ifndef THREAD_MINSTACKSIZE +#define THREAD_MINSTACKSIZE (1024U * 1024) +#endif /* ifndef THREAD_MINSTACKSIZE */ + +#define _FATAL(r, f) \ + { \ + char strbuf[ISC_STRERRORSIZE]; \ + strerror_r(r, strbuf, sizeof(strbuf)); \ + isc_error_fatal(__FILE__, __LINE__, f " failed: %s", strbuf); \ + } + +void +isc_thread_create(isc_threadfunc_t func, isc_threadarg_t arg, + isc_thread_t *thread) { + pthread_attr_t attr; + isc__trampoline_t *trampoline_arg; + + trampoline_arg = isc__trampoline_get(func, arg); + +#if defined(HAVE_PTHREAD_ATTR_GETSTACKSIZE) && \ + defined(HAVE_PTHREAD_ATTR_SETSTACKSIZE) + size_t stacksize; +#endif /* if defined(HAVE_PTHREAD_ATTR_GETSTACKSIZE) && \ + * defined(HAVE_PTHREAD_ATTR_SETSTACKSIZE) */ + int ret; + + pthread_attr_init(&attr); + +#if defined(HAVE_PTHREAD_ATTR_GETSTACKSIZE) && \ + defined(HAVE_PTHREAD_ATTR_SETSTACKSIZE) + ret = pthread_attr_getstacksize(&attr, &stacksize); + if (ret != 0) { + _FATAL(ret, "pthread_attr_getstacksize()"); + } + + if (stacksize < THREAD_MINSTACKSIZE) { + ret = pthread_attr_setstacksize(&attr, THREAD_MINSTACKSIZE); + if (ret != 0) { + _FATAL(ret, "pthread_attr_setstacksize()"); + } + } +#endif /* if defined(HAVE_PTHREAD_ATTR_GETSTACKSIZE) && \ + * defined(HAVE_PTHREAD_ATTR_SETSTACKSIZE) */ + + ret = pthread_create(thread, &attr, isc__trampoline_run, + trampoline_arg); + if (ret != 0) { + _FATAL(ret, "pthread_create()"); + } + + pthread_attr_destroy(&attr); + + return; +} + +void +isc_thread_join(isc_thread_t thread, isc_threadresult_t *result) { + int ret = pthread_join(thread, result); + if (ret != 0) { + _FATAL(ret, "pthread_join()"); + } +} + +void +isc_thread_setname(isc_thread_t thread, const char *name) { +#if defined(HAVE_PTHREAD_SETNAME_NP) && !defined(__APPLE__) + /* + * macOS has pthread_setname_np but only works on the + * current thread so it's not used here + */ +#if defined(__NetBSD__) + (void)pthread_setname_np(thread, name, NULL); +#else /* if defined(__NetBSD__) */ + (void)pthread_setname_np(thread, name); +#endif /* if defined(__NetBSD__) */ +#elif defined(HAVE_PTHREAD_SET_NAME_NP) + (void)pthread_set_name_np(thread, name); +#else /* if defined(HAVE_PTHREAD_SETNAME_NP) && !defined(__APPLE__) */ + UNUSED(thread); + UNUSED(name); +#endif /* if defined(HAVE_PTHREAD_SETNAME_NP) && !defined(__APPLE__) */ +} + +void +isc_thread_yield(void) { +#if defined(HAVE_SCHED_YIELD) + sched_yield(); +#elif defined(HAVE_PTHREAD_YIELD) + pthread_yield(); +#elif defined(HAVE_PTHREAD_YIELD_NP) + pthread_yield_np(); +#endif /* if defined(HAVE_SCHED_YIELD) */ +} diff --git a/lib/isc/quota.c b/lib/isc/quota.c new file mode 100644 index 0000000..02c7176 --- /dev/null +++ b/lib/isc/quota.c @@ -0,0 +1,198 @@ +/* + * 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. + */ + +/*! \file */ + +#include <stddef.h> + +#include <isc/atomic.h> +#include <isc/quota.h> +#include <isc/util.h> + +#define QUOTA_MAGIC ISC_MAGIC('Q', 'U', 'O', 'T') +#define VALID_QUOTA(p) ISC_MAGIC_VALID(p, QUOTA_MAGIC) + +#define QUOTA_CB_MAGIC ISC_MAGIC('Q', 'T', 'C', 'B') +#define VALID_QUOTA_CB(p) ISC_MAGIC_VALID(p, QUOTA_CB_MAGIC) + +void +isc_quota_init(isc_quota_t *quota, unsigned int max) { + atomic_init("a->max, max); + atomic_init("a->used, 0); + atomic_init("a->soft, 0); + atomic_init("a->waiting, 0); + ISC_LIST_INIT(quota->cbs); + isc_mutex_init("a->cblock); + quota->magic = QUOTA_MAGIC; +} + +void +isc_quota_destroy(isc_quota_t *quota) { + REQUIRE(VALID_QUOTA(quota)); + quota->magic = 0; + + INSIST(atomic_load("a->used) == 0); + INSIST(atomic_load("a->waiting) == 0); + INSIST(ISC_LIST_EMPTY(quota->cbs)); + atomic_store_release("a->max, 0); + atomic_store_release("a->used, 0); + atomic_store_release("a->soft, 0); + isc_mutex_destroy("a->cblock); +} + +void +isc_quota_soft(isc_quota_t *quota, unsigned int soft) { + REQUIRE(VALID_QUOTA(quota)); + atomic_store_release("a->soft, soft); +} + +void +isc_quota_max(isc_quota_t *quota, unsigned int max) { + REQUIRE(VALID_QUOTA(quota)); + atomic_store_release("a->max, max); +} + +unsigned int +isc_quota_getmax(isc_quota_t *quota) { + REQUIRE(VALID_QUOTA(quota)); + return (atomic_load_relaxed("a->max)); +} + +unsigned int +isc_quota_getsoft(isc_quota_t *quota) { + REQUIRE(VALID_QUOTA(quota)); + return (atomic_load_relaxed("a->soft)); +} + +unsigned int +isc_quota_getused(isc_quota_t *quota) { + REQUIRE(VALID_QUOTA(quota)); + return (atomic_load_relaxed("a->used)); +} + +static isc_result_t +quota_reserve(isc_quota_t *quota) { + isc_result_t result; + uint_fast32_t max = atomic_load_acquire("a->max); + uint_fast32_t soft = atomic_load_acquire("a->soft); + uint_fast32_t used = atomic_load_acquire("a->used); + do { + if (max != 0 && used >= max) { + return (ISC_R_QUOTA); + } + if (soft != 0 && used >= soft) { + result = ISC_R_SOFTQUOTA; + } else { + result = ISC_R_SUCCESS; + } + } while (!atomic_compare_exchange_weak_acq_rel("a->used, &used, + used + 1)); + return (result); +} + +/* Must be quota->cbslock locked */ +static void +enqueue(isc_quota_t *quota, isc_quota_cb_t *cb) { + REQUIRE(cb != NULL); + ISC_LIST_ENQUEUE(quota->cbs, cb, link); + atomic_fetch_add_release("a->waiting, 1); +} + +/* Must be quota->cbslock locked */ +static isc_quota_cb_t * +dequeue(isc_quota_t *quota) { + isc_quota_cb_t *cb = ISC_LIST_HEAD(quota->cbs); + INSIST(cb != NULL); + ISC_LIST_DEQUEUE(quota->cbs, cb, link); + atomic_fetch_sub_relaxed("a->waiting, 1); + return (cb); +} + +static void +quota_release(isc_quota_t *quota) { + /* + * This is opportunistic - we might race with a failing quota_attach_cb + * and not detect that something is waiting, but eventually someone will + * be releasing quota and will detect it, so we don't need to worry - + * and we're saving a lot by not locking cblock every time. + */ + + if (atomic_load_acquire("a->waiting) > 0) { + isc_quota_cb_t *cb = NULL; + LOCK("a->cblock); + if (atomic_load_relaxed("a->waiting) > 0) { + cb = dequeue(quota); + } + UNLOCK("a->cblock); + if (cb != NULL) { + cb->cb_func(quota, cb->data); + return; + } + } + + INSIST(atomic_fetch_sub_release("a->used, 1) > 0); +} + +static isc_result_t +doattach(isc_quota_t *quota, isc_quota_t **p) { + isc_result_t result; + REQUIRE(p != NULL && *p == NULL); + + result = quota_reserve(quota); + if (result == ISC_R_SUCCESS || result == ISC_R_SOFTQUOTA) { + *p = quota; + } + + return (result); +} + +isc_result_t +isc_quota_attach(isc_quota_t *quota, isc_quota_t **quotap) { + REQUIRE(VALID_QUOTA(quota)); + REQUIRE(quotap != NULL && *quotap == NULL); + + return (isc_quota_attach_cb(quota, quotap, NULL)); +} + +isc_result_t +isc_quota_attach_cb(isc_quota_t *quota, isc_quota_t **quotap, + isc_quota_cb_t *cb) { + REQUIRE(VALID_QUOTA(quota)); + REQUIRE(cb == NULL || VALID_QUOTA_CB(cb)); + REQUIRE(quotap != NULL && *quotap == NULL); + + isc_result_t result = doattach(quota, quotap); + if (result == ISC_R_QUOTA && cb != NULL) { + LOCK("a->cblock); + enqueue(quota, cb); + UNLOCK("a->cblock); + } + return (result); +} + +void +isc_quota_cb_init(isc_quota_cb_t *cb, isc_quota_cb_func_t cb_func, void *data) { + ISC_LINK_INIT(cb, link); + cb->cb_func = cb_func; + cb->data = data; + cb->magic = QUOTA_CB_MAGIC; +} + +void +isc_quota_detach(isc_quota_t **quotap) { + REQUIRE(quotap != NULL && VALID_QUOTA(*quotap)); + isc_quota_t *quota = *quotap; + *quotap = NULL; + + quota_release(quota); +} diff --git a/lib/isc/radix.c b/lib/isc/radix.c new file mode 100644 index 0000000..d84e5d0 --- /dev/null +++ b/lib/isc/radix.c @@ -0,0 +1,706 @@ +/* + * 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. + */ + +/* + * This source was adapted from MRT's RCS Ids: + * Id: radix.c,v 1.10.2.1 1999/11/29 05:16:24 masaki Exp + * Id: prefix.c,v 1.37.2.9 2000/03/10 02:53:19 labovit Exp + */ + +#include <inttypes.h> + +#include <isc/mem.h> +#include <isc/radix.h> +#include <isc/types.h> +#include <isc/util.h> + +#define BIT_TEST(f, b) (((f) & (b)) != 0) + +static isc_result_t +_new_prefix(isc_mem_t *mctx, isc_prefix_t **target, int family, void *dest, + int bitlen); + +static void +_deref_prefix(isc_prefix_t *prefix); + +static isc_result_t +_ref_prefix(isc_mem_t *mctx, isc_prefix_t **target, isc_prefix_t *prefix); + +static int +_comp_with_mask(void *addr, void *dest, u_int mask); + +static void +_clear_radix(isc_radix_tree_t *radix, isc_radix_destroyfunc_t func); + +static isc_result_t +_new_prefix(isc_mem_t *mctx, isc_prefix_t **target, int family, void *dest, + int bitlen) { + isc_prefix_t *prefix; + + REQUIRE(target != NULL); + + if (family != AF_INET6 && family != AF_INET && family != AF_UNSPEC) { + return (ISC_R_NOTIMPLEMENTED); + } + + prefix = isc_mem_get(mctx, sizeof(isc_prefix_t)); + + if (family == AF_INET6) { + prefix->bitlen = (bitlen >= 0) ? bitlen : 128; + memmove(&prefix->add.sin6, dest, 16); + } else { + /* AF_UNSPEC is "any" or "none"--treat it as AF_INET */ + prefix->bitlen = (bitlen >= 0) ? bitlen : 32; + memmove(&prefix->add.sin, dest, 4); + } + + prefix->family = family; + prefix->mctx = NULL; + isc_mem_attach(mctx, &prefix->mctx); + + isc_refcount_init(&prefix->refcount, 1); + + *target = prefix; + return (ISC_R_SUCCESS); +} + +static void +_deref_prefix(isc_prefix_t *prefix) { + if (prefix != NULL) { + if (isc_refcount_decrement(&prefix->refcount) == 1) { + isc_refcount_destroy(&prefix->refcount); + isc_mem_putanddetach(&prefix->mctx, prefix, + sizeof(isc_prefix_t)); + } + } +} + +static isc_result_t +_ref_prefix(isc_mem_t *mctx, isc_prefix_t **target, isc_prefix_t *prefix) { + INSIST(prefix != NULL); + INSIST((prefix->family == AF_INET && prefix->bitlen <= 32) || + (prefix->family == AF_INET6 && prefix->bitlen <= 128) || + (prefix->family == AF_UNSPEC && prefix->bitlen == 0)); + REQUIRE(target != NULL && *target == NULL); + + /* + * If this prefix is a static allocation, copy it into new memory. + * (Note, the refcount still has to be destroyed by the calling + * routine.) + */ + if (isc_refcount_current(&prefix->refcount) == 0) { + isc_result_t ret; + ret = _new_prefix(mctx, target, prefix->family, &prefix->add, + prefix->bitlen); + return (ret); + } + + isc_refcount_increment(&prefix->refcount); + + *target = prefix; + return (ISC_R_SUCCESS); +} + +static int +_comp_with_mask(void *addr, void *dest, u_int mask) { + /* Mask length of zero matches everything */ + if (mask == 0) { + return (1); + } + + if (memcmp(addr, dest, mask / 8) == 0) { + u_int n = mask / 8; + u_int m = ((~0U) << (8 - (mask % 8))); + + if ((mask % 8) == 0 || + (((u_char *)addr)[n] & m) == (((u_char *)dest)[n] & m)) + { + return (1); + } + } + return (0); +} + +isc_result_t +isc_radix_create(isc_mem_t *mctx, isc_radix_tree_t **target, int maxbits) { + isc_radix_tree_t *radix; + + REQUIRE(target != NULL && *target == NULL); + + radix = isc_mem_get(mctx, sizeof(isc_radix_tree_t)); + + radix->mctx = NULL; + isc_mem_attach(mctx, &radix->mctx); + radix->maxbits = maxbits; + radix->head = NULL; + radix->num_active_node = 0; + radix->num_added_node = 0; + RUNTIME_CHECK(maxbits <= RADIX_MAXBITS); /* XXX */ + radix->magic = RADIX_TREE_MAGIC; + *target = radix; + return (ISC_R_SUCCESS); +} + +/* + * if func is supplied, it will be called as func(node->data) + * before deleting the node + */ + +static void +_clear_radix(isc_radix_tree_t *radix, isc_radix_destroyfunc_t func) { + REQUIRE(radix != NULL); + + if (radix->head != NULL) { + isc_radix_node_t *Xstack[RADIX_MAXBITS + 1]; + isc_radix_node_t **Xsp = Xstack; + isc_radix_node_t *Xrn = radix->head; + + while (Xrn != NULL) { + isc_radix_node_t *l = Xrn->l; + isc_radix_node_t *r = Xrn->r; + + if (Xrn->prefix != NULL) { + _deref_prefix(Xrn->prefix); + if (func != NULL) { + func(Xrn->data); + } + } else { + INSIST(Xrn->data[RADIX_V4] == NULL && + Xrn->data[RADIX_V6] == NULL); + } + + isc_mem_put(radix->mctx, Xrn, sizeof(*Xrn)); + radix->num_active_node--; + + if (l != NULL) { + if (r != NULL) { + *Xsp++ = r; + } + Xrn = l; + } else if (r != NULL) { + Xrn = r; + } else if (Xsp != Xstack) { + Xrn = *(--Xsp); + } else { + Xrn = NULL; + } + } + } + RUNTIME_CHECK(radix->num_active_node == 0); +} + +void +isc_radix_destroy(isc_radix_tree_t *radix, isc_radix_destroyfunc_t func) { + REQUIRE(radix != NULL); + _clear_radix(radix, func); + isc_mem_putanddetach(&radix->mctx, radix, sizeof(*radix)); +} + +/* + * func will be called as func(node->prefix, node->data) + */ +void +isc_radix_process(isc_radix_tree_t *radix, isc_radix_processfunc_t func) { + isc_radix_node_t *node; + + REQUIRE(func != NULL); + + RADIX_WALK(radix->head, node) { func(node->prefix, node->data); } + RADIX_WALK_END; +} + +isc_result_t +isc_radix_search(isc_radix_tree_t *radix, isc_radix_node_t **target, + isc_prefix_t *prefix) { + isc_radix_node_t *node; + isc_radix_node_t *stack[RADIX_MAXBITS + 1]; + u_char *addr; + uint32_t bitlen; + int tfam = -1, cnt = 0; + + REQUIRE(radix != NULL); + REQUIRE(prefix != NULL); + REQUIRE(target != NULL && *target == NULL); + RUNTIME_CHECK(prefix->bitlen <= radix->maxbits); + + *target = NULL; + + node = radix->head; + + if (node == NULL) { + return (ISC_R_NOTFOUND); + } + + addr = isc_prefix_touchar(prefix); + bitlen = prefix->bitlen; + + while (node != NULL && node->bit < bitlen) { + if (node->prefix) { + stack[cnt++] = node; + } + + if (BIT_TEST(addr[node->bit >> 3], 0x80 >> (node->bit & 0x07))) + { + node = node->r; + } else { + node = node->l; + } + } + + if (node != NULL && node->prefix) { + stack[cnt++] = node; + } + + while (cnt-- > 0) { + node = stack[cnt]; + + if (prefix->bitlen < node->bit) { + continue; + } + + if (_comp_with_mask(isc_prefix_tochar(node->prefix), + isc_prefix_tochar(prefix), + node->prefix->bitlen)) + { + int fam = ISC_RADIX_FAMILY(prefix); + if (node->node_num[fam] != -1 && + ((*target == NULL) || + (*target)->node_num[tfam] > node->node_num[fam])) + { + *target = node; + tfam = fam; + } + } + } + + if (*target == NULL) { + return (ISC_R_NOTFOUND); + } else { + return (ISC_R_SUCCESS); + } +} + +isc_result_t +isc_radix_insert(isc_radix_tree_t *radix, isc_radix_node_t **target, + isc_radix_node_t *source, isc_prefix_t *prefix) { + isc_radix_node_t *node, *new_node, *parent, *glue = NULL; + u_char *addr, *test_addr; + uint32_t bitlen, fam, check_bit, differ_bit; + uint32_t i, j, r; + isc_result_t result; + + REQUIRE(radix != NULL); + REQUIRE(target != NULL && *target == NULL); + REQUIRE(prefix != NULL || (source != NULL && source->prefix != NULL)); + RUNTIME_CHECK(prefix == NULL || prefix->bitlen <= radix->maxbits); + + if (prefix == NULL) { + prefix = source->prefix; + } + + INSIST(prefix != NULL); + + bitlen = prefix->bitlen; + fam = prefix->family; + + if (radix->head == NULL) { + node = isc_mem_get(radix->mctx, sizeof(isc_radix_node_t)); + node->bit = bitlen; + for (i = 0; i < RADIX_FAMILIES; i++) { + node->node_num[i] = -1; + } + node->prefix = NULL; + result = _ref_prefix(radix->mctx, &node->prefix, prefix); + if (result != ISC_R_SUCCESS) { + isc_mem_put(radix->mctx, node, + sizeof(isc_radix_node_t)); + return (result); + } + node->parent = NULL; + node->l = node->r = NULL; + if (source != NULL) { + /* + * If source is non-NULL, then we're merging in a + * node from an existing radix tree. To keep + * the node_num values consistent, the calling + * function will add the total number of nodes + * added to num_added_node at the end of + * the merge operation--we don't do it here. + */ + for (i = 0; i < RADIX_FAMILIES; i++) { + if (source->node_num[i] != -1) { + node->node_num[i] = + radix->num_added_node + + source->node_num[i]; + } + node->data[i] = source->data[i]; + } + } else { + int next = ++radix->num_added_node; + if (fam == AF_UNSPEC) { + /* "any" or "none" */ + for (i = 0; i < RADIX_FAMILIES; i++) { + node->node_num[i] = next; + } + } else { + node->node_num[ISC_RADIX_FAMILY(prefix)] = next; + } + + memset(node->data, 0, sizeof(node->data)); + } + radix->head = node; + radix->num_active_node++; + *target = node; + return (ISC_R_SUCCESS); + } + + addr = isc_prefix_touchar(prefix); + node = radix->head; + + while (node->bit < bitlen || node->prefix == NULL) { + if (node->bit < radix->maxbits && + BIT_TEST(addr[node->bit >> 3], 0x80 >> (node->bit & 0x07))) + { + if (node->r == NULL) { + break; + } + node = node->r; + } else { + if (node->l == NULL) { + break; + } + node = node->l; + } + + INSIST(node != NULL); + } + + INSIST(node->prefix != NULL); + + test_addr = isc_prefix_touchar(node->prefix); + /* Find the first bit different. */ + check_bit = (node->bit < bitlen) ? node->bit : bitlen; + differ_bit = 0; + for (i = 0; i * 8 < check_bit; i++) { + if ((r = (addr[i] ^ test_addr[i])) == 0) { + differ_bit = (i + 1) * 8; + continue; + } + /* I know the better way, but for now. */ + for (j = 0; j < 8; j++) { + if (BIT_TEST(r, (0x80 >> j))) { + break; + } + } + /* Must be found. */ + INSIST(j < 8); + differ_bit = i * 8 + j; + break; + } + + if (differ_bit > check_bit) { + differ_bit = check_bit; + } + + parent = node->parent; + while (parent != NULL && parent->bit >= differ_bit) { + node = parent; + parent = node->parent; + } + + if (differ_bit == bitlen && node->bit == bitlen) { + if (node->prefix != NULL) { + /* Set node_num only if it hasn't been set before */ + if (source != NULL) { + /* Merging nodes */ + for (i = 0; i < RADIX_FAMILIES; i++) { + if (node->node_num[i] == -1 && + source->node_num[i] != -1) + { + node->node_num[i] = + radix->num_added_node + + source->node_num[i]; + node->data[i] = source->data[i]; + } + } + } else { + if (fam == AF_UNSPEC) { + /* "any" or "none" */ + int next = radix->num_added_node + 1; + for (i = 0; i < RADIX_FAMILIES; i++) { + if (node->node_num[i] == -1) { + node->node_num[i] = + next; + radix->num_added_node = + next; + } + } + } else { + int foff = ISC_RADIX_FAMILY(prefix); + if (node->node_num[foff] == -1) { + node->node_num[foff] = + ++radix->num_added_node; + } + } + } + *target = node; + return (ISC_R_SUCCESS); + } else { + result = _ref_prefix(radix->mctx, &node->prefix, + prefix); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + INSIST(node->data[RADIX_V4] == NULL && + node->node_num[RADIX_V4] == -1 && + node->data[RADIX_V4] == NULL && + node->node_num[RADIX_V4] == -1); + if (source != NULL) { + /* Merging node */ + for (i = 0; i < RADIX_FAMILIES; i++) { + int cur = radix->num_added_node; + if (source->node_num[i] != -1) { + node->node_num[i] = + source->node_num[i] + cur; + node->data[i] = source->data[i]; + } + } + } else { + int next = ++radix->num_added_node; + if (fam == AF_UNSPEC) { + /* "any" or "none" */ + for (i = 0; i < RADIX_FAMILIES; i++) { + node->node_num[i] = next; + } + } else { + node->node_num[ISC_RADIX_FAMILY(prefix)] = next; + } + } + *target = node; + return (ISC_R_SUCCESS); + } + + new_node = isc_mem_get(radix->mctx, sizeof(isc_radix_node_t)); + if (node->bit != differ_bit && bitlen != differ_bit) { + glue = isc_mem_get(radix->mctx, sizeof(isc_radix_node_t)); + } + new_node->bit = bitlen; + new_node->prefix = NULL; + result = _ref_prefix(radix->mctx, &new_node->prefix, prefix); + if (result != ISC_R_SUCCESS) { + isc_mem_put(radix->mctx, new_node, sizeof(isc_radix_node_t)); + if (glue != NULL) { + isc_mem_put(radix->mctx, glue, + sizeof(isc_radix_node_t)); + } + return (result); + } + new_node->parent = NULL; + new_node->l = new_node->r = NULL; + for (i = 0; i < RADIX_FAMILIES; i++) { + new_node->node_num[i] = -1; + new_node->data[i] = NULL; + } + radix->num_active_node++; + + if (source != NULL) { + /* Merging node */ + for (i = 0; i < RADIX_FAMILIES; i++) { + int cur = radix->num_added_node; + if (source->node_num[i] != -1) { + new_node->node_num[i] = source->node_num[i] + + cur; + new_node->data[i] = source->data[i]; + } + } + } else { + int next = ++radix->num_added_node; + if (fam == AF_UNSPEC) { + /* "any" or "none" */ + for (i = 0; i < RADIX_FAMILIES; i++) { + new_node->node_num[i] = next; + } + } else { + new_node->node_num[ISC_RADIX_FAMILY(prefix)] = next; + } + memset(new_node->data, 0, sizeof(new_node->data)); + } + + if (node->bit == differ_bit) { + INSIST(glue == NULL); + new_node->parent = node; + if (node->bit < radix->maxbits && + BIT_TEST(addr[node->bit >> 3], 0x80 >> (node->bit & 0x07))) + { + INSIST(node->r == NULL); + node->r = new_node; + } else { + INSIST(node->l == NULL); + node->l = new_node; + } + *target = new_node; + return (ISC_R_SUCCESS); + } + + if (bitlen == differ_bit) { + INSIST(glue == NULL); + if (bitlen < radix->maxbits && + BIT_TEST(test_addr[bitlen >> 3], 0x80 >> (bitlen & 0x07))) + { + new_node->r = node; + } else { + new_node->l = node; + } + new_node->parent = node->parent; + if (node->parent == NULL) { + INSIST(radix->head == node); + radix->head = new_node; + } else if (node->parent->r == node) { + node->parent->r = new_node; + } else { + node->parent->l = new_node; + } + node->parent = new_node; + } else { + INSIST(glue != NULL); + glue->bit = differ_bit; + glue->prefix = NULL; + glue->parent = node->parent; + for (i = 0; i < RADIX_FAMILIES; i++) { + glue->data[i] = NULL; + glue->node_num[i] = -1; + } + radix->num_active_node++; + if (differ_bit < radix->maxbits && + BIT_TEST(addr[differ_bit >> 3], 0x80 >> (differ_bit & 07))) + { + glue->r = new_node; + glue->l = node; + } else { + glue->r = node; + glue->l = new_node; + } + new_node->parent = glue; + + if (node->parent == NULL) { + INSIST(radix->head == node); + radix->head = glue; + } else if (node->parent->r == node) { + node->parent->r = glue; + } else { + node->parent->l = glue; + } + node->parent = glue; + } + + *target = new_node; + return (ISC_R_SUCCESS); +} + +void +isc_radix_remove(isc_radix_tree_t *radix, isc_radix_node_t *node) { + isc_radix_node_t *parent, *child; + + REQUIRE(radix != NULL); + REQUIRE(node != NULL); + + if (node->r && node->l) { + /* + * This might be a placeholder node -- have to check and + * make sure there is a prefix associated with it! + */ + if (node->prefix != NULL) { + _deref_prefix(node->prefix); + } + + node->prefix = NULL; + memset(node->data, 0, sizeof(node->data)); + return; + } + + if (node->r == NULL && node->l == NULL) { + parent = node->parent; + _deref_prefix(node->prefix); + + if (parent == NULL) { + INSIST(radix->head == node); + radix->head = NULL; + isc_mem_put(radix->mctx, node, sizeof(*node)); + radix->num_active_node--; + return; + } + + if (parent->r == node) { + parent->r = NULL; + child = parent->l; + } else { + INSIST(parent->l == node); + parent->l = NULL; + child = parent->r; + } + + isc_mem_put(radix->mctx, node, sizeof(*node)); + radix->num_active_node--; + + if (parent->prefix) { + return; + } + + /* We need to remove parent too. */ + if (parent->parent == NULL) { + INSIST(radix->head == parent); + radix->head = child; + } else if (parent->parent->r == parent) { + parent->parent->r = child; + } else { + INSIST(parent->parent->l == parent); + parent->parent->l = child; + } + + child->parent = parent->parent; + isc_mem_put(radix->mctx, parent, sizeof(*parent)); + radix->num_active_node--; + return; + } + + if (node->r) { + child = node->r; + } else { + INSIST(node->l != NULL); + child = node->l; + } + + parent = node->parent; + child->parent = parent; + + _deref_prefix(node->prefix); + + if (parent == NULL) { + INSIST(radix->head == node); + radix->head = child; + isc_mem_put(radix->mctx, node, sizeof(*node)); + radix->num_active_node--; + return; + } + + isc_mem_put(radix->mctx, node, sizeof(*node)); + radix->num_active_node--; + + if (parent->r == node) { + parent->r = child; + } else { + INSIST(parent->l == node); + parent->l = child; + } +} diff --git a/lib/isc/random.c b/lib/isc/random.c new file mode 100644 index 0000000..b11c39f --- /dev/null +++ b/lib/isc/random.c @@ -0,0 +1,161 @@ +/* + * 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. + */ + +/* + * Portions of isc_random_uniform(): + * + * Copyright (c) 1996, David Mazieres <dm@uun.org> + * Copyright (c) 2008, Damien Miller <djm@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <inttypes.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <isc/once.h> +#include <isc/platform.h> +#include <isc/random.h> +#include <isc/result.h> +#include <isc/thread.h> +#include <isc/types.h> +#include <isc/util.h> + +#include "entropy_private.h" + +/* + * The specific implementation for PRNG is included as a C file + * that has to provide a static variable named seed, and a function + * uint32_t next(void) that provides next random number. + * + * The implementation must be thread-safe. + */ + +/* + * Two contestants have been considered: the xoroshiro family of the + * functions by Villa&Blackman, and PCG by O'Neill. After + * consideration, the xoshiro128starstar function has been chosen as + * the uint32_t random number provider because it is very fast and has + * good enough properties for our usage pattern. + */ +#include "xoshiro128starstar.c" + +ISC_THREAD_LOCAL isc_once_t isc_random_once = ISC_ONCE_INIT; + +static void +isc_random_initialize(void) { + int useed[4] = { 0, 0, 0, 1 }; +#if FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION + /* + * Set a constant seed to help in problem reproduction should fuzzing + * find a crash or a hang. The seed array must be non-zero else + * xoshiro128starstar will generate an infinite series of zeroes. + */ +#else /* if FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION */ + isc_entropy_get(useed, sizeof(useed)); +#endif /* if FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION */ + memmove(seed, useed, sizeof(seed)); +} + +uint8_t +isc_random8(void) { + RUNTIME_CHECK(isc_once_do(&isc_random_once, isc_random_initialize) == + ISC_R_SUCCESS); + return (next() & 0xff); +} + +uint16_t +isc_random16(void) { + RUNTIME_CHECK(isc_once_do(&isc_random_once, isc_random_initialize) == + ISC_R_SUCCESS); + return (next() & 0xffff); +} + +uint32_t +isc_random32(void) { + RUNTIME_CHECK(isc_once_do(&isc_random_once, isc_random_initialize) == + ISC_R_SUCCESS); + return (next()); +} + +void +isc_random_buf(void *buf, size_t buflen) { + int i; + uint32_t r; + + REQUIRE(buf != NULL); + REQUIRE(buflen > 0); + + RUNTIME_CHECK(isc_once_do(&isc_random_once, isc_random_initialize) == + ISC_R_SUCCESS); + + for (i = 0; i + sizeof(r) <= buflen; i += sizeof(r)) { + r = next(); + memmove((uint8_t *)buf + i, &r, sizeof(r)); + } + r = next(); + memmove((uint8_t *)buf + i, &r, buflen % sizeof(r)); + return; +} + +uint32_t +isc_random_uniform(uint32_t upper_bound) { + /* Copy of arc4random_uniform from OpenBSD */ + uint32_t r, min; + + RUNTIME_CHECK(isc_once_do(&isc_random_once, isc_random_initialize) == + ISC_R_SUCCESS); + + if (upper_bound < 2) { + return (0); + } + +#if (ULONG_MAX > 0xffffffffUL) + min = 0x100000000UL % upper_bound; +#else /* if (ULONG_MAX > 0xffffffffUL) */ + /* Calculate (2**32 % upper_bound) avoiding 64-bit math */ + if (upper_bound > 0x80000000) { + min = 1 + ~upper_bound; /* 2**32 - upper_bound */ + } else { + /* (2**32 - (x * 2)) % x == 2**32 % x when x <= 2**31 */ + min = ((0xffffffff - (upper_bound * 2)) + 1) % upper_bound; + } +#endif /* if (ULONG_MAX > 0xffffffffUL) */ + + /* + * This could theoretically loop forever but each retry has + * p > 0.5 (worst case, usually far better) of selecting a + * number inside the range we need, so it should rarely need + * to re-roll. + */ + for (;;) { + r = next(); + if (r >= min) { + break; + } + } + + return (r % upper_bound); +} diff --git a/lib/isc/ratelimiter.c b/lib/isc/ratelimiter.c new file mode 100644 index 0000000..f502bbd --- /dev/null +++ b/lib/isc/ratelimiter.c @@ -0,0 +1,372 @@ +/* + * 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. + */ + +/*! \file */ + +#include <inttypes.h> +#include <stdbool.h> + +#include <isc/mem.h> +#include <isc/ratelimiter.h> +#include <isc/refcount.h> +#include <isc/task.h> +#include <isc/time.h> +#include <isc/timer.h> +#include <isc/util.h> + +typedef enum { + isc_ratelimiter_stalled = 0, + isc_ratelimiter_ratelimited = 1, + isc_ratelimiter_idle = 2, + isc_ratelimiter_shuttingdown = 3 +} isc_ratelimiter_state_t; + +struct isc_ratelimiter { + isc_mem_t *mctx; + isc_mutex_t lock; + isc_refcount_t references; + isc_task_t *task; + isc_timer_t *timer; + isc_interval_t interval; + uint32_t pertic; + bool pushpop; + isc_ratelimiter_state_t state; + isc_event_t shutdownevent; + ISC_LIST(isc_event_t) pending; +}; + +#define ISC_RATELIMITEREVENT_SHUTDOWN (ISC_EVENTCLASS_RATELIMITER + 1) + +static void +ratelimiter_tick(isc_task_t *task, isc_event_t *event); + +static void +ratelimiter_shutdowncomplete(isc_task_t *task, isc_event_t *event); + +isc_result_t +isc_ratelimiter_create(isc_mem_t *mctx, isc_timermgr_t *timermgr, + isc_task_t *task, isc_ratelimiter_t **ratelimiterp) { + isc_result_t result; + isc_ratelimiter_t *rl; + INSIST(ratelimiterp != NULL && *ratelimiterp == NULL); + + rl = isc_mem_get(mctx, sizeof(*rl)); + *rl = (isc_ratelimiter_t){ + .mctx = mctx, + .task = task, + .pertic = 1, + .state = isc_ratelimiter_idle, + }; + + isc_refcount_init(&rl->references, 1); + isc_interval_set(&rl->interval, 0, 0); + ISC_LIST_INIT(rl->pending); + + isc_mutex_init(&rl->lock); + + result = isc_timer_create(timermgr, isc_timertype_inactive, NULL, NULL, + rl->task, ratelimiter_tick, rl, &rl->timer); + if (result != ISC_R_SUCCESS) { + goto free_mutex; + } + + /* + * Increment the reference count to indicate that we may + * (soon) have events outstanding. + */ + isc_refcount_increment(&rl->references); + + ISC_EVENT_INIT(&rl->shutdownevent, sizeof(isc_event_t), 0, NULL, + ISC_RATELIMITEREVENT_SHUTDOWN, + ratelimiter_shutdowncomplete, rl, rl, NULL, NULL); + + *ratelimiterp = rl; + return (ISC_R_SUCCESS); + +free_mutex: + isc_refcount_decrementz(&rl->references); + isc_refcount_destroy(&rl->references); + isc_mutex_destroy(&rl->lock); + isc_mem_put(mctx, rl, sizeof(*rl)); + return (result); +} + +isc_result_t +isc_ratelimiter_setinterval(isc_ratelimiter_t *rl, isc_interval_t *interval) { + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(rl != NULL); + REQUIRE(interval != NULL); + + LOCK(&rl->lock); + rl->interval = *interval; + /* + * If the timer is currently running, change its rate. + */ + if (rl->state == isc_ratelimiter_ratelimited) { + result = isc_timer_reset(rl->timer, isc_timertype_ticker, NULL, + &rl->interval, false); + } + UNLOCK(&rl->lock); + return (result); +} + +void +isc_ratelimiter_setpertic(isc_ratelimiter_t *rl, uint32_t pertic) { + REQUIRE(rl != NULL); + + if (pertic == 0) { + pertic = 1; + } + rl->pertic = pertic; +} + +void +isc_ratelimiter_setpushpop(isc_ratelimiter_t *rl, bool pushpop) { + REQUIRE(rl != NULL); + + rl->pushpop = pushpop; +} + +isc_result_t +isc_ratelimiter_enqueue(isc_ratelimiter_t *rl, isc_task_t *task, + isc_event_t **eventp) { + isc_result_t result = ISC_R_SUCCESS; + isc_event_t *ev; + + REQUIRE(rl != NULL); + REQUIRE(task != NULL); + REQUIRE(eventp != NULL && *eventp != NULL); + ev = *eventp; + REQUIRE(ev->ev_sender == NULL); + + LOCK(&rl->lock); + if (rl->state == isc_ratelimiter_ratelimited || + rl->state == isc_ratelimiter_stalled) + { + ev->ev_sender = task; + *eventp = NULL; + if (rl->pushpop) { + ISC_LIST_PREPEND(rl->pending, ev, ev_ratelink); + } else { + ISC_LIST_APPEND(rl->pending, ev, ev_ratelink); + } + } else if (rl->state == isc_ratelimiter_idle) { + result = isc_timer_reset(rl->timer, isc_timertype_ticker, NULL, + &rl->interval, false); + if (result == ISC_R_SUCCESS) { + ev->ev_sender = task; + rl->state = isc_ratelimiter_ratelimited; + } + } else { + INSIST(rl->state == isc_ratelimiter_shuttingdown); + result = ISC_R_SHUTTINGDOWN; + } + UNLOCK(&rl->lock); + if (*eventp != NULL && result == ISC_R_SUCCESS) { + isc_task_send(task, eventp); + } + return (result); +} + +isc_result_t +isc_ratelimiter_dequeue(isc_ratelimiter_t *rl, isc_event_t *event) { + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(rl != NULL); + REQUIRE(event != NULL); + + LOCK(&rl->lock); + if (ISC_LINK_LINKED(event, ev_ratelink)) { + ISC_LIST_UNLINK(rl->pending, event, ev_ratelink); + event->ev_sender = NULL; + } else { + result = ISC_R_NOTFOUND; + } + UNLOCK(&rl->lock); + return (result); +} + +static void +ratelimiter_tick(isc_task_t *task, isc_event_t *event) { + isc_ratelimiter_t *rl = (isc_ratelimiter_t *)event->ev_arg; + isc_event_t *p; + uint32_t pertic; + + UNUSED(task); + + isc_event_free(&event); + + pertic = rl->pertic; + while (pertic != 0) { + pertic--; + LOCK(&rl->lock); + p = ISC_LIST_HEAD(rl->pending); + if (p != NULL) { + /* + * There is work to do. Let's do it after unlocking. + */ + ISC_LIST_UNLINK(rl->pending, p, ev_ratelink); + } else { + /* + * No work left to do. Stop the timer so that we don't + * waste resources by having it fire periodically. + */ + isc_result_t result = isc_timer_reset( + rl->timer, isc_timertype_inactive, NULL, NULL, + false); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + rl->state = isc_ratelimiter_idle; + pertic = 0; /* Force the loop to exit. */ + } + UNLOCK(&rl->lock); + if (p != NULL) { + isc_task_t *evtask = p->ev_sender; + isc_task_send(evtask, &p); + } + INSIST(p == NULL); + } +} + +void +isc_ratelimiter_shutdown(isc_ratelimiter_t *rl) { + isc_event_t *ev; + isc_task_t *task; + isc_result_t result; + + REQUIRE(rl != NULL); + + LOCK(&rl->lock); + rl->state = isc_ratelimiter_shuttingdown; + (void)isc_timer_reset(rl->timer, isc_timertype_inactive, NULL, NULL, + false); + while ((ev = ISC_LIST_HEAD(rl->pending)) != NULL) { + task = ev->ev_sender; + ISC_LIST_UNLINK(rl->pending, ev, ev_ratelink); + ev->ev_attributes |= ISC_EVENTATTR_CANCELED; + isc_task_send(task, &ev); + } + task = NULL; + isc_task_attach(rl->task, &task); + + result = isc_timer_reset(rl->timer, isc_timertype_inactive, NULL, NULL, + false); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + isc_timer_destroy(&rl->timer); + + /* + * Send an event to our task. The delivery of this event + * indicates that no more timer events will be delivered. + */ + ev = &rl->shutdownevent; + isc_task_send(rl->task, &ev); + + UNLOCK(&rl->lock); +} + +static void +ratelimiter_shutdowncomplete(isc_task_t *task, isc_event_t *event) { + isc_ratelimiter_t *rl = (isc_ratelimiter_t *)event->ev_arg; + + UNUSED(task); + + isc_ratelimiter_detach(&rl); + isc_task_detach(&task); +} + +static void +ratelimiter_free(isc_ratelimiter_t *rl) { + isc_refcount_destroy(&rl->references); + isc_mutex_destroy(&rl->lock); + isc_mem_put(rl->mctx, rl, sizeof(*rl)); +} + +void +isc_ratelimiter_attach(isc_ratelimiter_t *source, isc_ratelimiter_t **target) { + REQUIRE(source != NULL); + REQUIRE(target != NULL && *target == NULL); + + isc_refcount_increment(&source->references); + + *target = source; +} + +void +isc_ratelimiter_detach(isc_ratelimiter_t **rlp) { + isc_ratelimiter_t *rl; + + REQUIRE(rlp != NULL && *rlp != NULL); + + rl = *rlp; + *rlp = NULL; + + if (isc_refcount_decrement(&rl->references) == 1) { + ratelimiter_free(rl); + } +} + +isc_result_t +isc_ratelimiter_stall(isc_ratelimiter_t *rl) { + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(rl != NULL); + + LOCK(&rl->lock); + switch (rl->state) { + case isc_ratelimiter_shuttingdown: + result = ISC_R_SHUTTINGDOWN; + break; + case isc_ratelimiter_ratelimited: + result = isc_timer_reset(rl->timer, isc_timertype_inactive, + NULL, NULL, false); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + FALLTHROUGH; + case isc_ratelimiter_idle: + case isc_ratelimiter_stalled: + rl->state = isc_ratelimiter_stalled; + break; + } + UNLOCK(&rl->lock); + return (result); +} + +isc_result_t +isc_ratelimiter_release(isc_ratelimiter_t *rl) { + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(rl != NULL); + + LOCK(&rl->lock); + switch (rl->state) { + case isc_ratelimiter_shuttingdown: + result = ISC_R_SHUTTINGDOWN; + break; + case isc_ratelimiter_stalled: + if (!ISC_LIST_EMPTY(rl->pending)) { + result = isc_timer_reset(rl->timer, + isc_timertype_ticker, NULL, + &rl->interval, false); + if (result == ISC_R_SUCCESS) { + rl->state = isc_ratelimiter_ratelimited; + } + } else { + rl->state = isc_ratelimiter_idle; + } + break; + case isc_ratelimiter_ratelimited: + case isc_ratelimiter_idle: + break; + } + UNLOCK(&rl->lock); + return (result); +} diff --git a/lib/isc/regex.c b/lib/isc/regex.c new file mode 100644 index 0000000..f7a3f5e --- /dev/null +++ b/lib/isc/regex.c @@ -0,0 +1,436 @@ +/* + * 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. + */ + +#include <stdbool.h> + +#include <isc/file.h> +#include <isc/print.h> +#include <isc/regex.h> +#include <isc/string.h> + +#if VALREGEX_REPORT_REASON +#define FAIL(x) \ + do { \ + reason = (x); \ + goto error; \ + } while (0) +#else /* if VALREGEX_REPORT_REASON */ +#define FAIL(x) goto error +#endif /* if VALREGEX_REPORT_REASON */ + +/* + * Validate the regular expression 'C' locale. + */ +int +isc_regex_validate(const char *c) { + enum { + none, + parse_bracket, + parse_bound, + parse_ce, + parse_ec, + parse_cc + } state = none; + /* Well known character classes. */ + const char *cc[] = { ":alnum:", ":digit:", ":punct:", ":alpha:", + ":graph:", ":space:", ":blank:", ":lower:", + ":upper:", ":cntrl:", ":print:", ":xdigit:" }; + bool seen_comma = false; + bool seen_high = false; + bool seen_char = false; + bool seen_ec = false; + bool seen_ce = false; + bool have_atom = false; + int group = 0; + int range = 0; + int sub = 0; + bool empty_ok = false; + bool neg = false; + bool was_multiple = false; + unsigned int low = 0; + unsigned int high = 0; + const char *ccname = NULL; + int range_start = 0; +#if VALREGEX_REPORT_REASON + const char *reason = ""; +#endif /* if VALREGEX_REPORT_REASON */ + + if (c == NULL || *c == 0) { + FAIL("empty string"); + } + + while (c != NULL && *c != 0) { + switch (state) { + case none: + switch (*c) { + case '\\': /* make literal */ + ++c; + switch (*c) { + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if ((*c - '0') > sub) { + FAIL("bad back reference"); + } + have_atom = true; + was_multiple = false; + break; + case 0: + FAIL("escaped end-of-string"); + default: + goto literal; + } + ++c; + break; + case '[': /* bracket start */ + ++c; + neg = false; + was_multiple = false; + seen_char = false; + state = parse_bracket; + break; + case '{': /* bound start */ + switch (c[1]) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (!have_atom) { + FAIL("no atom"); + } + if (was_multiple) { + FAIL("was multiple"); + } + seen_comma = false; + seen_high = false; + low = high = 0; + state = parse_bound; + break; + default: + goto literal; + } + ++c; + have_atom = true; + was_multiple = true; + break; + case '}': + goto literal; + case '(': /* group start */ + have_atom = false; + was_multiple = false; + empty_ok = true; + ++group; + ++sub; + ++c; + break; + case ')': /* group end */ + if (group && !have_atom && !empty_ok) { + FAIL("empty alternative"); + } + have_atom = true; + was_multiple = false; + if (group != 0) { + --group; + } + ++c; + break; + case '|': /* alternative separator */ + if (!have_atom) { + FAIL("no atom"); + } + have_atom = false; + empty_ok = false; + was_multiple = false; + ++c; + break; + case '^': + case '$': + have_atom = true; + was_multiple = true; + ++c; + break; + case '+': + case '*': + case '?': + if (was_multiple) { + FAIL("was multiple"); + } + if (!have_atom) { + FAIL("no atom"); + } + have_atom = true; + was_multiple = true; + ++c; + break; + case '.': + default: + literal: + have_atom = true; + was_multiple = false; + ++c; + break; + } + break; + case parse_bound: + switch (*c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (!seen_comma) { + low = low * 10 + *c - '0'; + if (low > 255) { + FAIL("lower bound too big"); + } + } else { + seen_high = true; + high = high * 10 + *c - '0'; + if (high > 255) { + FAIL("upper bound too big"); + } + } + ++c; + break; + case ',': + if (seen_comma) { + FAIL("multiple commas"); + } + seen_comma = true; + ++c; + break; + default: + case '{': + FAIL("non digit/comma"); + case '}': + if (seen_high && low > high) { + FAIL("bad parse bound"); + } + seen_comma = false; + state = none; + ++c; + break; + } + break; + case parse_bracket: + switch (*c) { + case '^': + if (seen_char || neg) { + goto inside; + } + neg = true; + ++c; + break; + case '-': + if (range == 2) { + goto inside; + } + if (!seen_char) { + goto inside; + } + if (range == 1) { + FAIL("bad range"); + } + range = 2; + ++c; + break; + case '[': + ++c; + switch (*c) { + case '.': /* collating element */ + if (range != 0) { + --range; + } + ++c; + state = parse_ce; + seen_ce = false; + break; + case '=': /* equivalence class */ + if (range == 2) { + FAIL("equivalence class in " + "range"); + } + ++c; + state = parse_ec; + seen_ec = false; + break; + case ':': /* character class */ + if (range == 2) { + FAIL("character class in " + "range"); + } + ccname = c; + ++c; + state = parse_cc; + break; + } + seen_char = true; + break; + case ']': + if (!c[1] && !seen_char) { + FAIL("unfinished brace"); + } + if (!seen_char) { + goto inside; + } + ++c; + range = 0; + have_atom = true; + state = none; + break; + default: + inside: + seen_char = true; + if (range == 2 && (*c & 0xff) < range_start) { + FAIL("out of order range"); + } + if (range != 0) { + --range; + } + range_start = *c & 0xff; + ++c; + break; + } + break; + case parse_ce: + switch (*c) { + case '.': + ++c; + switch (*c) { + case ']': + if (!seen_ce) { + FAIL("empty ce"); + } + ++c; + state = parse_bracket; + break; + default: + if (seen_ce) { + range_start = 256; + } else { + range_start = '.'; + } + seen_ce = true; + break; + } + break; + default: + if (seen_ce) { + range_start = 256; + } else { + range_start = *c; + } + seen_ce = true; + ++c; + break; + } + break; + case parse_ec: + switch (*c) { + case '=': + ++c; + switch (*c) { + case ']': + if (!seen_ec) { + FAIL("no ec"); + } + ++c; + state = parse_bracket; + break; + default: + seen_ec = true; + break; + } + break; + default: + seen_ec = true; + ++c; + break; + } + break; + case parse_cc: + switch (*c) { + case ':': + ++c; + switch (*c) { + case ']': { + unsigned int i; + bool found = false; + for (i = 0; + i < sizeof(cc) / sizeof(*cc); i++) + { + unsigned int len; + len = strlen(cc[i]); + if (len != + (unsigned int)(c - ccname)) + { + continue; + } + if (strncmp(cc[i], ccname, len)) + { + continue; + } + found = true; + } + if (!found) { + FAIL("unknown cc"); + } + ++c; + state = parse_bracket; + break; + } + default: + break; + } + break; + default: + ++c; + break; + } + break; + } + } + if (group != 0) { + FAIL("group open"); + } + if (state != none) { + FAIL("incomplete"); + } + if (!have_atom) { + FAIL("no atom"); + } + return (sub); + +error: +#if VALREGEX_REPORT_REASON + fprintf(stderr, "%s\n", reason); +#endif /* if VALREGEX_REPORT_REASON */ + return (-1); +} diff --git a/lib/isc/region.c b/lib/isc/region.c new file mode 100644 index 0000000..675f4ec --- /dev/null +++ b/lib/isc/region.c @@ -0,0 +1,39 @@ +/* + * 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. + */ + +/*! \file */ + +#include <stdlib.h> +#include <string.h> + +#include <isc/region.h> +#include <isc/util.h> + +int +isc_region_compare(isc_region_t *r1, isc_region_t *r2) { + unsigned int l; + int result; + + REQUIRE(r1 != NULL); + REQUIRE(r2 != NULL); + + l = (r1->length < r2->length) ? r1->length : r2->length; + + if ((result = memcmp(r1->base, r2->base, l)) != 0) { + return ((result < 0) ? -1 : 1); + } else { + return ((r1->length == r2->length) ? 0 + : (r1->length < r2->length) ? -1 + : 1); + } +} diff --git a/lib/isc/result.c b/lib/isc/result.c new file mode 100644 index 0000000..d083000 --- /dev/null +++ b/lib/isc/result.c @@ -0,0 +1,306 @@ +/* + * 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. + */ + +/*! \file */ + +#include <stddef.h> +#include <stdlib.h> + +#include <isc/lib.h> +#include <isc/once.h> +#include <isc/resultclass.h> +#include <isc/rwlock.h> +#include <isc/util.h> + +typedef struct resulttable { + unsigned int base; + unsigned int last; + const char **text; + int set; + ISC_LINK(struct resulttable) link; +} resulttable; + +typedef ISC_LIST(resulttable) resulttable_list_t; + +static const char *description[ISC_R_NRESULTS] = { + "success", /*%< 0 */ + "out of memory", /*%< 1 */ + "timed out", /*%< 2 */ + "no available threads", /*%< 3 */ + "address not available", /*%< 4 */ + "address in use", /*%< 5 */ + "permission denied", /*%< 6 */ + "no pending connections", /*%< 7 */ + "network unreachable", /*%< 8 */ + "host unreachable", /*%< 9 */ + "network down", /*%< 10 */ + "host down", /*%< 11 */ + "connection refused", /*%< 12 */ + "not enough free resources", /*%< 13 */ + "end of file", /*%< 14 */ + "socket already bound", /*%< 15 */ + "reload", /*%< 16 */ + "lock busy", /*%< 17 */ + "already exists", /*%< 18 */ + "ran out of space", /*%< 19 */ + "operation canceled", /*%< 20 */ + "socket is not bound", /*%< 21 */ + "shutting down", /*%< 22 */ + "not found", /*%< 23 */ + "unexpected end of input", /*%< 24 */ + "failure", /*%< 25 */ + "I/O error", /*%< 26 */ + "not implemented", /*%< 27 */ + "unbalanced parentheses", /*%< 28 */ + "no more", /*%< 29 */ + "invalid file", /*%< 30 */ + "bad base64 encoding", /*%< 31 */ + "unexpected token", /*%< 32 */ + "quota reached", /*%< 33 */ + "unexpected error", /*%< 34 */ + "already running", /*%< 35 */ + "ignore", /*%< 36 */ + "address mask not contiguous", /*%< 37 */ + "file not found", /*%< 38 */ + "file already exists", /*%< 39 */ + "socket is not connected", /*%< 40 */ + "out of range", /*%< 41 */ + "out of entropy", /*%< 42 */ + "invalid use of multicast address", /*%< 43 */ + "not a file", /*%< 44 */ + "not a directory", /*%< 45 */ + "queue is empty", /*%< 46 */ + "address family mismatch", /*%< 47 */ + "address family not supported", /*%< 48 */ + "bad hex encoding", /*%< 49 */ + "too many open files", /*%< 50 */ + "not blocking", /*%< 51 */ + "unbalanced quotes", /*%< 52 */ + "operation in progress", /*%< 53 */ + "connection reset", /*%< 54 */ + "soft quota reached", /*%< 55 */ + "not a valid number", /*%< 56 */ + "disabled", /*%< 57 */ + "max size", /*%< 58 */ + "invalid address format", /*%< 59 */ + "bad base32 encoding", /*%< 60 */ + "unset", /*%< 61 */ + "multiple", /*%< 62 */ + "would block", /*%< 63 */ + "complete", /*%< 64 */ + "crypto failure", /*%< 65 */ + "disc quota", /*%< 66 */ + "disc full", /*%< 67 */ + "default", /*%< 68 */ + "IPv4 prefix", /*%< 69 */ + "TLS error", /*%< 70 */ + "ALPN for HTTP/2 failed" /*%< 71 */ +}; + +static const char *identifier[ISC_R_NRESULTS] = { "ISC_R_SUCCESS", + "ISC_R_NOMEMORY", + "ISC_R_TIMEDOUT", + "ISC_R_NOTHREADS", + "ISC_R_ADDRNOTAVAIL", + "ISC_R_ADDRINUSE", + "ISC_R_NOPERM", + "ISC_R_NOCONN", + "ISC_R_NETUNREACH", + "ISC_R_HOSTUNREACH", + "ISC_R_NETDOWN", + "ISC_R_HOSTDOWN", + "ISC_R_CONNREFUSED", + "ISC_R_NORESOURCES", + "ISC_R_EOF", + "ISC_R_BOUND", + "ISC_R_RELOAD", + "ISC_R_LOCKBUSY", + "ISC_R_EXISTS", + "ISC_R_NOSPACE", + "ISC_R_CANCELED", + "ISC_R_NOTBOUND", + "ISC_R_SHUTTINGDOWN", + "ISC_R_NOTFOUND", + "ISC_R_UNEXPECTEDEND", + "ISC_R_FAILURE", + "ISC_R_IOERROR", + "ISC_R_NOTIMPLEMENTED", + "ISC_R_UNBALANCED", + "ISC_R_NOMORE", + "ISC_R_INVALIDFILE", + "ISC_R_BADBASE64", + "ISC_R_UNEXPECTEDTOKEN", + "ISC_R_QUOTA", + "ISC_R_UNEXPECTED", + "ISC_R_ALREADYRUNNING", + "ISC_R_IGNORE", + "ISC_R_MASKNONCONTIG", + "ISC_R_FILENOTFOUND", + "ISC_R_FILEEXISTS", + "ISC_R_NOTCONNECTED", + "ISC_R_RANGE", + "ISC_R_NOENTROPY", + "ISC_R_MULTICAST", + "ISC_R_NOTFILE", + "ISC_R_NOTDIRECTORY", + "ISC_R_EMPTY", + "ISC_R_FAMILYMISMATCH", + "ISC_R_FAMILYNOSUPPORT", + "ISC_R_BADHEX", + "ISC_R_TOOMANYOPENFILES", + "ISC_R_NOTBLOCKING", + "ISC_R_UNBALANCEDQUOTES", + "ISC_R_INPROGRESS", + "ISC_R_CONNECTIONRESET", + "ISC_R_SOFTQUOTA", + "ISC_R_BADNUMBER", + "ISC_R_DISABLED", + "ISC_R_MAXSIZE", + "ISC_R_BADADDRESSFORM", + "ISC_R_BADBASE32", + "ISC_R_UNSET", + "ISC_R_MULTIPLE", + "ISC_R_WOULDBLOCK", + "ISC_R_COMPLETE", + "ISC_R_CRYPTOFAILURE", + "ISC_R_DISCQUOTA", + "ISC_R_DISCFULL", + "ISC_R_DEFAULT", + "ISC_R_IPV4PREFIX", + "ISC_R_TLSERROR", + "ISC_R_HTTP2ALPNERROR" }; + +#define ISC_RESULT_RESULTSET 2 +#define ISC_RESULT_UNAVAILABLESET 3 + +static isc_once_t once = ISC_ONCE_INIT; +static resulttable_list_t description_tables; +static resulttable_list_t identifier_tables; +static isc_rwlock_t lock; + +static isc_result_t +register_table(resulttable_list_t *tables, unsigned int base, + unsigned int nresults, const char **text, int set) { + resulttable *table; + + REQUIRE(base % ISC_RESULTCLASS_SIZE == 0); + REQUIRE(nresults <= ISC_RESULTCLASS_SIZE); + REQUIRE(text != NULL); + + /* + * We use malloc() here because we we want to be able to use + * isc_result_totext() even if there is no memory context. + */ + table = malloc(sizeof(*table)); + if (table == NULL) { + return (ISC_R_NOMEMORY); + } + table->base = base; + table->last = base + nresults - 1; + table->text = text; + table->set = set; + ISC_LINK_INIT(table, link); + + RWLOCK(&lock, isc_rwlocktype_write); + + ISC_LIST_APPEND(*tables, table, link); + + RWUNLOCK(&lock, isc_rwlocktype_write); + + return (ISC_R_SUCCESS); +} + +static void +initialize_action(void) { + isc_result_t result; + + isc_rwlock_init(&lock, 0, 0); + ISC_LIST_INIT(description_tables); + ISC_LIST_INIT(identifier_tables); + + result = register_table(&description_tables, ISC_RESULTCLASS_ISC, + ISC_R_NRESULTS, description, + ISC_RESULT_RESULTSET); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "register_table() failed: %u", result); + } + + result = register_table(&identifier_tables, ISC_RESULTCLASS_ISC, + ISC_R_NRESULTS, identifier, + ISC_RESULT_RESULTSET); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "register_table() failed: %u", result); + } +} + +static void +initialize(void) { + RUNTIME_CHECK(isc_once_do(&once, initialize_action) == ISC_R_SUCCESS); +} + +static const char * +isc_result_tomany_helper(resulttable_list_t *tables, isc_result_t result) { + resulttable *table; + const char *text; + int index; + + initialize(); + + RWLOCK(&lock, isc_rwlocktype_read); + + text = NULL; + for (table = ISC_LIST_HEAD(*tables); table != NULL; + table = ISC_LIST_NEXT(table, link)) + { + if (result >= table->base && result <= table->last) { + index = (int)(result - table->base); + text = table->text[index]; + break; + } + } + if (text == NULL) { + text = "(result code text not available)"; + } + + RWUNLOCK(&lock, isc_rwlocktype_read); + + return (text); +} + +const char * +isc_result_totext(isc_result_t result) { + return (isc_result_tomany_helper(&description_tables, result)); +} + +const char * +isc_result_toid(isc_result_t result) { + return (isc_result_tomany_helper(&identifier_tables, result)); +} + +isc_result_t +isc_result_register(unsigned int base, unsigned int nresults, const char **text, + int set) { + initialize(); + + return (register_table(&description_tables, base, nresults, text, set)); +} + +isc_result_t +isc_result_registerids(unsigned int base, unsigned int nresults, + const char **ids, int set) { + initialize(); + + return (register_table(&identifier_tables, base, nresults, ids, set)); +} diff --git a/lib/isc/rwlock.c b/lib/isc/rwlock.c new file mode 100644 index 0000000..c69b6d7 --- /dev/null +++ b/lib/isc/rwlock.c @@ -0,0 +1,646 @@ +/* + * 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. + */ + +/*! \file */ + +#include <inttypes.h> +#include <stdbool.h> +#include <stddef.h> + +#if defined(sun) && (defined(__sparc) || defined(__sparc__)) +#include <synch.h> /* for smt_pause(3c) */ +#endif /* if defined(sun) && (defined(__sparc) || defined(__sparc__)) */ + +#include <isc/atomic.h> +#include <isc/magic.h> +#include <isc/platform.h> +#include <isc/print.h> +#include <isc/rwlock.h> +#include <isc/util.h> + +#if USE_PTHREAD_RWLOCK + +#include <errno.h> +#include <pthread.h> + +void +isc_rwlock_init(isc_rwlock_t *rwl, unsigned int read_quota, + unsigned int write_quota) { + UNUSED(read_quota); + UNUSED(write_quota); + REQUIRE(pthread_rwlock_init(&rwl->rwlock, NULL) == 0); + atomic_init(&rwl->downgrade, false); +} + +isc_result_t +isc_rwlock_lock(isc_rwlock_t *rwl, isc_rwlocktype_t type) { + switch (type) { + case isc_rwlocktype_read: + REQUIRE(pthread_rwlock_rdlock(&rwl->rwlock) == 0); + break; + case isc_rwlocktype_write: + while (true) { + REQUIRE(pthread_rwlock_wrlock(&rwl->rwlock) == 0); + /* Unlock if in middle of downgrade operation */ + if (atomic_load_acquire(&rwl->downgrade)) { + REQUIRE(pthread_rwlock_unlock(&rwl->rwlock) == + 0); + while (atomic_load_acquire(&rwl->downgrade)) { + } + continue; + } + break; + } + break; + default: + UNREACHABLE(); + } + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_rwlock_trylock(isc_rwlock_t *rwl, isc_rwlocktype_t type) { + int ret = 0; + switch (type) { + case isc_rwlocktype_read: + ret = pthread_rwlock_tryrdlock(&rwl->rwlock); + break; + case isc_rwlocktype_write: + ret = pthread_rwlock_trywrlock(&rwl->rwlock); + if ((ret == 0) && atomic_load_acquire(&rwl->downgrade)) { + isc_rwlock_unlock(rwl, type); + return (ISC_R_LOCKBUSY); + } + break; + default: + UNREACHABLE(); + } + + switch (ret) { + case 0: + return (ISC_R_SUCCESS); + case EBUSY: + return (ISC_R_LOCKBUSY); + case EAGAIN: + return (ISC_R_LOCKBUSY); + default: + UNREACHABLE(); + } +} + +isc_result_t +isc_rwlock_unlock(isc_rwlock_t *rwl, isc_rwlocktype_t type) { + UNUSED(type); + REQUIRE(pthread_rwlock_unlock(&rwl->rwlock) == 0); + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_rwlock_tryupgrade(isc_rwlock_t *rwl) { + UNUSED(rwl); + return (ISC_R_LOCKBUSY); +} + +void +isc_rwlock_downgrade(isc_rwlock_t *rwl) { + isc_result_t result; + atomic_store_release(&rwl->downgrade, true); + result = isc_rwlock_unlock(rwl, isc_rwlocktype_write); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + result = isc_rwlock_lock(rwl, isc_rwlocktype_read); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + atomic_store_release(&rwl->downgrade, false); +} + +void +isc_rwlock_destroy(isc_rwlock_t *rwl) { + pthread_rwlock_destroy(&rwl->rwlock); +} + +#else /* if USE_PTHREAD_RWLOCK */ + +#define RWLOCK_MAGIC ISC_MAGIC('R', 'W', 'L', 'k') +#define VALID_RWLOCK(rwl) ISC_MAGIC_VALID(rwl, RWLOCK_MAGIC) + +#ifndef RWLOCK_DEFAULT_READ_QUOTA +#define RWLOCK_DEFAULT_READ_QUOTA 4 +#endif /* ifndef RWLOCK_DEFAULT_READ_QUOTA */ + +#ifndef RWLOCK_DEFAULT_WRITE_QUOTA +#define RWLOCK_DEFAULT_WRITE_QUOTA 4 +#endif /* ifndef RWLOCK_DEFAULT_WRITE_QUOTA */ + +#ifndef RWLOCK_MAX_ADAPTIVE_COUNT +#define RWLOCK_MAX_ADAPTIVE_COUNT 100 +#endif /* ifndef RWLOCK_MAX_ADAPTIVE_COUNT */ + +#if defined(_MSC_VER) +#include <intrin.h> +#define isc_rwlock_pause() YieldProcessor() +#elif defined(__x86_64__) +#include <immintrin.h> +#define isc_rwlock_pause() _mm_pause() +#elif defined(__i386__) +#define isc_rwlock_pause() __asm__ __volatile__("rep; nop") +#elif defined(__ia64__) +#define isc_rwlock_pause() __asm__ __volatile__("hint @pause") +#elif defined(__arm__) && HAVE_ARM_YIELD +#define isc_rwlock_pause() __asm__ __volatile__("yield") +#elif defined(sun) && (defined(__sparc) || defined(__sparc__)) +#define isc_rwlock_pause() smt_pause() +#elif (defined(__sparc) || defined(__sparc__)) && HAVE_SPARC_PAUSE +#define isc_rwlock_pause() __asm__ __volatile__("pause") +#elif defined(__ppc__) || defined(_ARCH_PPC) || defined(_ARCH_PWR) || \ + defined(_ARCH_PWR2) || defined(_POWER) +#define isc_rwlock_pause() __asm__ volatile("or 27,27,27") +#else /* if defined(_MSC_VER) */ +#define isc_rwlock_pause() +#endif /* if defined(_MSC_VER) */ + +static isc_result_t +isc__rwlock_lock(isc_rwlock_t *rwl, isc_rwlocktype_t type); + +#ifdef ISC_RWLOCK_TRACE +#include <stdio.h> /* Required for fprintf/stderr. */ + +#include <isc/thread.h> /* Required for isc_thread_self(). */ + +static void +print_lock(const char *operation, isc_rwlock_t *rwl, isc_rwlocktype_t type) { + fprintf(stderr, + "rwlock %p thread %" PRIuPTR " %s(%s): " + "write_requests=%u, write_completions=%u, " + "cnt_and_flag=0x%x, readers_waiting=%u, " + "write_granted=%u, write_quota=%u\n", + rwl, isc_thread_self(), operation, + (type == isc_rwlocktype_read ? "read" : "write"), + atomic_load_acquire(&rwl->write_requests), + atomic_load_acquire(&rwl->write_completions), + atomic_load_acquire(&rwl->cnt_and_flag), rwl->readers_waiting, + atomic_load_acquire(&rwl->write_granted), rwl->write_quota); +} +#endif /* ISC_RWLOCK_TRACE */ + +void +isc_rwlock_init(isc_rwlock_t *rwl, unsigned int read_quota, + unsigned int write_quota) { + REQUIRE(rwl != NULL); + + /* + * In case there's trouble initializing, we zero magic now. If all + * goes well, we'll set it to RWLOCK_MAGIC. + */ + rwl->magic = 0; + + atomic_init(&rwl->spins, 0); + atomic_init(&rwl->write_requests, 0); + atomic_init(&rwl->write_completions, 0); + atomic_init(&rwl->cnt_and_flag, 0); + rwl->readers_waiting = 0; + atomic_init(&rwl->write_granted, 0); + if (read_quota != 0) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "read quota is not supported"); + } + if (write_quota == 0) { + write_quota = RWLOCK_DEFAULT_WRITE_QUOTA; + } + rwl->write_quota = write_quota; + + isc_mutex_init(&rwl->lock); + + isc_condition_init(&rwl->readable); + isc_condition_init(&rwl->writeable); + + rwl->magic = RWLOCK_MAGIC; +} + +void +isc_rwlock_destroy(isc_rwlock_t *rwl) { + REQUIRE(VALID_RWLOCK(rwl)); + + REQUIRE(atomic_load_acquire(&rwl->write_requests) == + atomic_load_acquire(&rwl->write_completions) && + atomic_load_acquire(&rwl->cnt_and_flag) == 0 && + rwl->readers_waiting == 0); + + rwl->magic = 0; + (void)isc_condition_destroy(&rwl->readable); + (void)isc_condition_destroy(&rwl->writeable); + isc_mutex_destroy(&rwl->lock); +} + +/* + * When some architecture-dependent atomic operations are available, + * rwlock can be more efficient than the generic algorithm defined below. + * The basic algorithm is described in the following URL: + * http://www.cs.rochester.edu/u/scott/synchronization/pseudocode/rw.html + * + * The key is to use the following integer variables modified atomically: + * write_requests, write_completions, and cnt_and_flag. + * + * write_requests and write_completions act as a waiting queue for writers + * in order to ensure the FIFO order. Both variables begin with the initial + * value of 0. When a new writer tries to get a write lock, it increments + * write_requests and gets the previous value of the variable as a "ticket". + * When write_completions reaches the ticket number, the new writer can start + * writing. When the writer completes its work, it increments + * write_completions so that another new writer can start working. If the + * write_requests is not equal to write_completions, it means a writer is now + * working or waiting. In this case, a new readers cannot start reading, or + * in other words, this algorithm basically prefers writers. + * + * cnt_and_flag is a "lock" shared by all readers and writers. This integer + * variable is a kind of structure with two members: writer_flag (1 bit) and + * reader_count (31 bits). The writer_flag shows whether a writer is working, + * and the reader_count shows the number of readers currently working or almost + * ready for working. A writer who has the current "ticket" tries to get the + * lock by exclusively setting the writer_flag to 1, provided that the whole + * 32-bit is 0 (meaning no readers or writers working). On the other hand, + * a new reader tries to increment the "reader_count" field provided that + * the writer_flag is 0 (meaning there is no writer working). + * + * If some of the above operations fail, the reader or the writer sleeps + * until the related condition changes. When a working reader or writer + * completes its work, some readers or writers are sleeping, and the condition + * that suspended the reader or writer has changed, it wakes up the sleeping + * readers or writers. + * + * As already noted, this algorithm basically prefers writers. In order to + * prevent readers from starving, however, the algorithm also introduces the + * "writer quota" (Q). When Q consecutive writers have completed their work, + * suspending readers, the last writer will wake up the readers, even if a new + * writer is waiting. + * + * Implementation specific note: due to the combination of atomic operations + * and a mutex lock, ordering between the atomic operation and locks can be + * very sensitive in some cases. In particular, it is generally very important + * to check the atomic variable that requires a reader or writer to sleep after + * locking the mutex and before actually sleeping; otherwise, it could be very + * likely to cause a deadlock. For example, assume "var" is a variable + * atomically modified, then the corresponding code would be: + * if (var == need_sleep) { + * LOCK(lock); + * if (var == need_sleep) + * WAIT(cond, lock); + * UNLOCK(lock); + * } + * The second check is important, since "var" is protected by the atomic + * operation, not by the mutex, and can be changed just before sleeping. + * (The first "if" could be omitted, but this is also important in order to + * make the code efficient by avoiding the use of the mutex unless it is + * really necessary.) + */ + +#define WRITER_ACTIVE 0x1 +#define READER_INCR 0x2 + +static isc_result_t +isc__rwlock_lock(isc_rwlock_t *rwl, isc_rwlocktype_t type) { + int32_t cntflag; + + REQUIRE(VALID_RWLOCK(rwl)); + +#ifdef ISC_RWLOCK_TRACE + print_lock("prelock", rwl, type); +#endif /* ifdef ISC_RWLOCK_TRACE */ + + if (type == isc_rwlocktype_read) { + if (atomic_load_acquire(&rwl->write_requests) != + atomic_load_acquire(&rwl->write_completions)) + { + /* there is a waiting or active writer */ + LOCK(&rwl->lock); + if (atomic_load_acquire(&rwl->write_requests) != + atomic_load_acquire(&rwl->write_completions)) + { + rwl->readers_waiting++; + WAIT(&rwl->readable, &rwl->lock); + rwl->readers_waiting--; + } + UNLOCK(&rwl->lock); + } + + cntflag = atomic_fetch_add_release(&rwl->cnt_and_flag, + READER_INCR); + POST(cntflag); + while (1) { + if ((atomic_load_acquire(&rwl->cnt_and_flag) & + WRITER_ACTIVE) == 0) + { + break; + } + + /* A writer is still working */ + LOCK(&rwl->lock); + rwl->readers_waiting++; + if ((atomic_load_acquire(&rwl->cnt_and_flag) & + WRITER_ACTIVE) != 0) + { + WAIT(&rwl->readable, &rwl->lock); + } + rwl->readers_waiting--; + UNLOCK(&rwl->lock); + + /* + * Typically, the reader should be able to get a lock + * at this stage: + * (1) there should have been no pending writer when + * the reader was trying to increment the + * counter; otherwise, the writer should be in + * the waiting queue, preventing the reader from + * proceeding to this point. + * (2) once the reader increments the counter, no + * more writer can get a lock. + * Still, it is possible another writer can work at + * this point, e.g. in the following scenario: + * A previous writer unlocks the writer lock. + * This reader proceeds to point (1). + * A new writer appears, and gets a new lock before + * the reader increments the counter. + * The reader then increments the counter. + * The previous writer notices there is a waiting + * reader who is almost ready, and wakes it up. + * So, the reader needs to confirm whether it can now + * read explicitly (thus we loop). Note that this is + * not an infinite process, since the reader has + * incremented the counter at this point. + */ + } + + /* + * If we are temporarily preferred to writers due to the writer + * quota, reset the condition (race among readers doesn't + * matter). + */ + atomic_store_release(&rwl->write_granted, 0); + } else { + int32_t prev_writer; + + /* enter the waiting queue, and wait for our turn */ + prev_writer = atomic_fetch_add_release(&rwl->write_requests, 1); + while (atomic_load_acquire(&rwl->write_completions) != + prev_writer) + { + LOCK(&rwl->lock); + if (atomic_load_acquire(&rwl->write_completions) != + prev_writer) + { + WAIT(&rwl->writeable, &rwl->lock); + UNLOCK(&rwl->lock); + continue; + } + UNLOCK(&rwl->lock); + break; + } + + while (!atomic_compare_exchange_weak_acq_rel( + &rwl->cnt_and_flag, &(int_fast32_t){ 0 }, + WRITER_ACTIVE)) + { + /* Another active reader or writer is working. */ + LOCK(&rwl->lock); + if (atomic_load_acquire(&rwl->cnt_and_flag) != 0) { + WAIT(&rwl->writeable, &rwl->lock); + } + UNLOCK(&rwl->lock); + } + + INSIST((atomic_load_acquire(&rwl->cnt_and_flag) & + WRITER_ACTIVE)); + atomic_fetch_add_release(&rwl->write_granted, 1); + } + +#ifdef ISC_RWLOCK_TRACE + print_lock("postlock", rwl, type); +#endif /* ifdef ISC_RWLOCK_TRACE */ + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_rwlock_lock(isc_rwlock_t *rwl, isc_rwlocktype_t type) { + int32_t cnt = 0; + int32_t spins = atomic_load_acquire(&rwl->spins) * 2 + 10; + int32_t max_cnt = ISC_MAX(spins, RWLOCK_MAX_ADAPTIVE_COUNT); + isc_result_t result = ISC_R_SUCCESS; + + do { + if (cnt++ >= max_cnt) { + result = isc__rwlock_lock(rwl, type); + break; + } + isc_rwlock_pause(); + } while (isc_rwlock_trylock(rwl, type) != ISC_R_SUCCESS); + + atomic_fetch_add_release(&rwl->spins, (cnt - spins) / 8); + + return (result); +} + +isc_result_t +isc_rwlock_trylock(isc_rwlock_t *rwl, isc_rwlocktype_t type) { + int32_t cntflag; + + REQUIRE(VALID_RWLOCK(rwl)); + +#ifdef ISC_RWLOCK_TRACE + print_lock("prelock", rwl, type); +#endif /* ifdef ISC_RWLOCK_TRACE */ + + if (type == isc_rwlocktype_read) { + /* If a writer is waiting or working, we fail. */ + if (atomic_load_acquire(&rwl->write_requests) != + atomic_load_acquire(&rwl->write_completions)) + { + return (ISC_R_LOCKBUSY); + } + + /* Otherwise, be ready for reading. */ + cntflag = atomic_fetch_add_release(&rwl->cnt_and_flag, + READER_INCR); + if ((cntflag & WRITER_ACTIVE) != 0) { + /* + * A writer is working. We lose, and cancel the read + * request. + */ + cntflag = atomic_fetch_sub_release(&rwl->cnt_and_flag, + READER_INCR); + /* + * If no other readers are waiting and we've suspended + * new writers in this short period, wake them up. + */ + if (cntflag == READER_INCR && + atomic_load_acquire(&rwl->write_completions) != + atomic_load_acquire(&rwl->write_requests)) + { + LOCK(&rwl->lock); + BROADCAST(&rwl->writeable); + UNLOCK(&rwl->lock); + } + + return (ISC_R_LOCKBUSY); + } + } else { + /* Try locking without entering the waiting queue. */ + int_fast32_t zero = 0; + if (!atomic_compare_exchange_strong_acq_rel( + &rwl->cnt_and_flag, &zero, WRITER_ACTIVE)) + { + return (ISC_R_LOCKBUSY); + } + + /* + * XXXJT: jump into the queue, possibly breaking the writer + * order. + */ + atomic_fetch_sub_release(&rwl->write_completions, 1); + atomic_fetch_add_release(&rwl->write_granted, 1); + } + +#ifdef ISC_RWLOCK_TRACE + print_lock("postlock", rwl, type); +#endif /* ifdef ISC_RWLOCK_TRACE */ + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_rwlock_tryupgrade(isc_rwlock_t *rwl) { + REQUIRE(VALID_RWLOCK(rwl)); + + int_fast32_t reader_incr = READER_INCR; + + /* Try to acquire write access. */ + atomic_compare_exchange_strong_acq_rel(&rwl->cnt_and_flag, &reader_incr, + WRITER_ACTIVE); + /* + * There must have been no writer, and there must have + * been at least one reader. + */ + INSIST((reader_incr & WRITER_ACTIVE) == 0 && + (reader_incr & ~WRITER_ACTIVE) != 0); + + if (reader_incr == READER_INCR) { + /* + * We are the only reader and have been upgraded. + * Now jump into the head of the writer waiting queue. + */ + atomic_fetch_sub_release(&rwl->write_completions, 1); + } else { + return (ISC_R_LOCKBUSY); + } + + return (ISC_R_SUCCESS); +} + +void +isc_rwlock_downgrade(isc_rwlock_t *rwl) { + int32_t prev_readers; + + REQUIRE(VALID_RWLOCK(rwl)); + + /* Become an active reader. */ + prev_readers = atomic_fetch_add_release(&rwl->cnt_and_flag, + READER_INCR); + /* We must have been a writer. */ + INSIST((prev_readers & WRITER_ACTIVE) != 0); + + /* Complete write */ + atomic_fetch_sub_release(&rwl->cnt_and_flag, WRITER_ACTIVE); + atomic_fetch_add_release(&rwl->write_completions, 1); + + /* Resume other readers */ + LOCK(&rwl->lock); + if (rwl->readers_waiting > 0) { + BROADCAST(&rwl->readable); + } + UNLOCK(&rwl->lock); +} + +isc_result_t +isc_rwlock_unlock(isc_rwlock_t *rwl, isc_rwlocktype_t type) { + int32_t prev_cnt; + + REQUIRE(VALID_RWLOCK(rwl)); + +#ifdef ISC_RWLOCK_TRACE + print_lock("preunlock", rwl, type); +#endif /* ifdef ISC_RWLOCK_TRACE */ + + if (type == isc_rwlocktype_read) { + prev_cnt = atomic_fetch_sub_release(&rwl->cnt_and_flag, + READER_INCR); + /* + * If we're the last reader and any writers are waiting, wake + * them up. We need to wake up all of them to ensure the + * FIFO order. + */ + if (prev_cnt == READER_INCR && + atomic_load_acquire(&rwl->write_completions) != + atomic_load_acquire(&rwl->write_requests)) + { + LOCK(&rwl->lock); + BROADCAST(&rwl->writeable); + UNLOCK(&rwl->lock); + } + } else { + bool wakeup_writers = true; + + /* + * Reset the flag, and (implicitly) tell other writers + * we are done. + */ + atomic_fetch_sub_release(&rwl->cnt_and_flag, WRITER_ACTIVE); + atomic_fetch_add_release(&rwl->write_completions, 1); + + if ((atomic_load_acquire(&rwl->write_granted) >= + rwl->write_quota) || + (atomic_load_acquire(&rwl->write_requests) == + atomic_load_acquire(&rwl->write_completions)) || + (atomic_load_acquire(&rwl->cnt_and_flag) & ~WRITER_ACTIVE)) + { + /* + * We have passed the write quota, no writer is + * waiting, or some readers are almost ready, pending + * possible writers. Note that the last case can + * happen even if write_requests != write_completions + * (which means a new writer in the queue), so we need + * to catch the case explicitly. + */ + LOCK(&rwl->lock); + if (rwl->readers_waiting > 0) { + wakeup_writers = false; + BROADCAST(&rwl->readable); + } + UNLOCK(&rwl->lock); + } + + if ((atomic_load_acquire(&rwl->write_requests) != + atomic_load_acquire(&rwl->write_completions)) && + wakeup_writers) + { + LOCK(&rwl->lock); + BROADCAST(&rwl->writeable); + UNLOCK(&rwl->lock); + } + } + +#ifdef ISC_RWLOCK_TRACE + print_lock("postunlock", rwl, type); +#endif /* ifdef ISC_RWLOCK_TRACE */ + + return (ISC_R_SUCCESS); +} + +#endif /* USE_PTHREAD_RWLOCK */ diff --git a/lib/isc/safe.c b/lib/isc/safe.c new file mode 100644 index 0000000..988034d --- /dev/null +++ b/lib/isc/safe.c @@ -0,0 +1,26 @@ +/* + * 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. + */ + +#include <openssl/crypto.h> + +#include <isc/safe.h> + +int +isc_safe_memequal(const void *s1, const void *s2, size_t len) { + return (!CRYPTO_memcmp(s1, s2, len)); +} + +void +isc_safe_memwipe(void *ptr, size_t len) { + OPENSSL_cleanse(ptr, len); +} diff --git a/lib/isc/serial.c b/lib/isc/serial.c new file mode 100644 index 0000000..5ede64b --- /dev/null +++ b/lib/isc/serial.c @@ -0,0 +1,55 @@ +/* + * 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. + */ + +/*! \file */ + +#include <inttypes.h> +#include <stdbool.h> + +#include <isc/serial.h> + +bool +isc_serial_lt(uint32_t a, uint32_t b) { + /* + * Undefined => false + */ + if (a == (b ^ 0x80000000U)) { + return (false); + } + return (((int32_t)(a - b) < 0) ? true : false); +} + +bool +isc_serial_gt(uint32_t a, uint32_t b) { + return (((int32_t)(a - b) > 0) ? true : false); +} + +bool +isc_serial_le(uint32_t a, uint32_t b) { + return ((a == b) ? true : isc_serial_lt(a, b)); +} + +bool +isc_serial_ge(uint32_t a, uint32_t b) { + return ((a == b) ? true : isc_serial_gt(a, b)); +} + +bool +isc_serial_eq(uint32_t a, uint32_t b) { + return ((a == b) ? true : false); +} + +bool +isc_serial_ne(uint32_t a, uint32_t b) { + return ((a != b) ? true : false); +} diff --git a/lib/isc/siphash.c b/lib/isc/siphash.c new file mode 100644 index 0000000..a6e60cf --- /dev/null +++ b/lib/isc/siphash.c @@ -0,0 +1,235 @@ +/* + * 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. + */ + +#include <inttypes.h> +#include <string.h> +#include <unistd.h> + +#include <isc/endian.h> +#include <isc/siphash.h> +#include <isc/util.h> + +/* + * The implementation is based on SipHash reference C implementation by + * + * Copyright (c) 2012-2016 Jean-Philippe Aumasson + * <jeanphilippe.aumasson@gmail.com> Copyright (c) 2012-2014 Daniel J. Bernstein + * <djb@cr.yp.to> + * + * To the extent possible under law, the author(s) have dedicated all copyright + * and related and neighboring rights to this software to the public domain + * worldwide. This software is distributed without any warranty. You should + * have received a copy of the CC0 Public Domain Dedication along with this + * software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>. + */ + +#define cROUNDS 2 +#define dROUNDS 4 + +#define ROTATE64(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b)))) + +#define HALF_ROUND64(a, b, c, d, s, t) \ + a += b; \ + c += d; \ + b = ROTATE64(b, s) ^ a; \ + d = ROTATE64(d, t) ^ c; \ + a = ROTATE64(a, 32); + +#define FULL_ROUND64(v0, v1, v2, v3) \ + HALF_ROUND64(v0, v1, v2, v3, 13, 16); \ + HALF_ROUND64(v2, v1, v0, v3, 17, 21); + +#define SIPROUND FULL_ROUND64 + +#define ROTATE32(x, b) (uint32_t)(((x) << (b)) | ((x) >> (32 - (b)))) + +#define HALF_ROUND32(a, b, c, d, s, t) \ + a += b; \ + c += d; \ + b = ROTATE32(b, s) ^ a; \ + d = ROTATE32(d, t) ^ c; \ + a = ROTATE32(a, 16); + +#define FULL_ROUND32(v0, v1, v2, v3) \ + HALF_ROUND32(v0, v1, v2, v3, 5, 8); \ + HALF_ROUND32(v2, v1, v0, v3, 13, 7); + +#define HALFSIPROUND FULL_ROUND32 + +#define U32TO8_LE(p, v) \ + (p)[0] = (uint8_t)((v)); \ + (p)[1] = (uint8_t)((v) >> 8); \ + (p)[2] = (uint8_t)((v) >> 16); \ + (p)[3] = (uint8_t)((v) >> 24); + +#define U8TO32_LE(p) \ + (((uint32_t)((p)[0])) | ((uint32_t)((p)[1]) << 8) | \ + ((uint32_t)((p)[2]) << 16) | ((uint32_t)((p)[3]) << 24)) + +#define U64TO8_LE(p, v) \ + U32TO8_LE((p), (uint32_t)((v))); \ + U32TO8_LE((p) + 4, (uint32_t)((v) >> 32)); + +#define U8TO64_LE(p) \ + (((uint64_t)((p)[0])) | ((uint64_t)((p)[1]) << 8) | \ + ((uint64_t)((p)[2]) << 16) | ((uint64_t)((p)[3]) << 24) | \ + ((uint64_t)((p)[4]) << 32) | ((uint64_t)((p)[5]) << 40) | \ + ((uint64_t)((p)[6]) << 48) | ((uint64_t)((p)[7]) << 56)) + +void +isc_siphash24(const uint8_t *k, const uint8_t *in, const size_t inlen, + uint8_t *out) { + REQUIRE(k != NULL); + REQUIRE(out != NULL); + REQUIRE(inlen == 0 || in != NULL); + + uint64_t k0 = U8TO64_LE(k); + uint64_t k1 = U8TO64_LE(k + 8); + + uint64_t v0 = UINT64_C(0x736f6d6570736575) ^ k0; + uint64_t v1 = UINT64_C(0x646f72616e646f6d) ^ k1; + uint64_t v2 = UINT64_C(0x6c7967656e657261) ^ k0; + uint64_t v3 = UINT64_C(0x7465646279746573) ^ k1; + + uint64_t b = ((uint64_t)inlen) << 56; + + const uint8_t *end = (in == NULL) + ? NULL + : in + inlen - (inlen % sizeof(uint64_t)); + const size_t left = inlen & 7; + + for (; in != end; in += 8) { + uint64_t m = U8TO64_LE(in); + + v3 ^= m; + + for (size_t i = 0; i < cROUNDS; ++i) { + SIPROUND(v0, v1, v2, v3); + } + + v0 ^= m; + } + + switch (left) { + case 7: + b |= ((uint64_t)in[6]) << 48; + FALLTHROUGH; + case 6: + b |= ((uint64_t)in[5]) << 40; + FALLTHROUGH; + case 5: + b |= ((uint64_t)in[4]) << 32; + FALLTHROUGH; + case 4: + b |= ((uint64_t)in[3]) << 24; + FALLTHROUGH; + case 3: + b |= ((uint64_t)in[2]) << 16; + FALLTHROUGH; + case 2: + b |= ((uint64_t)in[1]) << 8; + FALLTHROUGH; + case 1: + b |= ((uint64_t)in[0]); + FALLTHROUGH; + case 0: + break; + default: + UNREACHABLE(); + } + + v3 ^= b; + + for (size_t i = 0; i < cROUNDS; ++i) { + SIPROUND(v0, v1, v2, v3); + } + + v0 ^= b; + + v2 ^= 0xff; + + for (size_t i = 0; i < dROUNDS; ++i) { + SIPROUND(v0, v1, v2, v3); + } + + b = v0 ^ v1 ^ v2 ^ v3; + + U64TO8_LE(out, b); +} + +void +isc_halfsiphash24(const uint8_t *k, const uint8_t *in, const size_t inlen, + uint8_t *out) { + REQUIRE(k != NULL); + REQUIRE(out != NULL); + REQUIRE(inlen == 0 || in != NULL); + + uint32_t k0 = U8TO32_LE(k); + uint32_t k1 = U8TO32_LE(k + 4); + + uint32_t v0 = UINT32_C(0x00000000) ^ k0; + uint32_t v1 = UINT32_C(0x00000000) ^ k1; + uint32_t v2 = UINT32_C(0x6c796765) ^ k0; + uint32_t v3 = UINT32_C(0x74656462) ^ k1; + + uint32_t b = ((uint32_t)inlen) << 24; + + const uint8_t *end = (in == NULL) + ? NULL + : in + inlen - (inlen % sizeof(uint32_t)); + const int left = inlen & 3; + + for (; in != end; in += 4) { + uint32_t m = U8TO32_LE(in); + v3 ^= m; + + for (size_t i = 0; i < cROUNDS; ++i) { + HALFSIPROUND(v0, v1, v2, v3); + } + + v0 ^= m; + } + + switch (left) { + case 3: + b |= ((uint32_t)in[2]) << 16; + FALLTHROUGH; + case 2: + b |= ((uint32_t)in[1]) << 8; + FALLTHROUGH; + case 1: + b |= ((uint32_t)in[0]); + FALLTHROUGH; + case 0: + break; + default: + UNREACHABLE(); + } + + v3 ^= b; + + for (size_t i = 0; i < cROUNDS; ++i) { + HALFSIPROUND(v0, v1, v2, v3); + } + + v0 ^= b; + + v2 ^= 0xff; + + for (size_t i = 0; i < dROUNDS; ++i) { + HALFSIPROUND(v0, v1, v2, v3); + } + + b = v1 ^ v3; + U32TO8_LE(out, b); +} diff --git a/lib/isc/sockaddr.c b/lib/isc/sockaddr.c new file mode 100644 index 0000000..16afbd5 --- /dev/null +++ b/lib/isc/sockaddr.c @@ -0,0 +1,513 @@ +/* + * 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. + */ + +/*! \file */ + +#include <stdbool.h> +#include <stdio.h> +#if defined(WIN32) || defined(WIN64) +#include <malloc.h> +#endif /* if defined(WIN32) || defined(WIN64) */ + +#include <isc/buffer.h> +#include <isc/hash.h> +#include <isc/netaddr.h> +#include <isc/print.h> +#include <isc/region.h> +#include <isc/sockaddr.h> +#include <isc/string.h> +#include <isc/util.h> + +bool +isc_sockaddr_equal(const isc_sockaddr_t *a, const isc_sockaddr_t *b) { + return (isc_sockaddr_compare(a, b, + ISC_SOCKADDR_CMPADDR | + ISC_SOCKADDR_CMPPORT | + ISC_SOCKADDR_CMPSCOPE)); +} + +bool +isc_sockaddr_eqaddr(const isc_sockaddr_t *a, const isc_sockaddr_t *b) { + return (isc_sockaddr_compare( + a, b, ISC_SOCKADDR_CMPADDR | ISC_SOCKADDR_CMPSCOPE)); +} + +bool +isc_sockaddr_compare(const isc_sockaddr_t *a, const isc_sockaddr_t *b, + unsigned int flags) { + REQUIRE(a != NULL && b != NULL); + + if (a->length != b->length) { + return (false); + } + + /* + * We don't just memcmp because the sin_zero field isn't always + * zero. + */ + + if (a->type.sa.sa_family != b->type.sa.sa_family) { + return (false); + } + switch (a->type.sa.sa_family) { + case AF_INET: + if ((flags & ISC_SOCKADDR_CMPADDR) != 0 && + memcmp(&a->type.sin.sin_addr, &b->type.sin.sin_addr, + sizeof(a->type.sin.sin_addr)) != 0) + { + return (false); + } + if ((flags & ISC_SOCKADDR_CMPPORT) != 0 && + a->type.sin.sin_port != b->type.sin.sin_port) + { + return (false); + } + break; + case AF_INET6: + if ((flags & ISC_SOCKADDR_CMPADDR) != 0 && + memcmp(&a->type.sin6.sin6_addr, &b->type.sin6.sin6_addr, + sizeof(a->type.sin6.sin6_addr)) != 0) + { + return (false); + } + /* + * If ISC_SOCKADDR_CMPSCOPEZERO is set then don't return + * false if one of the scopes in zero. + */ + if ((flags & ISC_SOCKADDR_CMPSCOPE) != 0 && + a->type.sin6.sin6_scope_id != b->type.sin6.sin6_scope_id && + ((flags & ISC_SOCKADDR_CMPSCOPEZERO) == 0 || + (a->type.sin6.sin6_scope_id != 0 && + b->type.sin6.sin6_scope_id != 0))) + { + return (false); + } + if ((flags & ISC_SOCKADDR_CMPPORT) != 0 && + a->type.sin6.sin6_port != b->type.sin6.sin6_port) + { + return (false); + } + break; + default: + if (memcmp(&a->type, &b->type, a->length) != 0) { + return (false); + } + } + return (true); +} + +bool +isc_sockaddr_eqaddrprefix(const isc_sockaddr_t *a, const isc_sockaddr_t *b, + unsigned int prefixlen) { + isc_netaddr_t na, nb; + isc_netaddr_fromsockaddr(&na, a); + isc_netaddr_fromsockaddr(&nb, b); + return (isc_netaddr_eqprefix(&na, &nb, prefixlen)); +} + +isc_result_t +isc_sockaddr_totext(const isc_sockaddr_t *sockaddr, isc_buffer_t *target) { + isc_result_t result; + isc_netaddr_t netaddr; + char pbuf[sizeof("65000")]; + unsigned int plen; + isc_region_t avail; + + REQUIRE(sockaddr != NULL); + + /* + * Do the port first, giving us the opportunity to check for + * unsupported address families before calling + * isc_netaddr_fromsockaddr(). + */ + switch (sockaddr->type.sa.sa_family) { + case AF_INET: + snprintf(pbuf, sizeof(pbuf), "%u", + ntohs(sockaddr->type.sin.sin_port)); + break; + case AF_INET6: + snprintf(pbuf, sizeof(pbuf), "%u", + ntohs(sockaddr->type.sin6.sin6_port)); + break; +#ifdef ISC_PLATFORM_HAVESYSUNH + case AF_UNIX: + plen = strlen(sockaddr->type.sunix.sun_path); + if (plen >= isc_buffer_availablelength(target)) { + return (ISC_R_NOSPACE); + } + + isc_buffer_putmem( + target, + (const unsigned char *)sockaddr->type.sunix.sun_path, + plen); + + /* + * Null terminate after used region. + */ + isc_buffer_availableregion(target, &avail); + INSIST(avail.length >= 1); + avail.base[0] = '\0'; + + return (ISC_R_SUCCESS); +#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */ + default: + return (ISC_R_FAILURE); + } + + plen = strlen(pbuf); + INSIST(plen < sizeof(pbuf)); + + isc_netaddr_fromsockaddr(&netaddr, sockaddr); + result = isc_netaddr_totext(&netaddr, target); + if (result != ISC_R_SUCCESS) { + return (result); + } + + if (1 + plen + 1 > isc_buffer_availablelength(target)) { + return (ISC_R_NOSPACE); + } + + isc_buffer_putmem(target, (const unsigned char *)"#", 1); + isc_buffer_putmem(target, (const unsigned char *)pbuf, plen); + + /* + * Null terminate after used region. + */ + isc_buffer_availableregion(target, &avail); + INSIST(avail.length >= 1); + avail.base[0] = '\0'; + + return (ISC_R_SUCCESS); +} + +void +isc_sockaddr_format(const isc_sockaddr_t *sa, char *array, unsigned int size) { + isc_result_t result; + isc_buffer_t buf; + + if (size == 0U) { + return; + } + + isc_buffer_init(&buf, array, size); + result = isc_sockaddr_totext(sa, &buf); + if (result != ISC_R_SUCCESS) { + /* + * The message is the same as in netaddr.c. + */ + snprintf(array, size, "<unknown address, family %u>", + sa->type.sa.sa_family); + array[size - 1] = '\0'; + } +} + +unsigned int +isc_sockaddr_hash(const isc_sockaddr_t *sockaddr, bool address_only) { + unsigned int length = 0; + const unsigned char *s = NULL; + unsigned int h = 0; + unsigned int p = 0; + const struct in6_addr *in6; + + REQUIRE(sockaddr != NULL); + + switch (sockaddr->type.sa.sa_family) { + case AF_INET: + s = (const unsigned char *)&sockaddr->type.sin.sin_addr; + p = ntohs(sockaddr->type.sin.sin_port); + length = sizeof(sockaddr->type.sin.sin_addr.s_addr); + break; + case AF_INET6: + in6 = &sockaddr->type.sin6.sin6_addr; + s = (const unsigned char *)in6; + if (IN6_IS_ADDR_V4MAPPED(in6)) { + s += 12; + length = sizeof(sockaddr->type.sin.sin_addr.s_addr); + } else { + length = sizeof(sockaddr->type.sin6.sin6_addr); + } + p = ntohs(sockaddr->type.sin6.sin6_port); + break; + default: + UNEXPECTED_ERROR(__FILE__, __LINE__, + "unknown address family: %d", + (int)sockaddr->type.sa.sa_family); + s = (const unsigned char *)&sockaddr->type; + length = sockaddr->length; + p = 0; + } + + uint8_t buf[sizeof(struct sockaddr_storage) + sizeof(p)]; + memmove(buf, s, length); + if (!address_only) { + memmove(buf + length, &p, sizeof(p)); + h = isc_hash_function(buf, length + sizeof(p), true); + } else { + h = isc_hash_function(buf, length, true); + } + + return (h); +} + +void +isc_sockaddr_any(isc_sockaddr_t *sockaddr) { + memset(sockaddr, 0, sizeof(*sockaddr)); + sockaddr->type.sin.sin_family = AF_INET; + sockaddr->type.sin.sin_addr.s_addr = INADDR_ANY; + sockaddr->type.sin.sin_port = 0; + sockaddr->length = sizeof(sockaddr->type.sin); + ISC_LINK_INIT(sockaddr, link); +} + +void +isc_sockaddr_any6(isc_sockaddr_t *sockaddr) { + memset(sockaddr, 0, sizeof(*sockaddr)); + sockaddr->type.sin6.sin6_family = AF_INET6; + sockaddr->type.sin6.sin6_addr = in6addr_any; + sockaddr->type.sin6.sin6_port = 0; + sockaddr->length = sizeof(sockaddr->type.sin6); + ISC_LINK_INIT(sockaddr, link); +} + +void +isc_sockaddr_fromin(isc_sockaddr_t *sockaddr, const struct in_addr *ina, + in_port_t port) { + memset(sockaddr, 0, sizeof(*sockaddr)); + sockaddr->type.sin.sin_family = AF_INET; + sockaddr->type.sin.sin_addr = *ina; + sockaddr->type.sin.sin_port = htons(port); + sockaddr->length = sizeof(sockaddr->type.sin); + ISC_LINK_INIT(sockaddr, link); +} + +void +isc_sockaddr_anyofpf(isc_sockaddr_t *sockaddr, int pf) { + switch (pf) { + case AF_INET: + isc_sockaddr_any(sockaddr); + break; + case AF_INET6: + isc_sockaddr_any6(sockaddr); + break; + default: + UNREACHABLE(); + } +} + +void +isc_sockaddr_fromin6(isc_sockaddr_t *sockaddr, const struct in6_addr *ina6, + in_port_t port) { + memset(sockaddr, 0, sizeof(*sockaddr)); + sockaddr->type.sin6.sin6_family = AF_INET6; + sockaddr->type.sin6.sin6_addr = *ina6; + sockaddr->type.sin6.sin6_port = htons(port); + sockaddr->length = sizeof(sockaddr->type.sin6); + ISC_LINK_INIT(sockaddr, link); +} + +void +isc_sockaddr_v6fromin(isc_sockaddr_t *sockaddr, const struct in_addr *ina, + in_port_t port) { + memset(sockaddr, 0, sizeof(*sockaddr)); + sockaddr->type.sin6.sin6_family = AF_INET6; + sockaddr->type.sin6.sin6_addr.s6_addr[10] = 0xff; + sockaddr->type.sin6.sin6_addr.s6_addr[11] = 0xff; + memmove(&sockaddr->type.sin6.sin6_addr.s6_addr[12], ina, 4); + sockaddr->type.sin6.sin6_port = htons(port); + sockaddr->length = sizeof(sockaddr->type.sin6); + ISC_LINK_INIT(sockaddr, link); +} + +int +isc_sockaddr_pf(const isc_sockaddr_t *sockaddr) { + /* + * Get the protocol family of 'sockaddr'. + */ + +#if (AF_INET == PF_INET && AF_INET6 == PF_INET6) + /* + * Assume that PF_xxx == AF_xxx for all AF and PF. + */ + return (sockaddr->type.sa.sa_family); +#else /* if (AF_INET == PF_INET && AF_INET6 == PF_INET6) */ + switch (sockaddr->type.sa.sa_family) { + case AF_INET: + return (PF_INET); + case AF_INET6: + return (PF_INET6); + default: + FATAL_ERROR(__FILE__, __LINE__, "unknown address family: %d", + (int)sockaddr->type.sa.sa_family); + } +#endif /* if (AF_INET == PF_INET && AF_INET6 == PF_INET6) */ +} + +void +isc_sockaddr_fromnetaddr(isc_sockaddr_t *sockaddr, const isc_netaddr_t *na, + in_port_t port) { + memset(sockaddr, 0, sizeof(*sockaddr)); + sockaddr->type.sin.sin_family = na->family; + switch (na->family) { + case AF_INET: + sockaddr->length = sizeof(sockaddr->type.sin); + sockaddr->type.sin.sin_addr = na->type.in; + sockaddr->type.sin.sin_port = htons(port); + break; + case AF_INET6: + sockaddr->length = sizeof(sockaddr->type.sin6); + memmove(&sockaddr->type.sin6.sin6_addr, &na->type.in6, 16); + sockaddr->type.sin6.sin6_scope_id = isc_netaddr_getzone(na); + sockaddr->type.sin6.sin6_port = htons(port); + break; + default: + UNREACHABLE(); + } + ISC_LINK_INIT(sockaddr, link); +} + +void +isc_sockaddr_setport(isc_sockaddr_t *sockaddr, in_port_t port) { + switch (sockaddr->type.sa.sa_family) { + case AF_INET: + sockaddr->type.sin.sin_port = htons(port); + break; + case AF_INET6: + sockaddr->type.sin6.sin6_port = htons(port); + break; + default: + FATAL_ERROR(__FILE__, __LINE__, "unknown address family: %d", + (int)sockaddr->type.sa.sa_family); + } +} + +in_port_t +isc_sockaddr_getport(const isc_sockaddr_t *sockaddr) { + in_port_t port = 0; + + switch (sockaddr->type.sa.sa_family) { + case AF_INET: + port = ntohs(sockaddr->type.sin.sin_port); + break; + case AF_INET6: + port = ntohs(sockaddr->type.sin6.sin6_port); + break; + default: + FATAL_ERROR(__FILE__, __LINE__, "unknown address family: %d", + (int)sockaddr->type.sa.sa_family); + } + + return (port); +} + +bool +isc_sockaddr_ismulticast(const isc_sockaddr_t *sockaddr) { + isc_netaddr_t netaddr; + + if (sockaddr->type.sa.sa_family == AF_INET || + sockaddr->type.sa.sa_family == AF_INET6) + { + isc_netaddr_fromsockaddr(&netaddr, sockaddr); + return (isc_netaddr_ismulticast(&netaddr)); + } + return (false); +} + +bool +isc_sockaddr_isexperimental(const isc_sockaddr_t *sockaddr) { + isc_netaddr_t netaddr; + + if (sockaddr->type.sa.sa_family == AF_INET) { + isc_netaddr_fromsockaddr(&netaddr, sockaddr); + return (isc_netaddr_isexperimental(&netaddr)); + } + return (false); +} + +bool +isc_sockaddr_issitelocal(const isc_sockaddr_t *sockaddr) { + isc_netaddr_t netaddr; + + if (sockaddr->type.sa.sa_family == AF_INET6) { + isc_netaddr_fromsockaddr(&netaddr, sockaddr); + return (isc_netaddr_issitelocal(&netaddr)); + } + return (false); +} + +bool +isc_sockaddr_islinklocal(const isc_sockaddr_t *sockaddr) { + isc_netaddr_t netaddr; + + if (sockaddr->type.sa.sa_family == AF_INET6) { + isc_netaddr_fromsockaddr(&netaddr, sockaddr); + return (isc_netaddr_islinklocal(&netaddr)); + } + return (false); +} + +bool +isc_sockaddr_isnetzero(const isc_sockaddr_t *sockaddr) { + isc_netaddr_t netaddr; + + if (sockaddr->type.sa.sa_family == AF_INET) { + isc_netaddr_fromsockaddr(&netaddr, sockaddr); + return (isc_netaddr_isnetzero(&netaddr)); + } + return (false); +} + +isc_result_t +isc_sockaddr_frompath(isc_sockaddr_t *sockaddr, const char *path) { +#ifdef ISC_PLATFORM_HAVESYSUNH + if (strlen(path) >= sizeof(sockaddr->type.sunix.sun_path)) { + return (ISC_R_NOSPACE); + } + memset(sockaddr, 0, sizeof(*sockaddr)); + sockaddr->length = sizeof(sockaddr->type.sunix); + sockaddr->type.sunix.sun_family = AF_UNIX; + strlcpy(sockaddr->type.sunix.sun_path, path, + sizeof(sockaddr->type.sunix.sun_path)); + return (ISC_R_SUCCESS); +#else /* ifdef ISC_PLATFORM_HAVESYSUNH */ + UNUSED(sockaddr); + UNUSED(path); + return (ISC_R_NOTIMPLEMENTED); +#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */ +} + +isc_result_t +isc_sockaddr_fromsockaddr(isc_sockaddr_t *isa, const struct sockaddr *sa) { + unsigned int length = 0; + + switch (sa->sa_family) { + case AF_INET: + length = sizeof(isa->type.sin); + break; + case AF_INET6: + length = sizeof(isa->type.sin6); + break; +#ifdef ISC_PLATFORM_HAVESYSUNH + case AF_UNIX: + length = sizeof(isa->type.sunix); + break; +#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */ + default: + return (ISC_R_NOTIMPLEMENTED); + } + + memset(isa, 0, sizeof(isc_sockaddr_t)); + memmove(isa, sa, length); + isa->length = length; + + return (ISC_R_SUCCESS); +} diff --git a/lib/isc/stats.c b/lib/isc/stats.c new file mode 100644 index 0000000..aca8074 --- /dev/null +++ b/lib/isc/stats.c @@ -0,0 +1,203 @@ +/* + * 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. + */ + +/*! \file */ + +#include <inttypes.h> +#include <string.h> + +#include <isc/atomic.h> +#include <isc/buffer.h> +#include <isc/magic.h> +#include <isc/mem.h> +#include <isc/platform.h> +#include <isc/print.h> +#include <isc/refcount.h> +#include <isc/stats.h> +#include <isc/util.h> + +#define ISC_STATS_MAGIC ISC_MAGIC('S', 't', 'a', 't') +#define ISC_STATS_VALID(x) ISC_MAGIC_VALID(x, ISC_STATS_MAGIC) + +#if defined(_WIN32) && !defined(_WIN64) +typedef atomic_int_fast32_t isc__atomic_statcounter_t; +#else /* if defined(_WIN32) && !defined(_WIN64) */ +typedef atomic_int_fast64_t isc__atomic_statcounter_t; +#endif /* if defined(_WIN32) && !defined(_WIN64) */ + +struct isc_stats { + unsigned int magic; + isc_mem_t *mctx; + isc_refcount_t references; + int ncounters; + isc__atomic_statcounter_t *counters; +}; + +static isc_result_t +create_stats(isc_mem_t *mctx, int ncounters, isc_stats_t **statsp) { + isc_stats_t *stats; + size_t counters_alloc_size; + + REQUIRE(statsp != NULL && *statsp == NULL); + + stats = isc_mem_get(mctx, sizeof(*stats)); + counters_alloc_size = sizeof(isc__atomic_statcounter_t) * ncounters; + stats->counters = isc_mem_get(mctx, counters_alloc_size); + isc_refcount_init(&stats->references, 1); + for (int i = 0; i < ncounters; i++) { + atomic_init(&stats->counters[i], 0); + } + stats->mctx = NULL; + isc_mem_attach(mctx, &stats->mctx); + stats->ncounters = ncounters; + stats->magic = ISC_STATS_MAGIC; + *statsp = stats; + + return (ISC_R_SUCCESS); +} + +void +isc_stats_attach(isc_stats_t *stats, isc_stats_t **statsp) { + REQUIRE(ISC_STATS_VALID(stats)); + REQUIRE(statsp != NULL && *statsp == NULL); + + isc_refcount_increment(&stats->references); + *statsp = stats; +} + +void +isc_stats_detach(isc_stats_t **statsp) { + isc_stats_t *stats; + + REQUIRE(statsp != NULL && ISC_STATS_VALID(*statsp)); + + stats = *statsp; + *statsp = NULL; + + if (isc_refcount_decrement(&stats->references) == 1) { + isc_refcount_destroy(&stats->references); + isc_mem_put(stats->mctx, stats->counters, + sizeof(isc__atomic_statcounter_t) * + stats->ncounters); + isc_mem_putanddetach(&stats->mctx, stats, sizeof(*stats)); + } +} + +int +isc_stats_ncounters(isc_stats_t *stats) { + REQUIRE(ISC_STATS_VALID(stats)); + + return (stats->ncounters); +} + +isc_result_t +isc_stats_create(isc_mem_t *mctx, isc_stats_t **statsp, int ncounters) { + REQUIRE(statsp != NULL && *statsp == NULL); + + return (create_stats(mctx, ncounters, statsp)); +} + +void +isc_stats_increment(isc_stats_t *stats, isc_statscounter_t counter) { + REQUIRE(ISC_STATS_VALID(stats)); + REQUIRE(counter < stats->ncounters); + + atomic_fetch_add_relaxed(&stats->counters[counter], 1); +} + +void +isc_stats_decrement(isc_stats_t *stats, isc_statscounter_t counter) { + REQUIRE(ISC_STATS_VALID(stats)); + REQUIRE(counter < stats->ncounters); + atomic_fetch_sub_release(&stats->counters[counter], 1); +} + +void +isc_stats_dump(isc_stats_t *stats, isc_stats_dumper_t dump_fn, void *arg, + unsigned int options) { + int i; + + REQUIRE(ISC_STATS_VALID(stats)); + + for (i = 0; i < stats->ncounters; i++) { + uint32_t counter = atomic_load_acquire(&stats->counters[i]); + if ((options & ISC_STATSDUMP_VERBOSE) == 0 && counter == 0) { + continue; + } + dump_fn((isc_statscounter_t)i, counter, arg); + } +} + +void +isc_stats_set(isc_stats_t *stats, uint64_t val, isc_statscounter_t counter) { + REQUIRE(ISC_STATS_VALID(stats)); + REQUIRE(counter < stats->ncounters); + + atomic_store_release(&stats->counters[counter], val); +} + +void +isc_stats_update_if_greater(isc_stats_t *stats, isc_statscounter_t counter, + isc_statscounter_t value) { + REQUIRE(ISC_STATS_VALID(stats)); + REQUIRE(counter < stats->ncounters); + + isc_statscounter_t curr_value = + atomic_load_acquire(&stats->counters[counter]); + do { + if (curr_value >= value) { + break; + } + } while (!atomic_compare_exchange_weak_acq_rel( + &stats->counters[counter], &curr_value, value)); +} + +isc_statscounter_t +isc_stats_get_counter(isc_stats_t *stats, isc_statscounter_t counter) { + REQUIRE(ISC_STATS_VALID(stats)); + REQUIRE(counter < stats->ncounters); + + return (atomic_load_acquire(&stats->counters[counter])); +} + +void +isc_stats_resize(isc_stats_t **statsp, int ncounters) { + isc_stats_t *stats; + size_t counters_alloc_size; + isc__atomic_statcounter_t *newcounters; + + REQUIRE(statsp != NULL && *statsp != NULL); + REQUIRE(ISC_STATS_VALID(*statsp)); + REQUIRE(ncounters > 0); + + stats = *statsp; + if (stats->ncounters >= ncounters) { + /* We already have enough counters. */ + return; + } + + /* Grow number of counters. */ + counters_alloc_size = sizeof(isc__atomic_statcounter_t) * ncounters; + newcounters = isc_mem_get(stats->mctx, counters_alloc_size); + for (int i = 0; i < ncounters; i++) { + atomic_init(&newcounters[i], 0); + } + for (int i = 0; i < stats->ncounters; i++) { + uint32_t counter = atomic_load_acquire(&stats->counters[i]); + atomic_store_release(&newcounters[i], counter); + } + isc_mem_put(stats->mctx, stats->counters, + sizeof(isc__atomic_statcounter_t) * stats->ncounters); + stats->counters = newcounters; + stats->ncounters = ncounters; +} diff --git a/lib/isc/string.c b/lib/isc/string.c new file mode 100644 index 0000000..5de6d88 --- /dev/null +++ b/lib/isc/string.c @@ -0,0 +1,149 @@ +/* + * 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. + */ + +/* + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 2001 Mike Barcroft <mike@FreeBSD.org> + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Chris Torek. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/*! \file */ + +#ifdef _GNU_SOURCE +#undef _GNU_SOURCE +#endif /* ifdef _GNU_SOURCE */ +#include <string.h> + +#include <isc/string.h> /* IWYU pragma: keep */ +#include <isc/util.h> + +#if !defined(HAVE_STRLCPY) +size_t +strlcpy(char *dst, const char *src, size_t size) { + char *d = dst; + const char *s = src; + size_t n = size; + + /* Copy as many bytes as will fit */ + if (n != 0U && --n != 0U) { + do { + if ((*d++ = *s++) == 0) { + break; + } + } while (--n != 0U); + } + + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0U) { + if (size != 0U) { + *d = '\0'; /* NUL-terminate dst */ + } + while (*s++) { + } + } + + return (s - src - 1); /* count does not include NUL */ +} +#endif /* !defined(HAVE_STRLCPY) */ + +#if !defined(HAVE_STRLCAT) +size_t +strlcat(char *dst, const char *src, size_t size) { + char *d = dst; + const char *s = src; + size_t n = size; + size_t dlen; + + /* Find the end of dst and adjust bytes left but don't go past end */ + while (n-- != 0U && *d != '\0') { + d++; + } + dlen = d - dst; + n = size - dlen; + + if (n == 0U) { + return (dlen + strlen(s)); + } + while (*s != '\0') { + if (n != 1U) { + *d++ = *s; + n--; + } + s++; + } + *d = '\0'; + + return (dlen + (s - src)); /* count does not include NUL */ +} +#endif /* !defined(HAVE_STRLCAT) */ + +#if !defined(HAVE_STRNSTR) +char * +strnstr(const char *s, const char *find, size_t slen) { + char c, sc, *r; + size_t len; + + if ((c = *find++) != '\0') { + len = strlen(find); + do { + do { + if (slen-- < 1 || (sc = *s++) == '\0') + return (NULL); + } while (sc != c); + if (len > slen) + return (NULL); + } while (strncmp(s, find, len) != 0); + s--; + } + DE_CONST(s, r); + return (r); +} +#endif + +int +isc_string_strerror_r(int errnum, char *buf, size_t buflen) { +#if defined(_WIN32) || defined(_WIN64) + return (strerror_s(buf, buflen, errnum)); +#else /* if defined(_WIN32) || defined(_WIN64) */ + return (strerror_r(errnum, buf, buflen)); +#endif /* if defined(_WIN32) || defined(_WIN64) */ +} diff --git a/lib/isc/symtab.c b/lib/isc/symtab.c new file mode 100644 index 0000000..ff022a2 --- /dev/null +++ b/lib/isc/symtab.c @@ -0,0 +1,294 @@ +/* + * 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. + */ + +/*! \file */ + +#include <ctype.h> +#include <stdbool.h> + +#include <isc/magic.h> +#include <isc/mem.h> +#include <isc/string.h> +#include <isc/symtab.h> +#include <isc/util.h> + +typedef struct elt { + char *key; + unsigned int type; + isc_symvalue_t value; + LINK(struct elt) link; +} elt_t; + +typedef LIST(elt_t) eltlist_t; + +#define SYMTAB_MAGIC ISC_MAGIC('S', 'y', 'm', 'T') +#define VALID_SYMTAB(st) ISC_MAGIC_VALID(st, SYMTAB_MAGIC) + +struct isc_symtab { + /* Unlocked. */ + unsigned int magic; + isc_mem_t *mctx; + unsigned int size; + unsigned int count; + unsigned int maxload; + eltlist_t *table; + isc_symtabaction_t undefine_action; + void *undefine_arg; + bool case_sensitive; +}; + +isc_result_t +isc_symtab_create(isc_mem_t *mctx, unsigned int size, + isc_symtabaction_t undefine_action, void *undefine_arg, + bool case_sensitive, isc_symtab_t **symtabp) { + isc_symtab_t *symtab; + unsigned int i; + + REQUIRE(mctx != NULL); + REQUIRE(symtabp != NULL && *symtabp == NULL); + REQUIRE(size > 0); /* Should be prime. */ + + symtab = isc_mem_get(mctx, sizeof(*symtab)); + + symtab->mctx = NULL; + isc_mem_attach(mctx, &symtab->mctx); + symtab->table = isc_mem_get(mctx, size * sizeof(eltlist_t)); + for (i = 0; i < size; i++) { + INIT_LIST(symtab->table[i]); + } + symtab->size = size; + symtab->count = 0; + symtab->maxload = size * 3 / 4; + symtab->undefine_action = undefine_action; + symtab->undefine_arg = undefine_arg; + symtab->case_sensitive = case_sensitive; + symtab->magic = SYMTAB_MAGIC; + + *symtabp = symtab; + + return (ISC_R_SUCCESS); +} + +void +isc_symtab_destroy(isc_symtab_t **symtabp) { + isc_symtab_t *symtab; + unsigned int i; + elt_t *elt, *nelt; + + REQUIRE(symtabp != NULL); + symtab = *symtabp; + *symtabp = NULL; + REQUIRE(VALID_SYMTAB(symtab)); + + for (i = 0; i < symtab->size; i++) { + for (elt = HEAD(symtab->table[i]); elt != NULL; elt = nelt) { + nelt = NEXT(elt, link); + if (symtab->undefine_action != NULL) { + (symtab->undefine_action)(elt->key, elt->type, + elt->value, + symtab->undefine_arg); + } + isc_mem_put(symtab->mctx, elt, sizeof(*elt)); + } + } + isc_mem_put(symtab->mctx, symtab->table, + symtab->size * sizeof(eltlist_t)); + symtab->magic = 0; + isc_mem_putanddetach(&symtab->mctx, symtab, sizeof(*symtab)); +} + +static unsigned int +hash(const char *key, bool case_sensitive) { + const char *s; + unsigned int h = 0; + int c; + + /* + * This hash function is similar to the one Ousterhout + * uses in Tcl. + */ + + if (case_sensitive) { + for (s = key; *s != '\0'; s++) { + h += (h << 3) + *s; + } + } else { + for (s = key; *s != '\0'; s++) { + c = *s; + c = tolower((unsigned char)c); + h += (h << 3) + c; + } + } + + return (h); +} + +#define FIND(s, k, t, b, e) \ + b = hash((k), (s)->case_sensitive) % (s)->size; \ + if ((s)->case_sensitive) { \ + for (e = HEAD((s)->table[b]); e != NULL; e = NEXT(e, link)) { \ + if (((t) == 0 || e->type == (t)) && \ + strcmp(e->key, (k)) == 0) \ + break; \ + } \ + } else { \ + for (e = HEAD((s)->table[b]); e != NULL; e = NEXT(e, link)) { \ + if (((t) == 0 || e->type == (t)) && \ + strcasecmp(e->key, (k)) == 0) \ + break; \ + } \ + } + +isc_result_t +isc_symtab_lookup(isc_symtab_t *symtab, const char *key, unsigned int type, + isc_symvalue_t *value) { + unsigned int bucket; + elt_t *elt; + + REQUIRE(VALID_SYMTAB(symtab)); + REQUIRE(key != NULL); + + FIND(symtab, key, type, bucket, elt); + + if (elt == NULL) { + return (ISC_R_NOTFOUND); + } + + if (value != NULL) { + *value = elt->value; + } + + return (ISC_R_SUCCESS); +} + +static void +grow_table(isc_symtab_t *symtab) { + eltlist_t *newtable; + unsigned int i, newsize, newmax; + + REQUIRE(symtab != NULL); + + newsize = symtab->size * 2; + newmax = newsize * 3 / 4; + INSIST(newsize > 0U && newmax > 0U); + + newtable = isc_mem_get(symtab->mctx, newsize * sizeof(eltlist_t)); + + for (i = 0; i < newsize; i++) { + INIT_LIST(newtable[i]); + } + + for (i = 0; i < symtab->size; i++) { + elt_t *elt, *nelt; + + for (elt = HEAD(symtab->table[i]); elt != NULL; elt = nelt) { + unsigned int hv; + + nelt = NEXT(elt, link); + + UNLINK(symtab->table[i], elt, link); + hv = hash(elt->key, symtab->case_sensitive); + APPEND(newtable[hv % newsize], elt, link); + } + } + + isc_mem_put(symtab->mctx, symtab->table, + symtab->size * sizeof(eltlist_t)); + + symtab->table = newtable; + symtab->size = newsize; + symtab->maxload = newmax; +} + +isc_result_t +isc_symtab_define(isc_symtab_t *symtab, const char *key, unsigned int type, + isc_symvalue_t value, isc_symexists_t exists_policy) { + unsigned int bucket; + elt_t *elt; + + REQUIRE(VALID_SYMTAB(symtab)); + REQUIRE(key != NULL); + REQUIRE(type != 0); + + FIND(symtab, key, type, bucket, elt); + + if (exists_policy != isc_symexists_add && elt != NULL) { + if (exists_policy == isc_symexists_reject) { + return (ISC_R_EXISTS); + } + INSIST(exists_policy == isc_symexists_replace); + UNLINK(symtab->table[bucket], elt, link); + if (symtab->undefine_action != NULL) { + (symtab->undefine_action)(elt->key, elt->type, + elt->value, + symtab->undefine_arg); + } + } else { + elt = isc_mem_get(symtab->mctx, sizeof(*elt)); + ISC_LINK_INIT(elt, link); + symtab->count++; + } + + /* + * Though the "key" can be const coming in, it is not stored as const + * so that the calling program can easily have writable access to + * it in its undefine_action function. In the event that it *was* + * truly const coming in and then the caller modified it anyway ... + * well, don't do that! + */ + DE_CONST(key, elt->key); + elt->type = type; + elt->value = value; + + /* + * We prepend so that the most recent definition will be found. + */ + PREPEND(symtab->table[bucket], elt, link); + + if (symtab->count > symtab->maxload) { + grow_table(symtab); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_symtab_undefine(isc_symtab_t *symtab, const char *key, unsigned int type) { + unsigned int bucket; + elt_t *elt; + + REQUIRE(VALID_SYMTAB(symtab)); + REQUIRE(key != NULL); + + FIND(symtab, key, type, bucket, elt); + + if (elt == NULL) { + return (ISC_R_NOTFOUND); + } + + if (symtab->undefine_action != NULL) { + (symtab->undefine_action)(elt->key, elt->type, elt->value, + symtab->undefine_arg); + } + UNLINK(symtab->table[bucket], elt, link); + isc_mem_put(symtab->mctx, elt, sizeof(*elt)); + symtab->count--; + + return (ISC_R_SUCCESS); +} + +unsigned int +isc_symtab_count(isc_symtab_t *symtab) { + REQUIRE(VALID_SYMTAB(symtab)); + return (symtab->count); +} diff --git a/lib/isc/task.c b/lib/isc/task.c new file mode 100644 index 0000000..3d8deaf --- /dev/null +++ b/lib/isc/task.c @@ -0,0 +1,1486 @@ +/* + * 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. + */ + +/*! \file */ + +/* + * XXXRTH Need to document the states a task can be in, and the rules + * for changing states. + */ + +#include <stdbool.h> +#include <unistd.h> + +#include <isc/app.h> +#include <isc/atomic.h> +#include <isc/condition.h> +#include <isc/event.h> +#include <isc/log.h> +#include <isc/magic.h> +#include <isc/mem.h> +#include <isc/once.h> +#include <isc/platform.h> +#include <isc/print.h> +#include <isc/random.h> +#include <isc/refcount.h> +#include <isc/string.h> +#include <isc/task.h> +#include <isc/thread.h> +#include <isc/time.h> +#include <isc/util.h> + +#ifdef HAVE_LIBXML2 +#include <libxml/xmlwriter.h> +#define ISC_XMLCHAR (const xmlChar *) +#endif /* HAVE_LIBXML2 */ + +#ifdef HAVE_JSON_C +#include <json_object.h> +#endif /* HAVE_JSON_C */ + +#include "task_p.h" + +/* + * Task manager is built around 'as little locking as possible' concept. + * Each thread has his own queue of tasks to be run, if a task is in running + * state it will stay on the runner it's currently on, if a task is in idle + * state it can be woken up on a specific runner with isc_task_sendto - that + * helps with data locality on CPU. + * + * To make load even some tasks (from task pools) are bound to specific + * queues using isc_task_create_bound. This way load balancing between + * CPUs/queues happens on the higher layer. + */ + +#ifdef ISC_TASK_TRACE +#define XTRACE(m) \ + fprintf(stderr, "task %p thread %zu: %s\n", task, isc_tid_v, (m)) +#define XTTRACE(t, m) \ + fprintf(stderr, "task %p thread %zu: %s\n", (t), isc_tid_v, (m)) +#define XTHREADTRACE(m) fprintf(stderr, "thread %zu: %s\n", isc_tid_v, (m)) +#else /* ifdef ISC_TASK_TRACE */ +#define XTRACE(m) +#define XTTRACE(t, m) +#define XTHREADTRACE(m) +#endif /* ifdef ISC_TASK_TRACE */ + +/*** + *** Types. + ***/ + +typedef enum { + task_state_idle, /* not doing anything, events queue empty */ + task_state_ready, /* waiting in worker's queue */ + task_state_paused, /* not running, paused */ + task_state_pausing, /* running, waiting to be paused */ + task_state_running, /* actively processing events */ + task_state_done /* shutting down, no events or references */ +} task_state_t; + +#if defined(HAVE_LIBXML2) || defined(HAVE_JSON_C) +static const char *statenames[] = { + "idle", "ready", "paused", "pausing", "running", "done", +}; +#endif /* if defined(HAVE_LIBXML2) || defined(HAVE_JSON_C) */ + +#define TASK_MAGIC ISC_MAGIC('T', 'A', 'S', 'K') +#define VALID_TASK(t) ISC_MAGIC_VALID(t, TASK_MAGIC) + +struct isc_task { + /* Not locked. */ + unsigned int magic; + isc_taskmgr_t *manager; + isc_mutex_t lock; + /* Locked by task lock. */ + int threadid; + task_state_t state; + int pause_cnt; + isc_refcount_t references; + isc_refcount_t running; + isc_eventlist_t events; + isc_eventlist_t on_shutdown; + unsigned int nevents; + unsigned int quantum; + isc_stdtime_t now; + isc_time_t tnow; + char name[16]; + void *tag; + bool bound; + /* Protected by atomics */ + atomic_bool shuttingdown; + atomic_bool privileged; + /* Locked by task manager lock. */ + LINK(isc_task_t) link; +}; + +#define TASK_SHUTTINGDOWN(t) (atomic_load_acquire(&(t)->shuttingdown)) +#define TASK_PRIVILEGED(t) (atomic_load_acquire(&(t)->privileged)) + +#define TASK_MANAGER_MAGIC ISC_MAGIC('T', 'S', 'K', 'M') +#define VALID_MANAGER(m) ISC_MAGIC_VALID(m, TASK_MANAGER_MAGIC) + +struct isc_taskmgr { + /* Not locked. */ + unsigned int magic; + isc_refcount_t references; + isc_mem_t *mctx; + isc_mutex_t lock; + atomic_uint_fast32_t tasks_count; + isc_nm_t *netmgr; + + /* Locked by task manager lock. */ + unsigned int default_quantum; + LIST(isc_task_t) tasks; + atomic_uint_fast32_t mode; + atomic_bool exclusive_req; + bool exiting; + isc_task_t *excl; +}; + +#define DEFAULT_DEFAULT_QUANTUM 25 + +/*% + * The following are intended for internal use (indicated by "isc__" + * prefix) but are not declared as static, allowing direct access from + * unit tests etc. + */ + +bool +isc_task_purgeevent(isc_task_t *task, isc_event_t *event); +void +isc_taskmgr_setexcltask(isc_taskmgr_t *mgr, isc_task_t *task); +isc_result_t +isc_taskmgr_excltask(isc_taskmgr_t *mgr, isc_task_t **taskp); + +/*** + *** Tasks. + ***/ + +static void +task_finished(isc_task_t *task) { + isc_taskmgr_t *manager = task->manager; + isc_mem_t *mctx = manager->mctx; + REQUIRE(EMPTY(task->events)); + REQUIRE(task->nevents == 0); + REQUIRE(EMPTY(task->on_shutdown)); + REQUIRE(task->state == task_state_done); + + XTRACE("task_finished"); + + isc_refcount_destroy(&task->running); + isc_refcount_destroy(&task->references); + + LOCK(&manager->lock); + UNLINK(manager->tasks, task, link); + atomic_fetch_sub(&manager->tasks_count, 1); + UNLOCK(&manager->lock); + + isc_mutex_destroy(&task->lock); + task->magic = 0; + isc_mem_put(mctx, task, sizeof(*task)); + + isc_taskmgr_detach(&manager); +} + +isc_result_t +isc_task_create(isc_taskmgr_t *manager, unsigned int quantum, + isc_task_t **taskp) { + return (isc_task_create_bound(manager, quantum, taskp, -1)); +} + +isc_result_t +isc_task_create_bound(isc_taskmgr_t *manager, unsigned int quantum, + isc_task_t **taskp, int threadid) { + isc_task_t *task = NULL; + bool exiting; + + REQUIRE(VALID_MANAGER(manager)); + REQUIRE(taskp != NULL && *taskp == NULL); + + XTRACE("isc_task_create"); + + task = isc_mem_get(manager->mctx, sizeof(*task)); + *task = (isc_task_t){ 0 }; + + isc_taskmgr_attach(manager, &task->manager); + + if (threadid == -1) { + /* + * Task is not pinned to a queue, it's threadid will be + * chosen when first task will be sent to it - either + * randomly or specified by isc_task_sendto. + */ + task->bound = false; + task->threadid = -1; + } else { + /* + * Task is pinned to a queue, it'll always be run + * by a specific thread. + */ + task->bound = true; + task->threadid = threadid; + } + + isc_mutex_init(&task->lock); + task->state = task_state_idle; + task->pause_cnt = 0; + + isc_refcount_init(&task->references, 1); + isc_refcount_init(&task->running, 0); + INIT_LIST(task->events); + INIT_LIST(task->on_shutdown); + task->nevents = 0; + task->quantum = (quantum > 0) ? quantum : manager->default_quantum; + atomic_init(&task->shuttingdown, false); + atomic_init(&task->privileged, false); + task->now = 0; + isc_time_settoepoch(&task->tnow); + memset(task->name, 0, sizeof(task->name)); + task->tag = NULL; + INIT_LINK(task, link); + task->magic = TASK_MAGIC; + + LOCK(&manager->lock); + exiting = manager->exiting; + if (!exiting) { + APPEND(manager->tasks, task, link); + atomic_fetch_add(&manager->tasks_count, 1); + } + UNLOCK(&manager->lock); + + if (exiting) { + isc_refcount_destroy(&task->running); + isc_refcount_decrement(&task->references); + isc_refcount_destroy(&task->references); + isc_mutex_destroy(&task->lock); + isc_taskmgr_detach(&task->manager); + isc_mem_put(manager->mctx, task, sizeof(*task)); + return (ISC_R_SHUTTINGDOWN); + } + + *taskp = task; + + return (ISC_R_SUCCESS); +} + +void +isc_task_attach(isc_task_t *source, isc_task_t **targetp) { + /* + * Attach *targetp to source. + */ + + REQUIRE(VALID_TASK(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + XTTRACE(source, "isc_task_attach"); + + isc_refcount_increment(&source->references); + + *targetp = source; +} + +static bool +task_shutdown(isc_task_t *task) { + bool was_idle = false; + isc_event_t *event, *prev; + + /* + * Caller must be holding the task's lock. + */ + + XTRACE("task_shutdown"); + + if (atomic_compare_exchange_strong(&task->shuttingdown, + &(bool){ false }, true)) + { + XTRACE("shutting down"); + if (task->state == task_state_idle) { + INSIST(EMPTY(task->events)); + task->state = task_state_ready; + was_idle = true; + } + INSIST(task->state == task_state_ready || + task->state == task_state_paused || + task->state == task_state_pausing || + task->state == task_state_running); + + /* + * Note that we post shutdown events LIFO. + */ + for (event = TAIL(task->on_shutdown); event != NULL; + event = prev) + { + prev = PREV(event, ev_link); + DEQUEUE(task->on_shutdown, event, ev_link); + ENQUEUE(task->events, event, ev_link); + task->nevents++; + } + } + + return (was_idle); +} + +/* + * Moves a task onto the appropriate run queue. + * + * Caller must NOT hold queue lock. + */ +static void +task_ready(isc_task_t *task) { + isc_taskmgr_t *manager = task->manager; + REQUIRE(VALID_MANAGER(manager)); + + XTRACE("task_ready"); + + isc_refcount_increment0(&task->running); + LOCK(&task->lock); + isc_nm_task_enqueue(manager->netmgr, task, task->threadid); + UNLOCK(&task->lock); +} + +void +isc_task_ready(isc_task_t *task) { + task_ready(task); +} + +static bool +task_detach(isc_task_t *task) { + /* + * Caller must be holding the task lock. + */ + + XTRACE("detach"); + + if (isc_refcount_decrement(&task->references) == 1 && + task->state == task_state_idle) + { + INSIST(EMPTY(task->events)); + /* + * There are no references to this task, and no + * pending events. We could try to optimize and + * either initiate shutdown or clean up the task, + * depending on its state, but it's easier to just + * make the task ready and allow run() or the event + * loop to deal with shutting down and termination. + */ + task->state = task_state_ready; + return (true); + } + + return (false); +} + +void +isc_task_detach(isc_task_t **taskp) { + isc_task_t *task; + bool was_idle; + + /* + * Detach *taskp from its task. + */ + + REQUIRE(taskp != NULL); + task = *taskp; + REQUIRE(VALID_TASK(task)); + + XTRACE("isc_task_detach"); + + LOCK(&task->lock); + was_idle = task_detach(task); + UNLOCK(&task->lock); + + if (was_idle) { + task_ready(task); + } + + *taskp = NULL; +} + +static bool +task_send(isc_task_t *task, isc_event_t **eventp, int c) { + bool was_idle = false; + isc_event_t *event; + + /* + * Caller must be holding the task lock. + */ + + REQUIRE(eventp != NULL); + event = *eventp; + *eventp = NULL; + REQUIRE(event != NULL); + REQUIRE(event->ev_type > 0); + REQUIRE(task->state != task_state_done); + REQUIRE(!ISC_LINK_LINKED(event, ev_ratelink)); + + XTRACE("task_send"); + + if (task->bound) { + c = task->threadid; + } else if (c < 0) { + c = -1; + } + + if (task->state == task_state_idle) { + was_idle = true; + task->threadid = c; + INSIST(EMPTY(task->events)); + task->state = task_state_ready; + } + INSIST(task->state == task_state_ready || + task->state == task_state_running || + task->state == task_state_paused || + task->state == task_state_pausing); + ENQUEUE(task->events, event, ev_link); + task->nevents++; + + return (was_idle); +} + +void +isc_task_send(isc_task_t *task, isc_event_t **eventp) { + isc_task_sendto(task, eventp, -1); +} + +void +isc_task_sendanddetach(isc_task_t **taskp, isc_event_t **eventp) { + isc_task_sendtoanddetach(taskp, eventp, -1); +} + +void +isc_task_sendto(isc_task_t *task, isc_event_t **eventp, int c) { + bool was_idle; + + /* + * Send '*event' to 'task'. + */ + + REQUIRE(VALID_TASK(task)); + XTRACE("isc_task_send"); + + /* + * We're trying hard to hold locks for as short a time as possible. + * We're also trying to hold as few locks as possible. This is why + * some processing is deferred until after the lock is released. + */ + LOCK(&task->lock); + was_idle = task_send(task, eventp, c); + UNLOCK(&task->lock); + + if (was_idle) { + /* + * We need to add this task to the ready queue. + * + * We've waited until now to do it because making a task + * ready requires locking the manager. If we tried to do + * this while holding the task lock, we could deadlock. + * + * We've changed the state to ready, so no one else will + * be trying to add this task to the ready queue. The + * only way to leave the ready state is by executing the + * task. It thus doesn't matter if events are added, + * removed, or a shutdown is started in the interval + * between the time we released the task lock, and the time + * we add the task to the ready queue. + */ + task_ready(task); + } +} + +void +isc_task_sendtoanddetach(isc_task_t **taskp, isc_event_t **eventp, int c) { + bool idle1, idle2; + isc_task_t *task; + + /* + * Send '*event' to '*taskp' and then detach '*taskp' from its + * task. + */ + + REQUIRE(taskp != NULL); + task = *taskp; + REQUIRE(VALID_TASK(task)); + XTRACE("isc_task_sendanddetach"); + + LOCK(&task->lock); + idle1 = task_send(task, eventp, c); + idle2 = task_detach(task); + UNLOCK(&task->lock); + + /* + * If idle1, then idle2 shouldn't be true as well since we're holding + * the task lock, and thus the task cannot switch from ready back to + * idle. + */ + INSIST(!(idle1 && idle2)); + + if (idle1 || idle2) { + task_ready(task); + } + + *taskp = NULL; +} + +#define PURGE_OK(event) (((event)->ev_attributes & ISC_EVENTATTR_NOPURGE) == 0) + +static unsigned int +dequeue_events(isc_task_t *task, void *sender, isc_eventtype_t first, + isc_eventtype_t last, void *tag, isc_eventlist_t *events, + bool purging) { + isc_event_t *event, *next_event; + unsigned int count = 0; + + REQUIRE(VALID_TASK(task)); + REQUIRE(last >= first); + + XTRACE("dequeue_events"); + + /* + * Events matching 'sender', whose type is >= first and <= last, and + * whose tag is 'tag' will be dequeued. If 'purging', matching events + * which are marked as unpurgable will not be dequeued. + * + * sender == NULL means "any sender", and tag == NULL means "any tag". + */ + + LOCK(&task->lock); + + for (event = HEAD(task->events); event != NULL; event = next_event) { + next_event = NEXT(event, ev_link); + if (event->ev_type >= first && event->ev_type <= last && + (sender == NULL || event->ev_sender == sender) && + (tag == NULL || event->ev_tag == tag) && + (!purging || PURGE_OK(event))) + { + DEQUEUE(task->events, event, ev_link); + task->nevents--; + ENQUEUE(*events, event, ev_link); + count++; + } + } + + UNLOCK(&task->lock); + + return (count); +} + +unsigned int +isc_task_purgerange(isc_task_t *task, void *sender, isc_eventtype_t first, + isc_eventtype_t last, void *tag) { + unsigned int count; + isc_eventlist_t events; + isc_event_t *event, *next_event; + REQUIRE(VALID_TASK(task)); + + /* + * Purge events from a task's event queue. + */ + + XTRACE("isc_task_purgerange"); + + ISC_LIST_INIT(events); + + count = dequeue_events(task, sender, first, last, tag, &events, true); + + for (event = HEAD(events); event != NULL; event = next_event) { + next_event = NEXT(event, ev_link); + ISC_LIST_UNLINK(events, event, ev_link); + isc_event_free(&event); + } + + /* + * Note that purging never changes the state of the task. + */ + + return (count); +} + +unsigned int +isc_task_purge(isc_task_t *task, void *sender, isc_eventtype_t type, + void *tag) { + /* + * Purge events from a task's event queue. + */ + REQUIRE(VALID_TASK(task)); + + XTRACE("isc_task_purge"); + + return (isc_task_purgerange(task, sender, type, type, tag)); +} + +bool +isc_task_purgeevent(isc_task_t *task, isc_event_t *event) { + bool found = false; + + /* + * Purge 'event' from a task's event queue. + */ + + REQUIRE(VALID_TASK(task)); + + /* + * If 'event' is on the task's event queue, it will be purged, + * unless it is marked as unpurgeable. 'event' does not have to be + * on the task's event queue; in fact, it can even be an invalid + * pointer. Purging only occurs if the event is actually on the task's + * event queue. + * + * Purging never changes the state of the task. + */ + + LOCK(&task->lock); + if (ISC_LINK_LINKED(event, ev_link)) { + DEQUEUE(task->events, event, ev_link); + task->nevents--; + found = true; + } + UNLOCK(&task->lock); + + if (!found) { + return (false); + } + + isc_event_free(&event); + + return (true); +} + +unsigned int +isc_task_unsendrange(isc_task_t *task, void *sender, isc_eventtype_t first, + isc_eventtype_t last, void *tag, isc_eventlist_t *events) { + /* + * Remove events from a task's event queue. + */ + REQUIRE(VALID_TASK(task)); + + XTRACE("isc_task_unsendrange"); + + return (dequeue_events(task, sender, first, last, tag, events, false)); +} + +unsigned int +isc_task_unsend(isc_task_t *task, void *sender, isc_eventtype_t type, void *tag, + isc_eventlist_t *events) { + /* + * Remove events from a task's event queue. + */ + + XTRACE("isc_task_unsend"); + + return (dequeue_events(task, sender, type, type, tag, events, false)); +} + +isc_result_t +isc_task_onshutdown(isc_task_t *task, isc_taskaction_t action, void *arg) { + bool disallowed = false; + isc_result_t result = ISC_R_SUCCESS; + isc_event_t *event; + + /* + * Send a shutdown event with action 'action' and argument 'arg' when + * 'task' is shutdown. + */ + + REQUIRE(VALID_TASK(task)); + REQUIRE(action != NULL); + + event = isc_event_allocate(task->manager->mctx, NULL, + ISC_TASKEVENT_SHUTDOWN, action, arg, + sizeof(*event)); + + if (TASK_SHUTTINGDOWN(task)) { + disallowed = true; + result = ISC_R_SHUTTINGDOWN; + } else { + LOCK(&task->lock); + ENQUEUE(task->on_shutdown, event, ev_link); + UNLOCK(&task->lock); + } + + if (disallowed) { + isc_mem_put(task->manager->mctx, event, sizeof(*event)); + } + + return (result); +} + +void +isc_task_shutdown(isc_task_t *task) { + bool was_idle; + + /* + * Shutdown 'task'. + */ + + REQUIRE(VALID_TASK(task)); + + LOCK(&task->lock); + was_idle = task_shutdown(task); + UNLOCK(&task->lock); + + if (was_idle) { + task_ready(task); + } +} + +void +isc_task_destroy(isc_task_t **taskp) { + /* + * Destroy '*taskp'. + */ + + REQUIRE(taskp != NULL); + + isc_task_shutdown(*taskp); + isc_task_detach(taskp); +} + +void +isc_task_setname(isc_task_t *task, const char *name, void *tag) { + /* + * Name 'task'. + */ + + REQUIRE(VALID_TASK(task)); + + LOCK(&task->lock); + strlcpy(task->name, name, sizeof(task->name)); + task->tag = tag; + UNLOCK(&task->lock); +} + +const char * +isc_task_getname(isc_task_t *task) { + REQUIRE(VALID_TASK(task)); + + return (task->name); +} + +void * +isc_task_gettag(isc_task_t *task) { + REQUIRE(VALID_TASK(task)); + + return (task->tag); +} + +void +isc_task_getcurrenttime(isc_task_t *task, isc_stdtime_t *t) { + REQUIRE(VALID_TASK(task)); + REQUIRE(t != NULL); + + LOCK(&task->lock); + *t = task->now; + UNLOCK(&task->lock); +} + +void +isc_task_getcurrenttimex(isc_task_t *task, isc_time_t *t) { + REQUIRE(VALID_TASK(task)); + REQUIRE(t != NULL); + + LOCK(&task->lock); + *t = task->tnow; + UNLOCK(&task->lock); +} + +isc_nm_t * +isc_task_getnetmgr(isc_task_t *task) { + REQUIRE(VALID_TASK(task)); + + return (task->manager->netmgr); +} + +void +isc_task_setquantum(isc_task_t *task, unsigned int quantum) { + REQUIRE(VALID_TASK(task)); + + LOCK(&task->lock); + task->quantum = (quantum > 0) ? quantum + : task->manager->default_quantum; + UNLOCK(&task->lock); +} + +/*** + *** Task Manager. + ***/ + +static isc_result_t +task_run(isc_task_t *task) { + unsigned int dispatch_count = 0; + bool finished = false; + isc_event_t *event = NULL; + isc_result_t result = ISC_R_SUCCESS; + uint32_t quantum; + + REQUIRE(VALID_TASK(task)); + + LOCK(&task->lock); + quantum = task->quantum; + + /* + * It is possible because that we have a paused task in the queue - it + * might have been paused in the meantime and we never hold both queue + * and task lock to avoid deadlocks, just bail then. + */ + if (task->state != task_state_ready) { + goto done; + } + + INSIST(task->state == task_state_ready); + task->state = task_state_running; + XTRACE("running"); + XTRACE(task->name); + TIME_NOW(&task->tnow); + task->now = isc_time_seconds(&task->tnow); + + while (true) { + if (!EMPTY(task->events)) { + event = HEAD(task->events); + DEQUEUE(task->events, event, ev_link); + task->nevents--; + + /* + * Execute the event action. + */ + XTRACE("execute action"); + XTRACE(task->name); + if (event->ev_action != NULL) { + UNLOCK(&task->lock); + (event->ev_action)(task, event); + LOCK(&task->lock); + } + XTRACE("execution complete"); + dispatch_count++; + } + + if (isc_refcount_current(&task->references) == 0 && + EMPTY(task->events) && !TASK_SHUTTINGDOWN(task)) + { + /* + * There are no references and no pending events for + * this task, which means it will not become runnable + * again via an external action (such as sending an + * event or detaching). + * + * We initiate shutdown to prevent it from becoming a + * zombie. + * + * We do this here instead of in the "if + * EMPTY(task->events)" block below because: + * + * If we post no shutdown events, we want the task + * to finish. + * + * If we did post shutdown events, will still want + * the task's quantum to be applied. + */ + INSIST(!task_shutdown(task)); + } + + if (EMPTY(task->events)) { + /* + * Nothing else to do for this task right now. + */ + XTRACE("empty"); + if (isc_refcount_current(&task->references) == 0 && + TASK_SHUTTINGDOWN(task)) + { + /* + * The task is done. + */ + XTRACE("done"); + task->state = task_state_done; + } else { + if (task->state == task_state_running) { + XTRACE("idling"); + task->state = task_state_idle; + } else if (task->state == task_state_pausing) { + XTRACE("pausing"); + task->state = task_state_paused; + } + } + break; + } else if (task->state == task_state_pausing) { + /* + * We got a pause request on this task, stop working on + * it and switch the state to paused. + */ + XTRACE("pausing"); + task->state = task_state_paused; + break; + } else if (dispatch_count >= quantum) { + /* + * Our quantum has expired, but there is more work to be + * done. We'll requeue it to the ready queue later. + * + * We don't check quantum until dispatching at least one + * event, so the minimum quantum is one. + */ + XTRACE("quantum"); + task->state = task_state_ready; + result = ISC_R_QUOTA; + break; + } + } + +done: + if (isc_refcount_decrement(&task->running) == 1 && + task->state == task_state_done) + { + finished = true; + } + UNLOCK(&task->lock); + + if (finished) { + task_finished(task); + } + + return (result); +} + +isc_result_t +isc_task_run(isc_task_t *task) { + return (task_run(task)); +} + +static void +manager_free(isc_taskmgr_t *manager) { + isc_refcount_destroy(&manager->references); + isc_nm_detach(&manager->netmgr); + + isc_mutex_destroy(&manager->lock); + manager->magic = 0; + isc_mem_putanddetach(&manager->mctx, manager, sizeof(*manager)); +} + +void +isc_taskmgr_attach(isc_taskmgr_t *source, isc_taskmgr_t **targetp) { + REQUIRE(VALID_MANAGER(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + isc_refcount_increment(&source->references); + + *targetp = source; +} + +void +isc_taskmgr_detach(isc_taskmgr_t **managerp) { + REQUIRE(managerp != NULL); + REQUIRE(VALID_MANAGER(*managerp)); + + isc_taskmgr_t *manager = *managerp; + *managerp = NULL; + + if (isc_refcount_decrement(&manager->references) == 1) { + manager_free(manager); + } +} + +isc_result_t +isc__taskmgr_create(isc_mem_t *mctx, unsigned int default_quantum, isc_nm_t *nm, + isc_taskmgr_t **managerp) { + isc_taskmgr_t *manager; + + /* + * Create a new task manager. + */ + + REQUIRE(managerp != NULL && *managerp == NULL); + REQUIRE(nm != NULL); + + manager = isc_mem_get(mctx, sizeof(*manager)); + *manager = (isc_taskmgr_t){ .magic = TASK_MANAGER_MAGIC }; + + isc_mutex_init(&manager->lock); + + if (default_quantum == 0) { + default_quantum = DEFAULT_DEFAULT_QUANTUM; + } + manager->default_quantum = default_quantum; + + if (nm != NULL) { + isc_nm_attach(nm, &manager->netmgr); + } + + INIT_LIST(manager->tasks); + atomic_init(&manager->mode, isc_taskmgrmode_normal); + atomic_init(&manager->exclusive_req, false); + atomic_init(&manager->tasks_count, 0); + + isc_mem_attach(mctx, &manager->mctx); + + isc_refcount_init(&manager->references, 1); + + *managerp = manager; + + return (ISC_R_SUCCESS); +} + +void +isc__taskmgr_shutdown(isc_taskmgr_t *manager) { + isc_task_t *task; + + REQUIRE(VALID_MANAGER(manager)); + + XTHREADTRACE("isc_taskmgr_shutdown"); + /* + * Only one non-worker thread may ever call this routine. + * If a worker thread wants to initiate shutdown of the + * task manager, it should ask some non-worker thread to call + * isc_taskmgr_destroy(), e.g. by signalling a condition variable + * that the startup thread is sleeping on. + */ + + /* + * Unlike elsewhere, we're going to hold this lock a long time. + * We need to do so, because otherwise the list of tasks could + * change while we were traversing it. + * + * This is also the only function where we will hold both the + * task manager lock and a task lock at the same time. + */ + LOCK(&manager->lock); + if (manager->excl != NULL) { + isc_task_detach((isc_task_t **)&manager->excl); + } + + /* + * Make sure we only get called once. + */ + INSIST(manager->exiting == false); + manager->exiting = true; + + /* + * Post shutdown event(s) to every task (if they haven't already been + * posted). + */ + for (task = HEAD(manager->tasks); task != NULL; task = NEXT(task, link)) + { + bool was_idle; + + LOCK(&task->lock); + was_idle = task_shutdown(task); + if (was_idle) { + task->threadid = 0; + } + UNLOCK(&task->lock); + + if (was_idle) { + task_ready(task); + } + } + + UNLOCK(&manager->lock); +} + +void +isc__taskmgr_destroy(isc_taskmgr_t **managerp) { + REQUIRE(managerp != NULL && VALID_MANAGER(*managerp)); + XTHREADTRACE("isc_taskmgr_destroy"); + +#ifdef ISC_TASK_TRACE + int counter = 0; + while (isc_refcount_current(&(*managerp)->references) > 1 && + counter++ < 1000) + { + usleep(10 * 1000); + } + INSIST(counter < 1000); +#else + while (isc_refcount_current(&(*managerp)->references) > 1) { + usleep(10 * 1000); + } +#endif + + isc_taskmgr_detach(managerp); +} + +void +isc_taskmgr_setexcltask(isc_taskmgr_t *mgr, isc_task_t *task) { + REQUIRE(VALID_MANAGER(mgr)); + REQUIRE(VALID_TASK(task)); + + LOCK(&task->lock); + REQUIRE(task->threadid == 0); + UNLOCK(&task->lock); + + LOCK(&mgr->lock); + if (mgr->excl != NULL) { + isc_task_detach(&mgr->excl); + } + isc_task_attach(task, &mgr->excl); + UNLOCK(&mgr->lock); +} + +isc_result_t +isc_taskmgr_excltask(isc_taskmgr_t *mgr, isc_task_t **taskp) { + isc_result_t result; + + REQUIRE(VALID_MANAGER(mgr)); + REQUIRE(taskp != NULL && *taskp == NULL); + + LOCK(&mgr->lock); + if (mgr->excl != NULL) { + isc_task_attach(mgr->excl, taskp); + result = ISC_R_SUCCESS; + } else if (mgr->exiting) { + result = ISC_R_SHUTTINGDOWN; + } else { + result = ISC_R_NOTFOUND; + } + UNLOCK(&mgr->lock); + + return (result); +} + +isc_result_t +isc_task_beginexclusive(isc_task_t *task) { + isc_taskmgr_t *manager; + + REQUIRE(VALID_TASK(task)); + + manager = task->manager; + + REQUIRE(task->state == task_state_running); + + LOCK(&manager->lock); + REQUIRE(task == manager->excl || + (manager->exiting && manager->excl == NULL)); + UNLOCK(&manager->lock); + + if (!atomic_compare_exchange_strong(&manager->exclusive_req, + &(bool){ false }, true)) + { + return (ISC_R_LOCKBUSY); + } + + if (isc_log_wouldlog(isc_lctx, ISC_LOG_DEBUG(1))) { + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_OTHER, ISC_LOG_DEBUG(1), + "exclusive task mode: %s", "starting"); + } + + isc_nm_pause(manager->netmgr); + + if (isc_log_wouldlog(isc_lctx, ISC_LOG_DEBUG(1))) { + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_OTHER, ISC_LOG_DEBUG(1), + "exclusive task mode: %s", "started"); + } + + return (ISC_R_SUCCESS); +} + +void +isc_task_endexclusive(isc_task_t *task) { + isc_taskmgr_t *manager; + + REQUIRE(VALID_TASK(task)); + REQUIRE(task->state == task_state_running); + manager = task->manager; + + if (isc_log_wouldlog(isc_lctx, ISC_LOG_DEBUG(1))) { + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_OTHER, ISC_LOG_DEBUG(1), + "exclusive task mode: %s", "ending"); + } + + isc_nm_resume(manager->netmgr); + + if (isc_log_wouldlog(isc_lctx, ISC_LOG_DEBUG(1))) { + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_OTHER, ISC_LOG_DEBUG(1), + "exclusive task mode: %s", "ended"); + } + + REQUIRE(atomic_compare_exchange_strong(&manager->exclusive_req, + &(bool){ true }, false)); +} + +void +isc_task_pause(isc_task_t *task) { + REQUIRE(VALID_TASK(task)); + + LOCK(&task->lock); + task->pause_cnt++; + if (task->pause_cnt > 1) { + /* + * Someone already paused this task, just increase + * the number of pausing clients. + */ + UNLOCK(&task->lock); + return; + } + + INSIST(task->state == task_state_idle || + task->state == task_state_ready || + task->state == task_state_running); + if (task->state == task_state_running) { + task->state = task_state_pausing; + } else { + task->state = task_state_paused; + } + UNLOCK(&task->lock); +} + +void +isc_task_unpause(isc_task_t *task) { + bool was_idle = false; + + REQUIRE(VALID_TASK(task)); + + LOCK(&task->lock); + task->pause_cnt--; + INSIST(task->pause_cnt >= 0); + if (task->pause_cnt > 0) { + UNLOCK(&task->lock); + return; + } + + INSIST(task->state == task_state_paused || + task->state == task_state_pausing); + /* If the task was pausing we can't reschedule it */ + if (task->state == task_state_pausing) { + task->state = task_state_running; + } else { + task->state = task_state_idle; + } + if (task->state == task_state_idle && !EMPTY(task->events)) { + task->state = task_state_ready; + was_idle = true; + } + UNLOCK(&task->lock); + + if (was_idle) { + task_ready(task); + } +} + +void +isc_taskmgr_setmode(isc_taskmgr_t *manager, isc_taskmgrmode_t mode) { + atomic_store(&manager->mode, mode); +} + +isc_taskmgrmode_t +isc_taskmgr_mode(isc_taskmgr_t *manager) { + return (atomic_load(&manager->mode)); +} + +void +isc_task_setprivilege(isc_task_t *task, bool priv) { + REQUIRE(VALID_TASK(task)); + + atomic_store_release(&task->privileged, priv); +} + +bool +isc_task_getprivilege(isc_task_t *task) { + REQUIRE(VALID_TASK(task)); + + return (TASK_PRIVILEGED(task)); +} + +bool +isc_task_privileged(isc_task_t *task) { + REQUIRE(VALID_TASK(task)); + + return (isc_taskmgr_mode(task->manager) && TASK_PRIVILEGED(task)); +} + +bool +isc_task_exiting(isc_task_t *task) { + REQUIRE(VALID_TASK(task)); + + return (TASK_SHUTTINGDOWN(task)); +} + +#ifdef HAVE_LIBXML2 +#define TRY0(a) \ + do { \ + xmlrc = (a); \ + if (xmlrc < 0) \ + goto error; \ + } while (0) +int +isc_taskmgr_renderxml(isc_taskmgr_t *mgr, void *writer0) { + isc_task_t *task = NULL; + int xmlrc; + xmlTextWriterPtr writer = (xmlTextWriterPtr)writer0; + + LOCK(&mgr->lock); + + /* + * Write out the thread-model, and some details about each depending + * on which type is enabled. + */ + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "thread-model")); + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "type")); + TRY0(xmlTextWriterWriteString(writer, ISC_XMLCHAR "threaded")); + TRY0(xmlTextWriterEndElement(writer)); /* type */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "default-quantum")); + TRY0(xmlTextWriterWriteFormatString(writer, "%d", + mgr->default_quantum)); + TRY0(xmlTextWriterEndElement(writer)); /* default-quantum */ + + TRY0(xmlTextWriterEndElement(writer)); /* thread-model */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "tasks")); + task = ISC_LIST_HEAD(mgr->tasks); + while (task != NULL) { + LOCK(&task->lock); + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "task")); + + if (task->name[0] != 0) { + TRY0(xmlTextWriterStartElement(writer, + ISC_XMLCHAR "name")); + TRY0(xmlTextWriterWriteFormatString(writer, "%s", + task->name)); + TRY0(xmlTextWriterEndElement(writer)); /* name */ + } + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "reference" + "s")); + TRY0(xmlTextWriterWriteFormatString( + writer, "%" PRIuFAST32, + isc_refcount_current(&task->references))); + TRY0(xmlTextWriterEndElement(writer)); /* references */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "id")); + TRY0(xmlTextWriterWriteFormatString(writer, "%p", task)); + TRY0(xmlTextWriterEndElement(writer)); /* id */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "state")); + TRY0(xmlTextWriterWriteFormatString(writer, "%s", + statenames[task->state])); + TRY0(xmlTextWriterEndElement(writer)); /* state */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "quantum")); + TRY0(xmlTextWriterWriteFormatString(writer, "%d", + task->quantum)); + TRY0(xmlTextWriterEndElement(writer)); /* quantum */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "events")); + TRY0(xmlTextWriterWriteFormatString(writer, "%d", + task->nevents)); + TRY0(xmlTextWriterEndElement(writer)); /* events */ + + TRY0(xmlTextWriterEndElement(writer)); + + UNLOCK(&task->lock); + task = ISC_LIST_NEXT(task, link); + } + TRY0(xmlTextWriterEndElement(writer)); /* tasks */ + +error: + if (task != NULL) { + UNLOCK(&task->lock); + } + UNLOCK(&mgr->lock); + + return (xmlrc); +} +#endif /* HAVE_LIBXML2 */ + +#ifdef HAVE_JSON_C +#define CHECKMEM(m) \ + do { \ + if (m == NULL) { \ + result = ISC_R_NOMEMORY; \ + goto error; \ + } \ + } while (0) + +isc_result_t +isc_taskmgr_renderjson(isc_taskmgr_t *mgr, void *tasks0) { + isc_result_t result = ISC_R_SUCCESS; + isc_task_t *task = NULL; + json_object *obj = NULL, *array = NULL, *taskobj = NULL; + json_object *tasks = (json_object *)tasks0; + + LOCK(&mgr->lock); + + /* + * Write out the thread-model, and some details about each depending + * on which type is enabled. + */ + obj = json_object_new_string("threaded"); + CHECKMEM(obj); + json_object_object_add(tasks, "thread-model", obj); + + obj = json_object_new_int(mgr->default_quantum); + CHECKMEM(obj); + json_object_object_add(tasks, "default-quantum", obj); + + array = json_object_new_array(); + CHECKMEM(array); + + for (task = ISC_LIST_HEAD(mgr->tasks); task != NULL; + task = ISC_LIST_NEXT(task, link)) + { + char buf[255]; + + LOCK(&task->lock); + + taskobj = json_object_new_object(); + CHECKMEM(taskobj); + json_object_array_add(array, taskobj); + + snprintf(buf, sizeof(buf), "%p", task); + obj = json_object_new_string(buf); + CHECKMEM(obj); + json_object_object_add(taskobj, "id", obj); + + if (task->name[0] != 0) { + obj = json_object_new_string(task->name); + CHECKMEM(obj); + json_object_object_add(taskobj, "name", obj); + } + + obj = json_object_new_int( + isc_refcount_current(&task->references)); + CHECKMEM(obj); + json_object_object_add(taskobj, "references", obj); + + obj = json_object_new_string(statenames[task->state]); + CHECKMEM(obj); + json_object_object_add(taskobj, "state", obj); + + obj = json_object_new_int(task->quantum); + CHECKMEM(obj); + json_object_object_add(taskobj, "quantum", obj); + + obj = json_object_new_int(task->nevents); + CHECKMEM(obj); + json_object_object_add(taskobj, "events", obj); + + UNLOCK(&task->lock); + } + + json_object_object_add(tasks, "tasks", array); + array = NULL; + result = ISC_R_SUCCESS; + +error: + if (array != NULL) { + json_object_put(array); + } + + if (task != NULL) { + UNLOCK(&task->lock); + } + UNLOCK(&mgr->lock); + + return (result); +} +#endif /* ifdef HAVE_JSON_C */ diff --git a/lib/isc/task_p.h b/lib/isc/task_p.h new file mode 100644 index 0000000..5fc50b0 --- /dev/null +++ b/lib/isc/task_p.h @@ -0,0 +1,106 @@ +/* + * 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. + */ + +#pragma once + +#include <isc/mem.h> +#include <isc/result.h> +#include <isc/task.h> + +isc_result_t +isc__taskmgr_create(isc_mem_t *mctx, unsigned int default_quantum, isc_nm_t *nm, + isc_taskmgr_t **managerp); +/*%< + * Create a new task manager. + * + * Notes: + * + *\li If 'default_quantum' is non-zero, then it will be used as the default + * quantum value when tasks are created. If zero, then an implementation + * defined default quantum will be used. + * + *\li If 'nm' is set then netmgr is paused when an exclusive task mode + * is requested. + * + * Requires: + * + *\li 'mctx' is a valid memory context. + * + *\li managerp != NULL && *managerp == NULL + * + * Ensures: + * + *\li On success, '*managerp' will be attached to the newly created task + * manager. + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + *\li #ISC_R_NOTHREADS No threads could be created. + *\li #ISC_R_UNEXPECTED An unexpected error occurred. + *\li #ISC_R_SHUTTINGDOWN The non-threaded, shared, task + * manager shutting down. + */ + +void +isc__taskmgr_destroy(isc_taskmgr_t **managerp); +/*%< + * Destroy '*managerp'. + * + * Notes: + * + *\li Calling isc_taskmgr_destroy() will shutdown all tasks managed by + * *managerp that haven't already been shutdown. The call will block + * until all tasks have entered the done state. + * + *\li isc_taskmgr_destroy() must not be called by a task event action, + * because it would block forever waiting for the event action to + * complete. An event action that wants to cause task manager shutdown + * should request some non-event action thread of execution to do the + * shutdown, e.g. by signaling a condition variable or using + * isc_app_shutdown(). + * + *\li Task manager references are not reference counted, so the caller + * must ensure that no attempt will be made to use the manager after + * isc_taskmgr_destroy() returns. + * + * Requires: + * + *\li '*managerp' is a valid task manager. + * + *\li 'isc__taskmgr_shutdown()' and isc__netmgr_shutdown() have been + * called. + */ + +void +isc__taskmgr_shutdown(isc_taskmgr_t *manager); +/*%> + * Shutdown 'manager'. + * + * Notes: + * + *\li Calling isc__taskmgr_shutdown() will shut down all tasks managed by + * *managerp that haven't already been shut down. + * + * Requires: + * + *\li 'manager' is a valid task manager. + * + *\li isc_taskmgr_destroy() has not be called previously on '*managerp'. + * + * Ensures: + * + *\li All resources used by the task manager, and any tasks it managed, + * have been freed. + */ diff --git a/lib/isc/taskpool.c b/lib/isc/taskpool.c new file mode 100644 index 0000000..3099532 --- /dev/null +++ b/lib/isc/taskpool.c @@ -0,0 +1,157 @@ +/* + * 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. + */ + +/*! \file */ + +#include <stdbool.h> + +#include <isc/mem.h> +#include <isc/random.h> +#include <isc/taskpool.h> +#include <isc/util.h> + +/*** + *** Types. + ***/ + +struct isc_taskpool { + isc_mem_t *mctx; + isc_taskmgr_t *tmgr; + unsigned int ntasks; + unsigned int quantum; + isc_task_t **tasks; +}; + +/*** + *** Functions. + ***/ + +static void +alloc_pool(isc_taskmgr_t *tmgr, isc_mem_t *mctx, unsigned int ntasks, + unsigned int quantum, isc_taskpool_t **poolp) { + isc_taskpool_t *pool; + unsigned int i; + + pool = isc_mem_get(mctx, sizeof(*pool)); + + pool->mctx = NULL; + isc_mem_attach(mctx, &pool->mctx); + pool->ntasks = ntasks; + pool->quantum = quantum; + pool->tmgr = tmgr; + pool->tasks = isc_mem_get(mctx, ntasks * sizeof(isc_task_t *)); + for (i = 0; i < ntasks; i++) { + pool->tasks[i] = NULL; + } + + *poolp = pool; +} + +isc_result_t +isc_taskpool_create(isc_taskmgr_t *tmgr, isc_mem_t *mctx, unsigned int ntasks, + unsigned int quantum, bool priv, isc_taskpool_t **poolp) { + unsigned int i; + isc_taskpool_t *pool = NULL; + + INSIST(ntasks > 0); + + /* Allocate the pool structure */ + alloc_pool(tmgr, mctx, ntasks, quantum, &pool); + + /* Create the tasks */ + for (i = 0; i < ntasks; i++) { + isc_result_t result = isc_task_create_bound(tmgr, quantum, + &pool->tasks[i], i); + if (result != ISC_R_SUCCESS) { + isc_taskpool_destroy(&pool); + return (result); + } + isc_task_setprivilege(pool->tasks[i], priv); + isc_task_setname(pool->tasks[i], "taskpool", NULL); + } + + *poolp = pool; + return (ISC_R_SUCCESS); +} + +void +isc_taskpool_gettask(isc_taskpool_t *pool, isc_task_t **targetp) { + isc_task_attach(pool->tasks[isc_random_uniform(pool->ntasks)], targetp); +} + +int +isc_taskpool_size(isc_taskpool_t *pool) { + REQUIRE(pool != NULL); + return (pool->ntasks); +} + +isc_result_t +isc_taskpool_expand(isc_taskpool_t **sourcep, unsigned int size, bool priv, + isc_taskpool_t **targetp) { + isc_taskpool_t *pool; + + REQUIRE(sourcep != NULL && *sourcep != NULL); + REQUIRE(targetp != NULL && *targetp == NULL); + + pool = *sourcep; + *sourcep = NULL; + if (size > pool->ntasks) { + isc_taskpool_t *newpool = NULL; + unsigned int i; + + /* Allocate a new pool structure */ + alloc_pool(pool->tmgr, pool->mctx, size, pool->quantum, + &newpool); + + /* Copy over the tasks from the old pool */ + for (i = 0; i < pool->ntasks; i++) { + newpool->tasks[i] = pool->tasks[i]; + pool->tasks[i] = NULL; + } + + /* Create new tasks */ + for (i = pool->ntasks; i < size; i++) { + isc_result_t result = + isc_task_create_bound(pool->tmgr, pool->quantum, + &newpool->tasks[i], i); + if (result != ISC_R_SUCCESS) { + *sourcep = pool; + isc_taskpool_destroy(&newpool); + return (result); + } + isc_task_setprivilege(newpool->tasks[i], priv); + isc_task_setname(newpool->tasks[i], "taskpool", NULL); + } + + isc_taskpool_destroy(&pool); + pool = newpool; + } + + *targetp = pool; + return (ISC_R_SUCCESS); +} + +void +isc_taskpool_destroy(isc_taskpool_t **poolp) { + unsigned int i; + isc_taskpool_t *pool = *poolp; + *poolp = NULL; + for (i = 0; i < pool->ntasks; i++) { + if (pool->tasks[i] != NULL) { + isc_task_detach(&pool->tasks[i]); + } + } + isc_mem_put(pool->mctx, pool->tasks, + pool->ntasks * sizeof(isc_task_t *)); + isc_mem_putanddetach(&pool->mctx, pool, sizeof(*pool)); +} diff --git a/lib/isc/tests/Kyuafile b/lib/isc/tests/Kyuafile new file mode 100644 index 0000000..89b962c --- /dev/null +++ b/lib/isc/tests/Kyuafile @@ -0,0 +1,43 @@ +-- 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. + +syntax(2) +test_suite('bind9') + +tap_test_program{name='aes_test'} +tap_test_program{name='buffer_test'} +tap_test_program{name='counter_test'} +tap_test_program{name='errno_test'} +tap_test_program{name='file_test'} +tap_test_program{name='hash_test'} +tap_test_program{name='heap_test'} +tap_test_program{name='hmac_test'} +tap_test_program{name='ht_test'} +tap_test_program{name='lex_test'} +tap_test_program{name='md_test'} +tap_test_program{name='mem_test'} +tap_test_program{name='netaddr_test'} +tap_test_program{name='netmgr_test'} +tap_test_program{name='parse_test'} +tap_test_program{name='pool_test'} +tap_test_program{name='radix_test'} +tap_test_program{name='quota_test'} +tap_test_program{name='regex_test'} +tap_test_program{name='result_test'} +tap_test_program{name='safe_test'} +tap_test_program{name='siphash_test'} +tap_test_program{name='sockaddr_test'} +tap_test_program{name='socket_test'} +tap_test_program{name='symtab_test'} +tap_test_program{name='task_test'} +tap_test_program{name='taskpool_test'} +tap_test_program{name='time_test'} +tap_test_program{name='timer_test'} diff --git a/lib/isc/tests/Makefile.in b/lib/isc/tests/Makefile.in new file mode 100644 index 0000000..2a2020f --- /dev/null +++ b/lib/isc/tests/Makefile.in @@ -0,0 +1,227 @@ +# 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. + +srcdir = @srcdir@ +VPATH = @srcdir@ +top_srcdir = @top_srcdir@ + +VERSION=@BIND9_VERSION@ + +@BIND9_MAKE_INCLUDES@ + +CINCLUDES = -I. -Iinclude ${ISC_INCLUDES} \ + ${OPENSSL_CFLAGS} @CMOCKA_CFLAGS@ \ + ${JSON_C_CFLAGS} \ + ${LIBUV_CFLAGS} \ + ${LIBXML2_CFLAGS} +CDEFINES = -DTESTS="\"${top_builddir}/lib/isc/tests/\"" + +ISCLIBS = ../libisc.@A@ @NO_LIBTOOL_ISCLIBS@ +ISCDEPLIBS = ../libisc.@A@ + +LIBS = @LIBS@ @CMOCKA_LIBS@ + +OBJS = isctest.@O@ + +SRCS = isctest.c aes_test.c buffer_test.c \ + counter_test.c crc64_test.c errno_test.c file_test.c hash_test.c \ + heap_test.c hmac_test.c ht_test.c lex_test.c \ + mem_test.c md_test.c netaddr_test.c netmgr_test.c \ + parse_test.c pool_test.c \ + quota_test.c radix_test.c random_test.c \ + regex_test.c result_test.c safe_test.c siphash_test.c sockaddr_test.c \ + socket_test.c stats_test.c symtab_test.c task_test.c \ + taskpool_test.c time_test.c timer_test.c + +SUBDIRS = +TARGETS = aes_test@EXEEXT@ buffer_test@EXEEXT@ \ + counter_test@EXEEXT@ crc64_test@EXEEXT@ \ + errno_test@EXEEXT@ file_test@EXEEXT@ \ + hash_test@EXEEXT@ heap_test@EXEEXT@ hmac_test@EXEEXT@ \ + ht_test@EXEEXT@ lex_test@EXEEXT@ \ + mem_test@EXEEXT@ md_test@EXEEXT@ \ + netaddr_test@EXEEXT@ netmgr_test@EXEEXT@ \ + parse_test@EXEEXT@ pool_test@EXEEXT@ \ + quota_test@EXEEXT@ radix_test@EXEEXT@ \ + random_test@EXEEXT@ regex_test@EXEEXT@ result_test@EXEEXT@ \ + safe_test@EXEEXT@ siphash_test@EXEEXT@ sockaddr_test@EXEEXT@ \ + socket_test@EXEEXT@ stats_test@EXEEXT@ symtab_test@EXEEXT@ \ + task_test@EXEEXT@ taskpool_test@EXEEXT@ time_test@EXEEXT@ \ + timer_test@EXEEXT@ + +@BIND9_MAKE_RULES@ + +aes_test@EXEEXT@: aes_test.@O@ ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ aes_test.@O@ \ + ${ISCLIBS} ${LIBS} + +buffer_test@EXEEXT@: buffer_test.@O@ isctest.@O@ ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ buffer_test.@O@ isctest.@O@ \ + ${ISCLIBS} ${LIBS} + +counter_test@EXEEXT@: counter_test.@O@ isctest.@O@ ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ counter_test.@O@ isctest.@O@ \ + ${ISCLIBS} ${LIBS} + +crc64_test@EXEEXT@: crc64_test.@O@ ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ crc64_test.@O@ \ + ${ISCLIBS} ${LIBS} + +errno_test@EXEEXT@: errno_test.@O@ ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ errno_test.@O@ \ + ${ISCLIBS} ${LIBS} + +file_test@EXEEXT@: file_test.@O@ ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ file_test.@O@ \ + ${ISCLIBS} ${LIBS} + +hash_test@EXEEXT@: hash_test.@O@ ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ hash_test.@O@ \ + ${ISCLIBS} ${LIBS} + +heap_test@EXEEXT@: heap_test.@O@ isctest.@O@ ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ heap_test.@O@ isctest.@O@ \ + ${ISCLIBS} ${LIBS} + +hmac_test@EXEEXT@: hmac_test.@O@ ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ hmac_test.@O@ \ + ${ISCLIBS} ${OPENSSL_LIBS} ${LIBS} + +ht_test@EXEEXT@: ht_test.@O@ isctest.@O@ ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ ht_test.@O@ isctest.@O@ \ + ${ISCLIBS} ${LIBS} + +lex_test@EXEEXT@: lex_test.@O@ isctest.@O@ ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ lex_test.@O@ isctest.@O@ \ + ${ISCLIBS} ${LIBS} + +md_test@EXEEXT@: md_test.@O@ ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ md_test.@O@ \ + ${ISCLIBS} ${OPENSSL_LIBS} ${LIBS} + +mem_test@EXEEXT@: mem_test.@O@ isctest.@O@ ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ mem_test.@O@ isctest.@O@ \ + ${ISCLIBS} ${LIBS} + +netaddr_test@EXEEXT@: netaddr_test.@O@ ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ netaddr_test.@O@ \ + ${ISCLIBS} ${LIBS} + +parse_test@EXEEXT@: parse_test.@O@ isctest.@O@ ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ parse_test.@O@ isctest.@O@ \ + ${ISCLIBS} ${LIBS} + +pool_test@EXEEXT@: pool_test.@O@ isctest.@O@ ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ pool_test.@O@ isctest.@O@ \ + ${ISCLIBS} ${LIBS} + +quota_test@EXEEXT@: quota_test.@O@ isctest.@O@ ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ quota_test.@O@ isctest.@O@ \ + ${ISCLIBS} ${LIBS} + +radix_test@EXEEXT@: radix_test.@O@ isctest.@O@ ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ radix_test.@O@ isctest.@O@ \ + ${ISCLIBS} ${LIBS} + +random_test@EXEEXT@: random_test.@O@ isctest.@O@ ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ random_test.@O@ isctest.@O@ \ + ${ISCLIBS} ${LIBS} -lm + +regex_test@EXEEXT@: regex_test.@O@ ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ regex_test.@O@ \ + ${ISCLIBS} ${LIBS} + +result_test@EXEEXT@: result_test.@O@ ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ result_test.@O@ \ + ${ISCLIBS} ${LIBS} + +safe_test@EXEEXT@: safe_test.@O@ ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ safe_test.@O@ \ + ${ISCLIBS} ${LIBS} + +siphash_test@EXEEXT@: siphash_test.@O@ ../siphash.c ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ siphash_test.@O@ \ + ${ISCLIBS} ${LIBS} + +socket_test@EXEEXT@: socket_test.@O@ isctest.@O@ ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ socket_test.@O@ isctest.@O@ \ + ${ISCLIBS} ${LIBS} + +sockaddr_test@EXEEXT@: sockaddr_test.@O@ isctest.@O@ ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ sockaddr_test.@O@ isctest.@O@ \ + ${ISCLIBS} ${LIBS} + +stats_test@EXEEXT@: stats_test.@O@ isctest.@O@ ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ stats_test.@O@ isctest.@O@ \ + ${ISCLIBS} ${LIBS} + +symtab_test@EXEEXT@: symtab_test.@O@ isctest.@O@ ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ symtab_test.@O@ isctest.@O@ \ + ${ISCLIBS} ${LIBS} + +task_test@EXEEXT@: task_test.@O@ isctest.@O@ ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ task_test.@O@ isctest.@O@ \ + ${ISCLIBS} ${LIBS} + +taskpool_test@EXEEXT@: taskpool_test.@O@ isctest.@O@ ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ taskpool_test.@O@ isctest.@O@ \ + ${ISCLIBS} ${LIBS} + +netmgr_test@EXEEXT@: netmgr_test.@O@ isctest.@O@ ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ netmgr_test.@O@ isctest.@O@ \ + ${ISCLIBS} ${LIBS} ${LIBUV_LIBS} + +time_test@EXEEXT@: time_test.@O@ ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ time_test.@O@ \ + ${ISCLIBS} ${LIBS} + +timer_test@EXEEXT@: timer_test.@O@ isctest.@O@ ${ISCDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} \ + ${LDFLAGS} -o $@ timer_test.@O@ isctest.@O@ \ + ${ISCLIBS} ${LIBS} + +unit:: + sh ${top_builddir}/unit/unittest.sh + +clean distclean:: + rm -f ${TARGETS} + rm -f atf.out diff --git a/lib/isc/tests/aes_test.c b/lib/isc/tests/aes_test.c new file mode 100644 index 0000000..2990184 --- /dev/null +++ b/lib/isc/tests/aes_test.c @@ -0,0 +1,249 @@ +/* + * 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 <setjmp.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define UNIT_TESTING +#include <cmocka.h> + +#include <isc/aes.h> +#include <isc/buffer.h> +#include <isc/hex.h> +#include <isc/platform.h> +#include <isc/region.h> +#include <isc/string.h> +#include <isc/util.h> + +/* + * Test data from NIST KAT + */ + +isc_result_t +tohexstr(unsigned char *d, char *out); + +size_t +fromhexstr(const char *in, unsigned char *d); + +unsigned char plaintext[3 * ISC_AES_BLOCK_LENGTH]; +unsigned char ciphertext[ISC_AES_BLOCK_LENGTH]; +char str[2 * ISC_AES_BLOCK_LENGTH + 1]; +unsigned char key[ISC_AES256_KEYLENGTH + 1]; +size_t len; + +isc_result_t +tohexstr(unsigned char *d, char *out) { + isc_buffer_t b; + isc_region_t r; + + isc_buffer_init(&b, out, 2 * ISC_AES_BLOCK_LENGTH + 1); + r.base = d; + r.length = ISC_AES_BLOCK_LENGTH; + return (isc_hex_totext(&r, 0, "", &b)); +} + +size_t +fromhexstr(const char *in, unsigned char *d) { + isc_buffer_t b; + isc_result_t ret; + + isc_buffer_init(&b, d, ISC_AES256_KEYLENGTH + 1); + ret = isc_hex_decodestring(in, &b); + if (ret != ISC_R_SUCCESS) { + return (0); + } + return (isc_buffer_usedlength(&b)); +} + +typedef struct aes_testcase { + const char *key; + const char *input; + const char *result; +} aes_testcase_t; + +/* AES 128 test vectors */ +static void +isc_aes128_test(void **state) { + aes_testcase_t testcases[] = { /* Test 1 (KAT ECBVarTxt128 #3) */ + { "00000000000000000000000000000000", + "F0000000000000000000000000000000", + "96D9FD5CC4F07441727DF0F33E401A36" }, + /* Test 2 (KAT ECBVarTxt128 #123) */ + { "00000000000000000000000000000000", + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0", + "F9B0FDA0C4A898F5B9E6F661C4CE4D07" }, + /* Test 3 (KAT ECBVarKey128 #3) */ + { "F0000000000000000000000000000000", + "00000000000000000000000000000000", + "970014D634E2B7650777E8E84D03CCD8" }, + /* Test 4 (KAT ECBVarKey128 #123) */ + { "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0", + "00000000000000000000000000000000", + "41C78C135ED9E98C096640647265DA1E" }, + /* Test 5 (KAT ECBGFSbox128 #3) */ + { "00000000000000000000000000000000", + "6A118A874519E64E9963798A503F1D35", + "DC43BE40BE0E53712F7E2BF5CA707209" }, + /* Test 6 (KAT ECBKeySbox128 #3) */ + { "B6364AC4E1DE1E285EAF144A2415F7A0", + "00000000000000000000000000000000", + "5D9B05578FC944B3CF1CCF0E746CD581" }, + { NULL, NULL, NULL } + }; + + aes_testcase_t *testcase = testcases; + + UNUSED(state); + + while (testcase->key != NULL) { + len = fromhexstr(testcase->key, key); + assert_int_equal(len, ISC_AES128_KEYLENGTH); + len = fromhexstr(testcase->input, plaintext); + assert_int_equal(len, ISC_AES_BLOCK_LENGTH); + isc_aes128_crypt(key, plaintext, ciphertext); + assert_int_equal(tohexstr(ciphertext, str), ISC_R_SUCCESS); + assert_string_equal(str, testcase->result); + + testcase++; + } +} + +/* AES 192 test vectors */ +static void +isc_aes192_test(void **state) { + aes_testcase_t testcases[] = { + /* Test 1 (KAT ECBVarTxt192 #3) */ + { "000000000000000000000000000000000000000000000000", + "F0000000000000000000000000000000", + "2A560364CE529EFC21788779568D5555" }, + /* Test 2 (KAT ECBVarTxt192 #123) */ + { "000000000000000000000000000000000000000000000000", + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0", + "2AABB999F43693175AF65C6C612C46FB" }, + /* Test 3 (KAT ECBVarKey192 #3) */ + { "F00000000000000000000000000000000000000000000000", + "00000000000000000000000000000000", + "180B09F267C45145DB2F826C2582D35C" }, + /* Test 4 (KAT ECBVarKey192 #187) */ + { "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0", + "00000000000000000000000000000000", + "EACF1E6C4224EFB38900B185AB1DFD42" }, + /* Test 5 (KAT ECBGFSbox192 #3) */ + { "000000000000000000000000000000000000000000000000", + "51719783D3185A535BD75ADC65071CE1", + "4F354592FF7C8847D2D0870CA9481B7C" }, + /* Test 6 (KAT ECBKeySbox192 #3) */ + { "CD62376D5EBB414917F0C78F05266433DC9192A1EC943300", + "00000000000000000000000000000000", + "7F6C25FF41858561BB62F36492E93C29" }, + { NULL, NULL, NULL } + }; + + aes_testcase_t *testcase = testcases; + + UNUSED(state); + + while (testcase->key != NULL) { + len = fromhexstr(testcase->key, key); + assert_int_equal(len, ISC_AES192_KEYLENGTH); + len = fromhexstr(testcase->input, plaintext); + assert_int_equal(len, ISC_AES_BLOCK_LENGTH); + isc_aes192_crypt(key, plaintext, ciphertext); + assert_int_equal(tohexstr(ciphertext, str), ISC_R_SUCCESS); + assert_string_equal(str, testcase->result); + + testcase++; + } +} + +/* AES 256 test vectors */ +static void +isc_aes256_test(void **state) { + aes_testcase_t testcases[] = { /* Test 1 (KAT ECBVarTxt256 #3) */ + { "00000000000000000000000000000000" + "00000000000000000000000000000000", + "F0000000000000000000000000000000", + "7F2C5ECE07A98D8BEE13C51177395FF7" }, + /* Test 2 (KAT ECBVarTxt256 #123) */ + { "00000000000000000000000000000000" + "00000000000000000000000000000000", + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0", + "7240E524BC51D8C4D440B1BE55D1062C" }, + /* Test 3 (KAT ECBVarKey256 #3) */ + { "F0000000000000000000000000000000" + "00000000000000000000000000000000", + "00000000000000000000000000000000", + "1C777679D50037C79491A94DA76A9A35" }, + /* Test 4 (KAT ECBVarKey256 #251) */ + { "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0", + "00000000000000000000000000000000", + "03720371A04962EAEA0A852E69972858" }, + /* Test 5 (KAT ECBGFSbox256 #3) */ + { "00000000000000000000000000000000" + "00000000000000000000000000000000", + "8A560769D605868AD80D819BDBA03771", + "38F2C7AE10612415D27CA190D27DA8B4" }, + /* Test 6 (KAT ECBKeySbox256 #3) */ + { "984CA75F4EE8D706F46C2D98C0BF4A45" + "F5B00D791C2DFEB191B5ED8E420FD627", + "00000000000000000000000000000000", + "4307456A9E67813B452E15FA8FFFE398" }, + { NULL, NULL, NULL } + }; + + aes_testcase_t *testcase = testcases; + + UNUSED(state); + + while (testcase->key != NULL) { + len = fromhexstr(testcase->key, key); + assert_int_equal(len, ISC_AES256_KEYLENGTH); + len = fromhexstr(testcase->input, plaintext); + assert_int_equal(len, ISC_AES_BLOCK_LENGTH); + isc_aes256_crypt(key, plaintext, ciphertext); + assert_int_equal(tohexstr(ciphertext, str), ISC_R_SUCCESS); + assert_string_equal(str, testcase->result); + + testcase++; + } +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(isc_aes128_test), + cmocka_unit_test(isc_aes192_test), + cmocka_unit_test(isc_aes256_test), + }; + + 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 */ diff --git a/lib/isc/tests/buffer_test.c b/lib/isc/tests/buffer_test.c new file mode 100644 index 0000000..91eb890 --- /dev/null +++ b/lib/isc/tests/buffer_test.c @@ -0,0 +1,351 @@ +/* + * 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 <fcntl.h> +#include <limits.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/print.h> +#include <isc/region.h> +#include <isc/result.h> +#include <isc/types.h> +#include <isc/util.h> + +#include "isctest.h" + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = isc_test_begin(NULL, true, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + isc_test_end(); + + return (0); +} + +/* reserve space in dynamic buffers */ +static void +isc_buffer_reserve_test(void **state) { + isc_result_t result; + isc_buffer_t *b; + + UNUSED(state); + + b = NULL; + isc_buffer_allocate(test_mctx, &b, 1024); + assert_int_equal(b->length, 1024); + + /* + * 1024 bytes should already be available, so this call does + * nothing. + */ + result = isc_buffer_reserve(&b, 1024); + assert_int_equal(result, ISC_R_SUCCESS); + assert_true(ISC_BUFFER_VALID(b)); + assert_non_null(b); + assert_int_equal(b->length, 1024); + + /* + * This call should grow it to 2048 bytes as only 1024 bytes are + * available in the buffer. + */ + result = isc_buffer_reserve(&b, 1025); + assert_int_equal(result, ISC_R_SUCCESS); + assert_true(ISC_BUFFER_VALID(b)); + assert_non_null(b); + assert_int_equal(b->length, 2048); + + /* + * 2048 bytes should already be available, so this call does + * nothing. + */ + result = isc_buffer_reserve(&b, 2000); + assert_int_equal(result, ISC_R_SUCCESS); + assert_true(ISC_BUFFER_VALID(b)); + assert_non_null(b); + assert_int_equal(b->length, 2048); + + /* + * This call should grow it to 4096 bytes as only 2048 bytes are + * available in the buffer. + */ + result = isc_buffer_reserve(&b, 3000); + assert_int_equal(result, ISC_R_SUCCESS); + assert_true(ISC_BUFFER_VALID(b)); + assert_non_null(b); + assert_int_equal(b->length, 4096); + + /* Consume some of the buffer so we can run the next test. */ + isc_buffer_add(b, 4096); + + /* + * This call should fail and leave buffer untouched. + */ + result = isc_buffer_reserve(&b, UINT_MAX); + assert_int_equal(result, ISC_R_NOMEMORY); + assert_true(ISC_BUFFER_VALID(b)); + assert_non_null(b); + assert_int_equal(b->length, 4096); + + isc_buffer_free(&b); +} + +/* dynamic buffer automatic reallocation */ +static void +isc_buffer_dynamic_test(void **state) { + isc_buffer_t *b; + size_t last_length = 10; + int i; + + UNUSED(state); + + b = NULL; + isc_buffer_allocate(test_mctx, &b, last_length); + assert_non_null(b); + assert_int_equal(b->length, last_length); + + isc_buffer_setautorealloc(b, true); + + isc_buffer_putuint8(b, 1); + + for (i = 0; i < 1000; i++) { + isc_buffer_putstr(b, "thisisa24charslongstring"); + } + assert_true(b->length - last_length >= 1000 * 24); + last_length += 1000 * 24; + + for (i = 0; i < 10000; i++) { + isc_buffer_putuint8(b, 1); + } + + assert_true(b->length - last_length >= 10000 * 1); + last_length += 10000 * 1; + + for (i = 0; i < 10000; i++) { + isc_buffer_putuint16(b, 1); + } + + assert_true(b->length - last_length >= 10000 * 2); + + last_length += 10000 * 2; + for (i = 0; i < 10000; i++) { + isc_buffer_putuint24(b, 1); + } + assert_true(b->length - last_length >= 10000 * 3); + + last_length += 10000 * 3; + + for (i = 0; i < 10000; i++) { + isc_buffer_putuint32(b, 1); + } + assert_true(b->length - last_length >= 10000 * 4); + + isc_buffer_free(&b); +} + +/* copy a region into a buffer */ +static void +isc_buffer_copyregion_test(void **state) { + unsigned char data[] = { 0x11, 0x22, 0x33, 0x44 }; + isc_buffer_t *b = NULL; + isc_result_t result; + + isc_region_t r = { + .base = data, + .length = sizeof(data), + }; + + UNUSED(state); + + isc_buffer_allocate(test_mctx, &b, sizeof(data)); + + /* + * Fill originally allocated buffer space. + */ + result = isc_buffer_copyregion(b, &r); + assert_int_equal(result, ISC_R_SUCCESS); + + /* + * Appending more data to the buffer should fail. + */ + result = isc_buffer_copyregion(b, &r); + assert_int_equal(result, ISC_R_NOSPACE); + + /* + * Enable auto reallocation and retry. Appending should now succeed. + */ + isc_buffer_setautorealloc(b, true); + result = isc_buffer_copyregion(b, &r); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_buffer_free(&b); +} + +/* sprintf() into a buffer */ +static void +isc_buffer_printf_test(void **state) { + unsigned int used, prev_used; + const char *empty_fmt; + isc_result_t result; + isc_buffer_t *b, sb; + char buf[8]; + + UNUSED(state); + + /* + * Prepare a buffer with auto-reallocation enabled. + */ + b = NULL; + isc_buffer_allocate(test_mctx, &b, 0); + isc_buffer_setautorealloc(b, true); + + /* + * Sanity check. + */ + result = isc_buffer_printf(b, "foo"); + assert_int_equal(result, ISC_R_SUCCESS); + used = isc_buffer_usedlength(b); + assert_int_equal(used, 3); + + result = isc_buffer_printf(b, "bar"); + assert_int_equal(result, ISC_R_SUCCESS); + used = isc_buffer_usedlength(b); + assert_int_equal(used, 3 + 3); + + /* + * Also check the terminating NULL byte is there, even though it is not + * part of the buffer's used region. + */ + assert_memory_equal(isc_buffer_current(b), "foobar", 7); + + /* + * Skip over data from previous check to prevent failures in previous + * check from affecting this one. + */ + prev_used = used; + isc_buffer_forward(b, prev_used); + + /* + * Some standard usage checks. + */ + isc_buffer_printf(b, "%d", 42); + used = isc_buffer_usedlength(b); + assert_int_equal(used - prev_used, 2); + + isc_buffer_printf(b, "baz%1X", 42); + used = isc_buffer_usedlength(b); + assert_int_equal(used - prev_used, 2 + 5); + + isc_buffer_printf(b, "%6.1f", 42.42f); + used = isc_buffer_usedlength(b); + assert_int_equal(used - prev_used, 2 + 5 + 6); + + /* + * Also check the terminating NULL byte is there, even though it is not + * part of the buffer's used region. + */ + assert_memory_equal(isc_buffer_current(b), "42baz2A 42.4", 14); + + /* + * Check an empty format string is properly handled. + * + * Note: we don't use a string literal for the format string to + * avoid triggering [-Werror=format-zero-length]. + * Note: we have a dummy third argument as some compilers complain + * without it. + */ + prev_used = used; + empty_fmt = ""; + result = isc_buffer_printf(b, empty_fmt, ""); + assert_int_equal(result, ISC_R_SUCCESS); + used = isc_buffer_usedlength(b); + assert_int_equal(prev_used, used); + + isc_buffer_free(&b); + + /* + * Check overflow on a static buffer. + */ + isc_buffer_init(&sb, buf, sizeof(buf)); + result = isc_buffer_printf(&sb, "123456"); + assert_int_equal(result, ISC_R_SUCCESS); + used = isc_buffer_usedlength(&sb); + assert_int_equal(used, 6); + + result = isc_buffer_printf(&sb, "789"); + assert_int_equal(result, ISC_R_NOSPACE); + used = isc_buffer_usedlength(&sb); + assert_int_equal(used, 6); + + result = isc_buffer_printf(&sb, "78"); + assert_int_equal(result, ISC_R_NOSPACE); + used = isc_buffer_usedlength(&sb); + assert_int_equal(used, 6); + + result = isc_buffer_printf(&sb, "7"); + assert_int_equal(result, ISC_R_SUCCESS); + used = isc_buffer_usedlength(&sb); + assert_int_equal(used, 7); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(isc_buffer_reserve_test, _setup, + _teardown), + cmocka_unit_test_setup_teardown(isc_buffer_dynamic_test, _setup, + _teardown), + cmocka_unit_test_setup_teardown(isc_buffer_copyregion_test, + _setup, _teardown), + cmocka_unit_test_setup_teardown(isc_buffer_printf_test, _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 */ diff --git a/lib/isc/tests/counter_test.c b/lib/isc/tests/counter_test.c new file mode 100644 index 0000000..6a454f8 --- /dev/null +++ b/lib/isc/tests/counter_test.c @@ -0,0 +1,105 @@ +/* + * 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 <sched.h> /* IWYU pragma: keep */ +#include <setjmp.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +#define UNIT_TESTING +#include <cmocka.h> + +#include <isc/counter.h> +#include <isc/result.h> +#include <isc/util.h> + +#include "isctest.h" + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = isc_test_begin(NULL, true, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + isc_test_end(); + + return (0); +} + +/* test isc_counter object */ +static void +isc_counter_test(void **state) { + isc_result_t result; + isc_counter_t *counter = NULL; + int i; + + UNUSED(state); + + result = isc_counter_create(test_mctx, 0, &counter); + assert_int_equal(result, ISC_R_SUCCESS); + + for (i = 0; i < 10; i++) { + result = isc_counter_increment(counter); + assert_int_equal(result, ISC_R_SUCCESS); + } + + assert_int_equal(isc_counter_used(counter), 10); + + isc_counter_setlimit(counter, 15); + for (i = 0; i < 10; i++) { + result = isc_counter_increment(counter); + if (result != ISC_R_SUCCESS) { + break; + } + } + + assert_int_equal(isc_counter_used(counter), 15); + + isc_counter_detach(&counter); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(isc_counter_test, _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 */ diff --git a/lib/isc/tests/crc64_test.c b/lib/isc/tests/crc64_test.c new file mode 100644 index 0000000..1c4f4f4 --- /dev/null +++ b/lib/isc/tests/crc64_test.c @@ -0,0 +1,108 @@ +/* + * 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. + */ + +/* ! \file */ + +#if HAVE_CMOCKA + +#include <setjmp.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +#define UNIT_TESTING +#include <cmocka.h> + +#include <isc/crc64.h> +#include <isc/print.h> +#include <isc/result.h> +#include <isc/util.h> + +#define TEST_INPUT(x) (x), sizeof(x) - 1 + +typedef struct hash_testcase { + const char *input; + size_t input_len; + const char *result; + int repeats; +} hash_testcase_t; + +static void +isc_crc64_init_test(void **state) { + uint64_t crc; + + UNUSED(state); + + isc_crc64_init(&crc); + assert_int_equal(crc, 0xffffffffffffffffUL); +} + +static void +_crc64(const char *buf, size_t buflen, const char *result, const int repeats) { + uint64_t crc; + + isc_crc64_init(&crc); + assert_int_equal(crc, 0xffffffffffffffffUL); + + for (int i = 0; i < repeats; i++) { + isc_crc64_update(&crc, buf, buflen); + } + + isc_crc64_final(&crc); + + char hex[16 + 1]; + snprintf(hex, sizeof(hex), "%016" PRIX64, crc); + + assert_memory_equal(hex, result, (result ? strlen(result) : 0)); +} + +/* 64-bit cyclic redundancy check */ +static void +isc_crc64_test(void **state) { + UNUSED(state); + + _crc64(TEST_INPUT(""), "0000000000000000", 1); + _crc64(TEST_INPUT("a"), "CE73F427ACC0A99A", 1); + _crc64(TEST_INPUT("abc"), "048B813AF9F49702", 1); + _crc64(TEST_INPUT("message digest"), "5273F9EA7A357BF4", 1); + _crc64(TEST_INPUT("abcdefghijklmnopqrstuvwxyz"), "59F079F9218BAAA1", 1); + _crc64(TEST_INPUT("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklm" + "nopqrstuvwxyz0123456789"), + "A36DA8F71E78B6FB", 1); + _crc64(TEST_INPUT("123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890"), + "81E5EB73C8E7874A", 1); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(isc_crc64_init_test), + cmocka_unit_test(isc_crc64_test), + }; + + 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 */ diff --git a/lib/isc/tests/errno_test.c b/lib/isc/tests/errno_test.c new file mode 100644 index 0000000..56da651 --- /dev/null +++ b/lib/isc/tests/errno_test.c @@ -0,0 +1,123 @@ +/* + * 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 <errno.h> +#include <setjmp.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +#define UNIT_TESTING +#include <cmocka.h> + +#include <isc/errno.h> +#include <isc/result.h> +#include <isc/util.h> + +typedef struct { + int err; + isc_result_t result; +} testpair_t; + +testpair_t testpair[] = { { EPERM, ISC_R_NOPERM }, + { ENOENT, ISC_R_FILENOTFOUND }, + { EIO, ISC_R_IOERROR }, + { EBADF, ISC_R_INVALIDFILE }, + { ENOMEM, ISC_R_NOMEMORY }, + { EACCES, ISC_R_NOPERM }, + { EEXIST, ISC_R_FILEEXISTS }, + { ENOTDIR, ISC_R_INVALIDFILE }, + { EINVAL, ISC_R_INVALIDFILE }, + { ENFILE, ISC_R_TOOMANYOPENFILES }, + { EMFILE, ISC_R_TOOMANYOPENFILES }, + { EPIPE, ISC_R_CONNECTIONRESET }, + { ENAMETOOLONG, ISC_R_INVALIDFILE }, + { ELOOP, ISC_R_INVALIDFILE }, +#ifdef EOVERFLOW + { EOVERFLOW, ISC_R_RANGE }, +#endif /* ifdef EOVERFLOW */ +#ifdef EAFNOSUPPORT + { EAFNOSUPPORT, ISC_R_FAMILYNOSUPPORT }, +#endif /* ifdef EAFNOSUPPORT */ +#ifdef EADDRINUSE + { EADDRINUSE, ISC_R_ADDRINUSE }, +#endif /* ifdef EADDRINUSE */ + { EADDRNOTAVAIL, ISC_R_ADDRNOTAVAIL }, +#ifdef ENETDOWN + { ENETDOWN, ISC_R_NETDOWN }, +#endif /* ifdef ENETDOWN */ +#ifdef ENETUNREACH + { ENETUNREACH, ISC_R_NETUNREACH }, +#endif /* ifdef ENETUNREACH */ +#ifdef ECONNABORTED + { ECONNABORTED, ISC_R_CONNECTIONRESET }, +#endif /* ifdef ECONNABORTED */ +#ifdef ECONNRESET + { ECONNRESET, ISC_R_CONNECTIONRESET }, +#endif /* ifdef ECONNRESET */ +#ifdef ENOBUFS + { ENOBUFS, ISC_R_NORESOURCES }, +#endif /* ifdef ENOBUFS */ +#ifdef ENOTCONN + { ENOTCONN, ISC_R_NOTCONNECTED }, +#endif /* ifdef ENOTCONN */ +#ifdef ETIMEDOUT + { ETIMEDOUT, ISC_R_TIMEDOUT }, +#endif /* ifdef ETIMEDOUT */ + { ECONNREFUSED, ISC_R_CONNREFUSED }, +#ifdef EHOSTDOWN + { EHOSTDOWN, ISC_R_HOSTDOWN }, +#endif /* ifdef EHOSTDOWN */ +#ifdef EHOSTUNREACH + { EHOSTUNREACH, ISC_R_HOSTUNREACH }, +#endif /* ifdef EHOSTUNREACH */ + { 0, ISC_R_UNEXPECTED } }; + +/* convert errno to ISC result */ +static void +isc_errno_toresult_test(void **state) { + isc_result_t result, expect; + size_t i; + + UNUSED(state); + + for (i = 0; i < sizeof(testpair) / sizeof(testpair[0]); i++) { + result = isc_errno_toresult(testpair[i].err); + expect = testpair[i].result; + assert_int_equal(result, expect); + } +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(isc_errno_toresult_test), + }; + + 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 */ diff --git a/lib/isc/tests/file_test.c b/lib/isc/tests/file_test.c new file mode 100644 index 0000000..6d81ff9 --- /dev/null +++ b/lib/isc/tests/file_test.c @@ -0,0 +1,154 @@ +/* + * 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 <fcntl.h> +#include <setjmp.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define UNIT_TESTING +#include <cmocka.h> + +#include <isc/file.h> +#include <isc/result.h> +#include <isc/util.h> + +#define NAME "internal" +#define SHA "3bed2cb3a3acf7b6a8ef408420cc682d5520e26976d354254f528c965612054f" +#define TRUNC_SHA "3bed2cb3a3acf7b6" + +#define BAD1 "in/internal" +#define BADHASH1 "8bbb97a888791399" + +#define BAD2 "Internal" +#define BADHASH2 "2ea1842b445b0c81" + +#define F(x) "testdata/file/" x ".test" + +static void +touch(const char *filename) { + int fd; + + unlink(filename); + fd = creat(filename, 0644); + if (fd != -1) { + close(fd); + } +} + +/* test sanitized filenames */ +static void +isc_file_sanitize_test(void **state) { + isc_result_t result; + char buf[1024]; + + UNUSED(state); + + assert_return_code(chdir(TESTS), 0); + + result = isc_file_sanitize("testdata/file", NAME, "test", buf, 1024); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(strcmp(buf, F(NAME)), 0); + + touch(F(TRUNC_SHA)); + result = isc_file_sanitize("testdata/file", NAME, "test", buf, 1024); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(strcmp(buf, F(TRUNC_SHA)), 0); + + touch(F(SHA)); + result = isc_file_sanitize("testdata/file", NAME, "test", buf, 1024); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(strcmp(buf, F(SHA)), 0); + + result = isc_file_sanitize("testdata/file", BAD1, "test", buf, 1024); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(strcmp(buf, F(BADHASH1)), 0); + + result = isc_file_sanitize("testdata/file", BAD2, "test", buf, 1024); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(strcmp(buf, F(BADHASH2)), 0); + + unlink(F(TRUNC_SHA)); + unlink(F(SHA)); +} + +/* test filename templates */ +static void +isc_file_template_test(void **state) { + isc_result_t result; + char buf[1024]; + + UNUSED(state); + + assert_return_code(chdir(TESTS), 0); + + result = isc_file_template("/absolute/path", "file-XXXXXXXX", buf, + sizeof(buf)); + assert_int_equal(result, ISC_R_SUCCESS); + assert_string_equal(buf, "/absolute/file-XXXXXXXX"); + + result = isc_file_template("relative/path", "file-XXXXXXXX", buf, + sizeof(buf)); + assert_int_equal(result, ISC_R_SUCCESS); + assert_string_equal(buf, "relative/file-XXXXXXXX"); + + result = isc_file_template("/trailing/slash/", "file-XXXXXXXX", buf, + sizeof(buf)); + assert_int_equal(result, ISC_R_SUCCESS); + assert_string_equal(buf, "/trailing/slash/file-XXXXXXXX"); + + result = isc_file_template("relative/trailing/slash/", "file-XXXXXXXX", + buf, sizeof(buf)); + assert_int_equal(result, ISC_R_SUCCESS); + assert_string_equal(buf, "relative/trailing/slash/file-XXXXXXXX"); + + result = isc_file_template("/", "file-XXXXXXXX", buf, sizeof(buf)); + assert_int_equal(result, ISC_R_SUCCESS); + assert_string_equal(buf, "/file-XXXXXXXX"); + + result = isc_file_template("noslash", "file-XXXXXXXX", buf, + sizeof(buf)); + assert_int_equal(result, ISC_R_SUCCESS); + assert_string_equal(buf, "file-XXXXXXXX"); + + result = isc_file_template(NULL, "file-XXXXXXXX", buf, sizeof(buf)); + assert_int_equal(result, ISC_R_SUCCESS); + assert_string_equal(buf, "file-XXXXXXXX"); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(isc_file_sanitize_test), + cmocka_unit_test(isc_file_template_test), + }; + + 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 */ diff --git a/lib/isc/tests/hash_test.c b/lib/isc/tests/hash_test.c new file mode 100644 index 0000000..ad0eb9d --- /dev/null +++ b/lib/isc/tests/hash_test.c @@ -0,0 +1,114 @@ +/* + * 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 <setjmp.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define UNIT_TESTING +#include <cmocka.h> + +#include <isc/buffer.h> +#include <isc/hash.h> +#include <isc/hex.h> +#include <isc/print.h> +#include <isc/region.h> +#include <isc/string.h> +#include <isc/util.h> + +#include <pk11/site.h> + +#define TEST_INPUT(x) (x), sizeof(x) - 1 + +/*Hash function test */ +static void +isc_hash_function_test(void **state) { + unsigned int h1; + unsigned int h2; + + UNUSED(state); + + /* Immutability of hash function */ + h1 = isc_hash_function(NULL, 0, true); + h2 = isc_hash_function(NULL, 0, true); + + assert_int_equal(h1, h2); + + /* Hash function characteristics */ + h1 = isc_hash_function("Hello world", 12, true); + h2 = isc_hash_function("Hello world", 12, true); + + assert_int_equal(h1, h2); + + /* Case */ + h1 = isc_hash_function("Hello world", 12, false); + h2 = isc_hash_function("heLLo WorLd", 12, false); + + assert_int_equal(h1, h2); + + /* Unequal */ + h1 = isc_hash_function("Hello world", 12, true); + h2 = isc_hash_function("heLLo WorLd", 12, true); + + assert_int_not_equal(h1, h2); +} + +/* Hash function initializer test */ +static void +isc_hash_initializer_test(void **state) { + unsigned int h1; + unsigned int h2; + + UNUSED(state); + + h1 = isc_hash_function("Hello world", 12, true); + h2 = isc_hash_function("Hello world", 12, true); + + assert_int_equal(h1, h2); + + isc_hash_set_initializer(isc_hash_get_initializer()); + + /* Hash value must not change */ + h2 = isc_hash_function("Hello world", 12, true); + + assert_int_equal(h1, h2); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(isc_hash_function_test), + cmocka_unit_test(isc_hash_initializer_test), + }; + + 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 */ diff --git a/lib/isc/tests/heap_test.c b/lib/isc/tests/heap_test.c new file mode 100644 index 0000000..5c41b20 --- /dev/null +++ b/lib/isc/tests/heap_test.c @@ -0,0 +1,115 @@ +/* + * 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. + */ + +/* ! \file */ + +#if HAVE_CMOCKA + +#include <sched.h> /* IWYU pragma: keep */ +#include <setjmp.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +#define UNIT_TESTING +#include <cmocka.h> + +#include <isc/heap.h> +#include <isc/mem.h> +#include <isc/util.h> + +#include "isctest.h" + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = isc_test_begin(NULL, true, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + isc_test_end(); + + return (0); +} + +struct e { + unsigned int value; + unsigned int index; +}; + +static bool +compare(void *p1, void *p2) { + struct e *e1 = p1; + struct e *e2 = p2; + + return (e1->value < e2->value); +} + +static void +idx(void *p, unsigned int i) { + struct e *e = p; + + e->index = i; +} + +/* test isc_heap_delete() */ +static void +isc_heap_delete_test(void **state) { + isc_heap_t *heap = NULL; + struct e e1 = { 100, 0 }; + + UNUSED(state); + + isc_heap_create(test_mctx, compare, idx, 0, &heap); + assert_non_null(heap); + + isc_heap_insert(heap, &e1); + assert_int_equal(e1.index, 1); + + isc_heap_delete(heap, e1.index); + assert_int_equal(e1.index, 0); + + isc_heap_destroy(&heap); + assert_null(heap); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(isc_heap_delete_test), + }; + + return (cmocka_run_group_tests(tests, _setup, _teardown)); +} + +#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 */ diff --git a/lib/isc/tests/hmac_test.c b/lib/isc/tests/hmac_test.c new file mode 100644 index 0000000..74c8106 --- /dev/null +++ b/lib/isc/tests/hmac_test.c @@ -0,0 +1,949 @@ +/* + * 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. + */ + +/* ! \file */ + +#if HAVE_CMOCKA + +#include <sched.h> /* IWYU pragma: keep */ +#include <setjmp.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +#define UNIT_TESTING +#include <cmocka.h> + +#include <isc/buffer.h> +#include <isc/hex.h> +#include <isc/hmac.h> +#include <isc/region.h> +#include <isc/result.h> + +#include "../hmac.c" + +#define TEST_INPUT(x) (x), sizeof(x) - 1 + +static int +_setup(void **state) { + isc_hmac_t *hmac = isc_hmac_new(); + if (hmac == NULL) { + return (-1); + } + *state = hmac; + return (0); +} + +static int +_teardown(void **state) { + if (*state == NULL) { + return (-1); + } + isc_hmac_free(*state); + return (0); +} + +static int +_reset(void **state) { + if (*state == NULL) { + return (-1); + } + if (isc_hmac_reset(*state) != ISC_R_SUCCESS) { + return (-1); + } + return (0); +} + +static void +isc_hmac_new_test(void **state) { + UNUSED(state); + + isc_hmac_t *hmac = isc_hmac_new(); + assert_non_null(hmac); + isc_hmac_free(hmac); /* Cleanup */ +} + +static void +isc_hmac_free_test(void **state) { + UNUSED(state); + + isc_hmac_t *hmac = isc_hmac_new(); + assert_non_null(hmac); + isc_hmac_free(hmac); /* Test freeing valid message digest context */ + isc_hmac_free(NULL); /* Test freeing NULL argument */ +} + +static void +isc_hmac_test(isc_hmac_t *hmac, const void *key, size_t keylen, + const isc_md_type_t *type, const char *buf, size_t buflen, + const char *result, const int repeats) { + assert_non_null(hmac); + assert_int_equal(isc_hmac_init(hmac, key, keylen, type), ISC_R_SUCCESS); + + int i; + + for (i = 0; i < repeats; i++) { + assert_int_equal(isc_hmac_update(hmac, + (const unsigned char *)buf, + buflen), + ISC_R_SUCCESS); + } + + unsigned char digest[ISC_MAX_MD_SIZE]; + unsigned int digestlen; + assert_int_equal(isc_hmac_final(hmac, digest, &digestlen), + ISC_R_SUCCESS); + + char hexdigest[ISC_MAX_MD_SIZE * 2 + 3]; + isc_region_t r = { .base = digest, .length = digestlen }; + isc_buffer_t b; + isc_buffer_init(&b, hexdigest, sizeof(hexdigest)); + + assert_return_code(isc_hex_totext(&r, 0, "", &b), ISC_R_SUCCESS); + + assert_memory_equal(hexdigest, result, (result ? strlen(result) : 0)); + assert_int_equal(isc_hmac_reset(hmac), ISC_R_SUCCESS); +} + +static void +isc_hmac_init_test(void **state) { + isc_hmac_t *hmac = *state; + assert_non_null(hmac); + + expect_assert_failure(isc_hmac_init(NULL, "", 0, ISC_MD_MD5)); + + assert_int_equal(isc_hmac_init(hmac, "", 0, NULL), + ISC_R_NOTIMPLEMENTED); + + expect_assert_failure(isc_hmac_init(hmac, NULL, 0, ISC_MD_MD5)); + + assert_int_equal(isc_hmac_init(hmac, "", 0, ISC_MD_MD5), ISC_R_SUCCESS); + assert_int_equal(isc_hmac_reset(hmac), ISC_R_SUCCESS); + + assert_int_equal(isc_hmac_init(hmac, "", 0, ISC_MD_SHA1), + ISC_R_SUCCESS); + assert_int_equal(isc_hmac_reset(hmac), ISC_R_SUCCESS); + + assert_int_equal(isc_hmac_init(hmac, "", 0, ISC_MD_SHA224), + ISC_R_SUCCESS); + assert_int_equal(isc_hmac_reset(hmac), ISC_R_SUCCESS); + + assert_int_equal(isc_hmac_init(hmac, "", 0, ISC_MD_SHA256), + ISC_R_SUCCESS); + assert_int_equal(isc_hmac_reset(hmac), ISC_R_SUCCESS); + + assert_int_equal(isc_hmac_init(hmac, "", 0, ISC_MD_SHA384), + ISC_R_SUCCESS); + assert_int_equal(isc_hmac_reset(hmac), ISC_R_SUCCESS); + + assert_int_equal(isc_hmac_init(hmac, "", 0, ISC_MD_SHA512), + ISC_R_SUCCESS); + assert_int_equal(isc_hmac_reset(hmac), ISC_R_SUCCESS); +} + +static void +isc_hmac_update_test(void **state) { + isc_hmac_t *hmac = *state; + assert_non_null(hmac); + + /* Uses message digest context initialized in isc_hmac_init_test() */ + expect_assert_failure(isc_hmac_update(NULL, NULL, 0)); + + assert_int_equal(isc_hmac_update(hmac, NULL, 100), ISC_R_SUCCESS); + assert_int_equal(isc_hmac_update(hmac, (const unsigned char *)"", 0), + ISC_R_SUCCESS); +} + +static void +isc_hmac_reset_test(void **state) { + isc_hmac_t *hmac = *state; +#if 0 + unsigned char digest[ISC_MAX_MD_SIZE] __attribute((unused)); + unsigned int digestlen __attribute((unused)); +#endif /* if 0 */ + + assert_non_null(hmac); + + assert_int_equal(isc_hmac_init(hmac, "", 0, ISC_MD_SHA512), + ISC_R_SUCCESS); + assert_int_equal(isc_hmac_update(hmac, (const unsigned char *)"a", 1), + ISC_R_SUCCESS); + assert_int_equal(isc_hmac_update(hmac, (const unsigned char *)"b", 1), + ISC_R_SUCCESS); + + assert_int_equal(isc_hmac_reset(hmac), ISC_R_SUCCESS); + +#if 0 + /* + * This test would require OpenSSL compiled with mock_assert(), + * so this could be only manually checked that the test will + * segfault when called by hand + */ + expect_assert_failure(isc_hmac_final(hmac,digest,&digestlen)); +#endif /* if 0 */ +} + +static void +isc_hmac_final_test(void **state) { + isc_hmac_t *hmac = *state; + assert_non_null(hmac); + + unsigned char digest[ISC_MAX_MD_SIZE]; + unsigned int digestlen; + + /* Fail when message digest context is empty */ + expect_assert_failure(isc_hmac_final(NULL, digest, &digestlen)); + /* Fail when output buffer is empty */ + expect_assert_failure(isc_hmac_final(hmac, NULL, &digestlen)); + + assert_int_equal(isc_hmac_init(hmac, "", 0, ISC_MD_SHA512), + ISC_R_SUCCESS); + assert_int_equal(isc_hmac_final(hmac, digest, NULL), ISC_R_SUCCESS); +} + +static void +isc_hmac_md5_test(void **state) { + isc_hmac_t *hmac = *state; + + /* Test 0 */ + isc_hmac_test(hmac, TEST_INPUT(""), ISC_MD_MD5, TEST_INPUT(""), + "74E6F7298A9C2D168935F58C001BAD88", 1); + + /* Test 1 */ + isc_hmac_test(hmac, + TEST_INPUT("\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b" + "\x0b\x0b\x0b\x0b\x0b\x0b"), + ISC_MD_MD5, + TEST_INPUT("\x48\x69\x20\x54\x68\x65\x72\x65"), + "9294727A3638BB1C13F48EF8158BFC9D", 1); + + /* Test 2 */ + isc_hmac_test(hmac, TEST_INPUT("Jefe"), ISC_MD_MD5, + TEST_INPUT("\x77\x68\x61\x74\x20\x64\x6f\x20\x79" + "\x61\x20\x77\x61\x6e\x74\x20\x66\x6f" + "\x72\x20\x6e\x6f\x74\x68\x69\x6e\x67\x3f"), + "750C783E6AB0B503EAA86E310A5DB738", 1); + + /* Test 3 */ + isc_hmac_test(hmac, + TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa"), + ISC_MD_MD5, + TEST_INPUT("\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD" + "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD" + "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD" + "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD" + "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"), + "56BE34521D144C88DBB8C733F0E8B3F6", 1); + /* Test 4 */ + isc_hmac_test(hmac, + TEST_INPUT("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a" + "\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14" + "\x15\x16\x17\x18\x19"), + ISC_MD_MD5, + TEST_INPUT("\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"), + "697EAF0ACA3A3AEA3A75164746FFAA79", 1); +#if 0 + /* Test 5 -- unimplemented optional functionality */ + isc_hmac_test(hmac, + TEST_INPUT("\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c" + "\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c"), + ISC_MD_MD5, + TEST_INPUT("Test With Truncation"), + "4C1A03424B55E07FE7F27BE1", + 1); + /* Test 6 -- unimplemented optional functionality */ + isc_hmac_test(hmac, + TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"), + ISC_MD_MD5, + TEST_INPUT("Test Using Larger Than Block-Size Key - " + "Hash Key First"), + "AA4AE5E15272D00E95705637CE8A3B55ED402112", + 1); + /* Test 7 -- unimplemented optional functionality */ + isc_hmac_test(hmac, + TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"), + ISC_MD_MD5, + TEST_INPUT("Test Using Larger Than Block-Size Key and " + "Larger Than One Block-Size Data"), + "E8E99D0F45237D786D6BBAA7965C7808BBFF1A91", + 1); +#endif /* if 0 */ +} + +static void +isc_hmac_sha1_test(void **state) { + isc_hmac_t *hmac = *state; + + /* Test 0 */ + isc_hmac_test(hmac, TEST_INPUT(""), ISC_MD_SHA1, TEST_INPUT(""), + "FBDB1D1B18AA6C08324B7D64B71FB76370690E1D", 1); + + /* Test 1 */ + isc_hmac_test(hmac, + TEST_INPUT("\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b" + "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"), + ISC_MD_SHA1, + TEST_INPUT("\x48\x69\x20\x54\x68\x65\x72\x65"), + "B617318655057264E28BC0B6FB378C8EF146BE00", 1); + /* Test 2 */ + isc_hmac_test(hmac, TEST_INPUT("Jefe"), ISC_MD_SHA1, + TEST_INPUT("\x77\x68\x61\x74\x20\x64\x6f\x20\x79\x61" + "\x20\x77\x61\x6e\x74\x20\x66\x6f\x72\x20" + "\x6e\x6f\x74\x68\x69\x6e\x67\x3f"), + "EFFCDF6AE5EB2FA2D27416D5F184DF9C259A7C79", 1); + /* Test 3 */ + isc_hmac_test(hmac, + TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"), + ISC_MD_SHA1, + TEST_INPUT("\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD" + "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD" + "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD" + "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD" + "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"), + "125D7342B9AC11CD91A39AF48AA17B4F63F175D3", 1); + /* Test 4 */ + isc_hmac_test(hmac, + TEST_INPUT("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a" + "\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14" + "\x15\x16\x17\x18\x19"), + ISC_MD_SHA1, + TEST_INPUT("\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"), + "4C9007F4026250C6BC8414F9BF50C86C2D7235DA", 1); +#if 0 + /* Test 5 */ + isc_hmac_test(hmac, + TEST_INPUT("\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c" + "\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c"), + ISC_MD_SHA1, + TEST_INPUT("Test With Truncation"), + "4C1A03424B55E07FE7F27BE1", + 1); +#endif /* if 0 */ + /* Test 6 */ + isc_hmac_test(hmac, + TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"), + ISC_MD_SHA1, + TEST_INPUT("Test Using Larger Than Block-Size Key - " + "Hash Key First"), + "AA4AE5E15272D00E95705637CE8A3B55ED402112", 1); + /* Test 7 */ + isc_hmac_test(hmac, + TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"), + ISC_MD_SHA1, + TEST_INPUT("Test Using Larger Than Block-Size Key and " + "Larger Than One Block-Size Data"), + "E8E99D0F45237D786D6BBAA7965C7808BBFF1A91", 1); +} + +static void +isc_hmac_sha224_test(void **state) { + isc_hmac_t *hmac = *state; + + /* Test 0 */ + isc_hmac_test(hmac, TEST_INPUT(""), ISC_MD_SHA224, TEST_INPUT(""), + "5CE14F72894662213E2748D2A6BA234B74263910CEDDE2F5" + "A9271524", + 1); + + /* Test 1 */ + isc_hmac_test(hmac, + TEST_INPUT("\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b" + "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"), + ISC_MD_SHA224, + TEST_INPUT("\x48\x69\x20\x54\x68\x65\x72\x65"), + "896FB1128ABBDF196832107CD49DF33F47B4B1169912BA" + "4F53684B22", + 1); + /* Test 2 */ + isc_hmac_test(hmac, TEST_INPUT("Jefe"), ISC_MD_SHA224, + TEST_INPUT("\x77\x68\x61\x74\x20\x64\x6f\x20\x79\x61" + "\x20\x77\x61\x6e\x74\x20\x66\x6f\x72\x20" + "\x6e\x6f\x74\x68\x69\x6e\x67\x3f"), + "A30E01098BC6DBBF45690F3A7E9E6D0F8BBEA2A39E61480" + "08FD05E44", + 1); + /* Test 3 */ + isc_hmac_test(hmac, + TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"), + ISC_MD_SHA224, + TEST_INPUT("\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD" + "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD" + "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD" + "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD" + "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"), + "7FB3CB3588C6C1F6FFA9694D7D6AD2649365B0C1F65D69" + "D1EC8333EA", + 1); + /* Test 4 */ + isc_hmac_test(hmac, + TEST_INPUT("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a" + "\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14" + "\x15\x16\x17\x18\x19"), + ISC_MD_SHA224, + TEST_INPUT("\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"), + "6C11506874013CAC6A2ABC1BB382627CEC6A90D86EFC01" + "2DE7AFEC5A", + 1); +#if 0 + /* Test 5 -- unimplemented optional functionality */ + isc_hmac_test(hmac, + TEST_INPUT("\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c" + "\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c"), + ISC_MD_SHA224, + TEST_INPUT("Test With Truncation"), + "4C1A03424B55E07FE7F27BE1", + 1); +#endif /* if 0 */ + /* Test 6 */ + isc_hmac_test(hmac, + TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa"), + ISC_MD_SHA224, + TEST_INPUT("Test Using Larger Than Block-Size Key - " + "Hash Key First"), + "95E9A0DB962095ADAEBE9B2D6F0DBCE2D499F112F2D2B7" + "273FA6870E", + 1); + /* Test 7 */ + isc_hmac_test(hmac, + TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa"), + ISC_MD_SHA224, + TEST_INPUT("\x54\x68\x69\x73\x20\x69\x73\x20\x61\x20" + "\x74\x65\x73\x74\x20\x75\x73\x69\x6e\x67" + "\x20\x61\x20\x6c\x61\x72\x67\x65\x72\x20" + "\x74\x68\x61\x6e\x20\x62\x6c\x6f\x63\x6b" + "\x2d\x73\x69\x7a\x65\x20\x6b\x65\x79\x20" + "\x61\x6e\x64\x20\x61\x20\x6c\x61\x72\x67" + "\x65\x72\x20\x74\x68\x61\x6e\x20\x62\x6c" + "\x6f\x63\x6b\x2d\x73\x69\x7a\x65\x20\x64" + "\x61\x74\x61\x2e\x20\x54\x68\x65\x20\x6b" + "\x65\x79\x20\x6e\x65\x65\x64\x73\x20\x74" + "\x6f\x20\x62\x65\x20\x68\x61\x73\x68\x65" + "\x64\x20\x62\x65\x66\x6f\x72\x65\x20\x62" + "\x65\x69\x6e\x67\x20\x75\x73\x65\x64\x20" + "\x62\x79\x20\x74\x68\x65\x20\x48\x4d\x41" + "\x43\x20\x61\x6c\x67\x6f\x72\x69\x74\x68" + "\x6d\x2e"), + "3A854166AC5D9F023F54D517D0B39DBD946770DB9C2B95" + "C9F6F565D1", + 1); +} + +static void +isc_hmac_sha256_test(void **state) { + isc_hmac_t *hmac = *state; + + /* Test 0 */ + isc_hmac_test(hmac, TEST_INPUT(""), ISC_MD_SHA256, TEST_INPUT(""), + "B613679A0814D9EC772F95D778C35FC5FF1697C493715653" + "C6C712144292C5AD", + 1); + + /* Test 1 */ + isc_hmac_test(hmac, + TEST_INPUT("\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b" + "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"), + ISC_MD_SHA256, + TEST_INPUT("\x48\x69\x20\x54\x68\x65\x72\x65"), + "B0344C61D8DB38535CA8AFCEAF0BF12B881DC200C9833D" + "A726E9376C2E32CFF7", + 1); + /* Test 2 */ + isc_hmac_test(hmac, TEST_INPUT("Jefe"), ISC_MD_SHA256, + TEST_INPUT("\x77\x68\x61\x74\x20\x64\x6f\x20\x79\x61" + "\x20\x77\x61\x6e\x74\x20\x66\x6f\x72\x20" + "\x6e\x6f\x74\x68\x69\x6e\x67\x3f"), + "5BDCC146BF60754E6A042426089575C75A003F089D2739" + "839DEC58B964EC3843", + 1); + /* Test 3 */ + isc_hmac_test(hmac, + TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"), + ISC_MD_SHA256, + TEST_INPUT("\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD" + "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD" + "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD" + "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD" + "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"), + "773EA91E36800E46854DB8EBD09181A72959098B3EF8C1" + "22D9635514CED565FE", + 1); + /* Test 4 */ + isc_hmac_test(hmac, + TEST_INPUT("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a" + "\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14" + "\x15\x16\x17\x18\x19"), + ISC_MD_SHA256, + TEST_INPUT("\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"), + "82558A389A443C0EA4CC819899F2083A85F0FAA3E578F8" + "077A2E3FF46729665B", + 1); +#if 0 + /* Test 5 -- unimplemented optional functionality */ + isc_hmac_test(hmac, + TEST_INPUT("\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c" + "\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c"), + ISC_MD_SHA256, + TEST_INPUT("Test With Truncation"), + "4C1A03424B55E07FE7F27BE1", + 1); +#endif /* if 0 */ + /* Test 6 */ + isc_hmac_test(hmac, + TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa"), + ISC_MD_SHA256, + TEST_INPUT("Test Using Larger Than Block-Size Key - " + "Hash Key First"), + "60E431591EE0B67F0D8A26AACBF5B77F8E0BC6213728C5" + "140546040F0EE37F54", + 1); + /* Test 7 */ + isc_hmac_test(hmac, + TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa"), + ISC_MD_SHA256, + TEST_INPUT("\x54\x68\x69\x73\x20\x69\x73\x20\x61\x20" + "\x74\x65\x73\x74\x20\x75\x73\x69\x6e\x67" + "\x20\x61\x20\x6c\x61\x72\x67\x65\x72\x20" + "\x74\x68\x61\x6e\x20\x62\x6c\x6f\x63\x6b" + "\x2d\x73\x69\x7a\x65\x20\x6b\x65\x79\x20" + "\x61\x6e\x64\x20\x61\x20\x6c\x61\x72\x67" + "\x65\x72\x20\x74\x68\x61\x6e\x20\x62\x6c" + "\x6f\x63\x6b\x2d\x73\x69\x7a\x65\x20\x64" + "\x61\x74\x61\x2e\x20\x54\x68\x65\x20\x6b" + "\x65\x79\x20\x6e\x65\x65\x64\x73\x20\x74" + "\x6f\x20\x62\x65\x20\x68\x61\x73\x68\x65" + "\x64\x20\x62\x65\x66\x6f\x72\x65\x20\x62" + "\x65\x69\x6e\x67\x20\x75\x73\x65\x64\x20" + "\x62\x79\x20\x74\x68\x65\x20\x48\x4d\x41" + "\x43\x20\x61\x6c\x67\x6f\x72\x69\x74\x68" + "\x6d\x2e"), + "9B09FFA71B942FCB27635FBCD5B0E944BFDC63644F0713" + "938A7F51535C3A35E2", + 1); +} + +static void +isc_hmac_sha384_test(void **state) { + isc_hmac_t *hmac = *state; + + /* Test 0 */ + isc_hmac_test(hmac, TEST_INPUT(""), ISC_MD_SHA384, TEST_INPUT(""), + "6C1F2EE938FAD2E24BD91298474382CA218C75DB3D83E114" + "B3D4367776D14D3551289E75E8209CD4B792302840234ADC", + 1); + + /* Test 1 */ + isc_hmac_test(hmac, + TEST_INPUT("\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b" + "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"), + ISC_MD_SHA384, + TEST_INPUT("\x48\x69\x20\x54\x68\x65\x72\x65"), + "AFD03944D84895626B0825F4AB46907F15F9DADBE4101E" + "C682AA034C7CEBC59CFAEA9EA9076EDE7F4AF152" + "E8B2FA9CB6", + 1); + /* Test 2 */ + isc_hmac_test(hmac, TEST_INPUT("Jefe"), ISC_MD_SHA384, + TEST_INPUT("\x77\x68\x61\x74\x20\x64\x6f\x20\x79\x61" + "\x20\x77\x61\x6e\x74\x20\x66\x6f\x72\x20" + "\x6e\x6f\x74\x68\x69\x6e\x67\x3f"), + "AF45D2E376484031617F78D2B58A6B1B9C7EF464F5A01B" + "47E42EC3736322445E8E2240CA5E69E2C78B3239" + "ECFAB21649", + 1); + /* Test 3 */ + isc_hmac_test(hmac, + TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"), + ISC_MD_SHA384, + TEST_INPUT("\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD" + "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD" + "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD" + "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD" + "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"), + "88062608D3E6AD8A0AA2ACE014C8A86F0AA635D947AC9F" + "EBE83EF4E55966144B2A5AB39DC13814B94E3AB6" + "E101A34F27", + 1); + /* Test 4 */ + isc_hmac_test(hmac, + TEST_INPUT("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a" + "\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14" + "\x15\x16\x17\x18\x19"), + ISC_MD_SHA384, + TEST_INPUT("\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"), + "3E8A69B7783C25851933AB6290AF6CA77A998148085000" + "9CC5577C6E1F573B4E6801DD23C4A7D679CCF8A3" + "86C674CFFB", + 1); +#if 0 + /* Test 5 -- unimplemented optional functionality */ + isc_hmac_test(hmac, + TEST_INPUT("\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c" + "\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c"), + ISC_MD_SHA384, + TEST_INPUT("Test With Truncation"), + "4C1A03424B55E07FE7F27BE1", + 1); +#endif /* if 0 */ + /* Test 6 */ + isc_hmac_test(hmac, + TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa"), + ISC_MD_SHA384, + TEST_INPUT("Test Using Larger Than Block-Size Key - " + "Hash Key First"), + "4ECE084485813E9088D2C63A041BC5B44F9EF1012A2B58" + "8F3CD11F05033AC4C60C2EF6AB4030FE8296248D" + "F163F44952", + 1); + /* Test 7 */ + isc_hmac_test(hmac, + TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa"), + ISC_MD_SHA384, + TEST_INPUT("\x54\x68\x69\x73\x20\x69\x73\x20\x61\x20" + "\x74\x65\x73\x74\x20\x75\x73\x69\x6e\x67" + "\x20\x61\x20\x6c\x61\x72\x67\x65\x72\x20" + "\x74\x68\x61\x6e\x20\x62\x6c\x6f\x63\x6b" + "\x2d\x73\x69\x7a\x65\x20\x6b\x65\x79\x20" + "\x61\x6e\x64\x20\x61\x20\x6c\x61\x72\x67" + "\x65\x72\x20\x74\x68\x61\x6e\x20\x62\x6c" + "\x6f\x63\x6b\x2d\x73\x69\x7a\x65\x20\x64" + "\x61\x74\x61\x2e\x20\x54\x68\x65\x20\x6b" + "\x65\x79\x20\x6e\x65\x65\x64\x73\x20\x74" + "\x6f\x20\x62\x65\x20\x68\x61\x73\x68\x65" + "\x64\x20\x62\x65\x66\x6f\x72\x65\x20\x62" + "\x65\x69\x6e\x67\x20\x75\x73\x65\x64\x20" + "\x62\x79\x20\x74\x68\x65\x20\x48\x4d\x41" + "\x43\x20\x61\x6c\x67\x6f\x72\x69\x74\x68" + "\x6d\x2e"), + "6617178E941F020D351E2F254E8FD32C602420FEB0B8FB" + "9ADCCEBB82461E99C5A678CC31E799176D3860E6" + "110C46523E", + 1); +} + +static void +isc_hmac_sha512_test(void **state) { + isc_hmac_t *hmac = *state; + + /* Test 0 */ + isc_hmac_test(hmac, TEST_INPUT(""), ISC_MD_SHA512, TEST_INPUT(""), + "B936CEE86C9F87AA5D3C6F2E84CB5A4239A5FE50480A6EC6" + "6B70AB5B1F4AC6730C6C515421B327EC1D69402E53DFB49A" + "D7381EB067B338FD7B0CB22247225D47", + 1); + + /* Test 1 */ + isc_hmac_test(hmac, + TEST_INPUT("\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b" + "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"), + ISC_MD_SHA512, + TEST_INPUT("\x48\x69\x20\x54\x68\x65\x72\x65"), + "87AA7CDEA5EF619D4FF0B4241A1D6CB02379F4E2CE4EC2" + "787AD0B30545E17CDEDAA833B7D6B8A702038B27" + "4EAEA3F4E4BE9D914EEB61F1702E696C203A126854", + 1); + /* Test 2 */ + isc_hmac_test(hmac, TEST_INPUT("Jefe"), ISC_MD_SHA512, + TEST_INPUT("\x77\x68\x61\x74\x20\x64\x6f\x20\x79\x61" + "\x20\x77\x61\x6e\x74\x20\x66\x6f\x72\x20" + "\x6e\x6f\x74\x68\x69\x6e\x67\x3f"), + "164B7A7BFCF819E2E395FBE73B56E0A387BD64222E831F" + "D610270CD7EA2505549758BF75C05A994A6D034F" + "65F8F0E6FDCAEAB1A34D4A6B4B636E070A38BCE737", + 1); + /* Test 3 */ + isc_hmac_test(hmac, + TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa"), + ISC_MD_SHA512, + TEST_INPUT("\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD" + "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD" + "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD" + "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD" + "\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD\xDD"), + "FA73B0089D56A284EFB0F0756C890BE9B1B5DBDD8EE81A" + "3655F83E33B2279D39BF3E848279A722C806B485" + "A47E67C807B946A337BEE8942674278859E13292FB", + 1); + /* Test 4 */ + isc_hmac_test(hmac, + TEST_INPUT("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a" + "\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14" + "\x15\x16\x17\x18\x19"), + ISC_MD_SHA512, + TEST_INPUT("\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd"), + "B0BA465637458C6990E5A8C5F61D4AF7E576D97FF94B87" + "2DE76F8050361EE3DBA91CA5C11AA25EB4D67927" + "5CC5788063A5F19741120C4F2DE2ADEBEB10A298DD", + 1); +#if 0 + /* Test 5 -- unimplemented optional functionality */ + isc_hmac_test(hmac, + TEST_INPUT("\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c" + "\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c"), + ISC_MD_SHA512, + TEST_INPUT("Test With Truncation"), + "4C1A03424B55E07FE7F27BE1", + 1); +#endif /* if 0 */ + /* Test 6 */ + isc_hmac_test(hmac, + TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa"), + ISC_MD_SHA512, + TEST_INPUT("Test Using Larger Than Block-Size Key - " + "Hash Key First"), + "80B24263C7C1A3EBB71493C1DD7BE8B49B46D1F41B4AEE" + "C1121B013783F8F3526B56D037E05F2598BD0FD2" + "215D6A1E5295E64F73F63F0AEC8B915A985D786598", + 1); + /* Test 7 */ + isc_hmac_test(hmac, + TEST_INPUT("\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa"), + ISC_MD_SHA512, + TEST_INPUT("\x54\x68\x69\x73\x20\x69\x73\x20\x61\x20" + "\x74\x65\x73\x74\x20\x75\x73\x69\x6e\x67" + "\x20\x61\x20\x6c\x61\x72\x67\x65\x72\x20" + "\x74\x68\x61\x6e\x20\x62\x6c\x6f\x63\x6b" + "\x2d\x73\x69\x7a\x65\x20\x6b\x65\x79\x20" + "\x61\x6e\x64\x20\x61\x20\x6c\x61\x72\x67" + "\x65\x72\x20\x74\x68\x61\x6e\x20\x62\x6c" + "\x6f\x63\x6b\x2d\x73\x69\x7a\x65\x20\x64" + "\x61\x74\x61\x2e\x20\x54\x68\x65\x20\x6b" + "\x65\x79\x20\x6e\x65\x65\x64\x73\x20\x74" + "\x6f\x20\x62\x65\x20\x68\x61\x73\x68\x65" + "\x64\x20\x62\x65\x66\x6f\x72\x65\x20\x62" + "\x65\x69\x6e\x67\x20\x75\x73\x65\x64\x20" + "\x62\x79\x20\x74\x68\x65\x20\x48\x4d\x41" + "\x43\x20\x61\x6c\x67\x6f\x72\x69\x74\x68" + "\x6d\x2e"), + "E37B6A775DC87DBAA4DFA9F96E5E3FFDDEBD71F8867289" + "865DF5A32D20CDC944B6022CAC3C4982B10D5EEB" + "55C3E4DE15134676FB6DE0446065C97440FA8C6A58", + 1); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + /* isc_hmac_new() */ + cmocka_unit_test(isc_hmac_new_test), + + /* isc_hmac_init() */ + cmocka_unit_test_setup_teardown(isc_hmac_init_test, _reset, + _reset), + + /* isc_hmac_reset() */ + cmocka_unit_test_setup_teardown(isc_hmac_reset_test, _reset, + _reset), + + /* isc_hmac_init() -> isc_hmac_update() -> isc_hmac_final() */ + cmocka_unit_test(isc_hmac_md5_test), + cmocka_unit_test(isc_hmac_sha1_test), + cmocka_unit_test(isc_hmac_sha224_test), + cmocka_unit_test(isc_hmac_sha256_test), + cmocka_unit_test(isc_hmac_sha384_test), + cmocka_unit_test(isc_hmac_sha512_test), + + cmocka_unit_test_setup_teardown(isc_hmac_update_test, _reset, + _reset), + cmocka_unit_test_setup_teardown(isc_hmac_final_test, _reset, + _reset), + + cmocka_unit_test(isc_hmac_free_test), + }; + + return (cmocka_run_group_tests(tests, _setup, _teardown)); +} + +#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 */ diff --git a/lib/isc/tests/ht_test.c b/lib/isc/tests/ht_test.c new file mode 100644 index 0000000..30cc615 --- /dev/null +++ b/lib/isc/tests/ht_test.c @@ -0,0 +1,358 @@ +/* + * 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 <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define UNIT_TESTING +#include <cmocka.h> + +#include <isc/hash.h> +#include <isc/ht.h> +#include <isc/mem.h> +#include <isc/print.h> +#include <isc/string.h> +#include <isc/util.h> + +#include "isctest.h" + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = isc_test_begin(NULL, true, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + isc_test_end(); + + return (0); +} + +static void +test_ht_full(int bits, uintptr_t count) { + isc_ht_t *ht = NULL; + isc_result_t result; + uintptr_t i; + + isc_ht_init(&ht, test_mctx, bits); + assert_non_null(ht); + + for (i = 1; i < count; i++) { + /* + * Note: snprintf() is followed with strlcat() + * to ensure we are always filling the 16 byte key. + */ + unsigned char key[16]; + snprintf((char *)key, sizeof(key), "%u", (unsigned int)i); + strlcat((char *)key, " key of a raw hashtable!!", sizeof(key)); + result = isc_ht_add(ht, key, 16, (void *)i); + assert_int_equal(result, ISC_R_SUCCESS); + } + + for (i = 1; i < count; i++) { + unsigned char key[16]; + void *f = NULL; + snprintf((char *)key, sizeof(key), "%u", (unsigned int)i); + strlcat((char *)key, " key of a raw hashtable!!", sizeof(key)); + result = isc_ht_find(ht, key, 16, &f); + assert_int_equal(result, ISC_R_SUCCESS); + assert_ptr_equal((void *)i, (uintptr_t)f); + } + + for (i = 1; i < count; i++) { + unsigned char key[16]; + snprintf((char *)key, sizeof(key), "%u", (unsigned int)i); + strlcat((char *)key, " key of a raw hashtable!!", sizeof(key)); + result = isc_ht_add(ht, key, 16, (void *)i); + assert_int_equal(result, ISC_R_EXISTS); + } + + for (i = 1; i < count; i++) { + char key[64]; + /* + * Note: the key size is now strlen(key) which is bigger + * then the keys added above. + */ + snprintf((char *)key, sizeof(key), "%u", (unsigned int)i); + strlcat((char *)key, " key of a raw hashtable!!", sizeof(key)); + result = isc_ht_add(ht, (const unsigned char *)key, strlen(key), + (void *)i); + assert_int_equal(result, ISC_R_SUCCESS); + } + + for (i = 1; i < count; i++) { + unsigned char key[16]; + void *f = NULL; + /* + * Note: case of KEY is now in capitals, + */ + snprintf((char *)key, sizeof(key), "%u", (unsigned int)i); + strlcat((char *)key, " KEY of a raw hashtable!!", sizeof(key)); + result = isc_ht_find(ht, key, 16, &f); + assert_int_equal(result, ISC_R_NOTFOUND); + assert_null(f); + } + + for (i = 1; i < count; i++) { + char key[64]; + void *f = NULL; + snprintf((char *)key, sizeof(key), "%u", (unsigned int)i); + strlcat((char *)key, " key of a raw hashtable!!", sizeof(key)); + result = isc_ht_find(ht, (const unsigned char *)key, + strlen(key), &f); + assert_int_equal(result, ISC_R_SUCCESS); + assert_ptr_equal(f, (void *)i); + } + + for (i = 1; i < count; i++) { + unsigned char key[16]; + void *f = NULL; + snprintf((char *)key, sizeof(key), "%u", (unsigned int)i); + strlcat((char *)key, " key of a raw hashtable!!", sizeof(key)); + result = isc_ht_delete(ht, key, 16); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_ht_find(ht, key, 16, &f); + assert_int_equal(result, ISC_R_NOTFOUND); + assert_null(f); + } + + for (i = 1; i < count; i++) { + unsigned char key[16]; + /* + * Note: upper case KEY. + */ + snprintf((char *)key, sizeof(key), "%u", (unsigned int)i); + strlcat((char *)key, " KEY of a raw hashtable!!", sizeof(key)); + result = isc_ht_add(ht, key, 16, (void *)i); + assert_int_equal(result, ISC_R_SUCCESS); + } + + for (i = 1; i < count; i++) { + char key[64]; + void *f = NULL; + snprintf((char *)key, sizeof(key), "%u", (unsigned int)i); + strlcat((char *)key, " key of a raw hashtable!!", sizeof(key)); + result = isc_ht_delete(ht, (const unsigned char *)key, + strlen(key)); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_ht_find(ht, (const unsigned char *)key, + strlen(key), &f); + assert_int_equal(result, ISC_R_NOTFOUND); + assert_null(f); + } + + for (i = 1; i < count; i++) { + unsigned char key[16]; + void *f = NULL; + /* + * Note: case of KEY is now in capitals, + */ + snprintf((char *)key, sizeof(key), "%u", (unsigned int)i); + strlcat((char *)key, " KEY of a raw hashtable!!", sizeof(key)); + result = isc_ht_find(ht, key, 16, &f); + assert_int_equal(result, ISC_R_SUCCESS); + assert_ptr_equal((void *)i, (uintptr_t)f); + } + + for (i = 1; i < count; i++) { + unsigned char key[16]; + void *f = NULL; + snprintf((char *)key, sizeof(key), "%u", (unsigned int)i); + strlcat((char *)key, " key of a raw hashtable!!", sizeof(key)); + result = isc_ht_find(ht, key, 16, &f); + assert_int_equal(result, ISC_R_NOTFOUND); + assert_null(f); + } + + isc_ht_destroy(&ht); + assert_null(ht); +} + +static void +test_ht_iterator() { + isc_ht_t *ht = NULL; + isc_result_t result; + isc_ht_iter_t *iter = NULL; + uintptr_t i; + uintptr_t count = 10000; + uint32_t walked; + unsigned char key[16]; + size_t tksize; + + isc_ht_init(&ht, test_mctx, 16); + assert_non_null(ht); + for (i = 1; i <= count; i++) { + /* + * Note that the string we're snprintfing is always > 16 bytes + * so we are always filling the key. + */ + snprintf((char *)key, sizeof(key), "%u", (unsigned int)i); + strlcat((char *)key, "key of a raw hashtable!!", sizeof(key)); + result = isc_ht_add(ht, key, 16, (void *)i); + assert_int_equal(result, ISC_R_SUCCESS); + } + + walked = 0; + isc_ht_iter_create(ht, &iter); + + for (result = isc_ht_iter_first(iter); result == ISC_R_SUCCESS; + result = isc_ht_iter_next(iter)) + { + unsigned char *tkey = NULL; + void *v = NULL; + + isc_ht_iter_current(iter, &v); + isc_ht_iter_currentkey(iter, &tkey, &tksize); + assert_int_equal(tksize, 16); + i = (uintptr_t)v; + snprintf((char *)key, sizeof(key), "%u", (unsigned int)i); + strlcat((char *)key, "key of a raw hashtable!!", sizeof(key)); + assert_memory_equal(key, tkey, 16); + walked++; + } + assert_int_equal(walked, count); + assert_int_equal(result, ISC_R_NOMORE); + + /* erase odd */ + walked = 0; + result = isc_ht_iter_first(iter); + while (result == ISC_R_SUCCESS) { + unsigned char *tkey = NULL; + void *v = NULL; + + isc_ht_iter_current(iter, &v); + isc_ht_iter_currentkey(iter, &tkey, &tksize); + assert_int_equal(tksize, 16); + i = (uintptr_t)v; + snprintf((char *)key, sizeof(key), "%u", (unsigned int)i); + strlcat((char *)key, "key of a raw hashtable!!", sizeof(key)); + assert_memory_equal(key, tkey, 16); + if ((uintptr_t)v % 2 == 0) { + result = isc_ht_iter_delcurrent_next(iter); + } else { + result = isc_ht_iter_next(iter); + } + walked++; + } + assert_int_equal(result, ISC_R_NOMORE); + assert_int_equal(walked, count); + + /* erase even */ + walked = 0; + result = isc_ht_iter_first(iter); + while (result == ISC_R_SUCCESS) { + unsigned char *tkey = NULL; + void *v = NULL; + + isc_ht_iter_current(iter, &v); + isc_ht_iter_currentkey(iter, &tkey, &tksize); + assert_int_equal(tksize, 16); + i = (uintptr_t)v; + snprintf((char *)key, sizeof(key), "%u", (unsigned int)i); + strlcat((char *)key, "key of a raw hashtable!!", sizeof(key)); + assert_memory_equal(key, tkey, 16); + if ((uintptr_t)v % 2 == 1) { + result = isc_ht_iter_delcurrent_next(iter); + } else { + result = isc_ht_iter_next(iter); + } + walked++; + } + assert_int_equal(result, ISC_R_NOMORE); + assert_int_equal(walked, count / 2); + + walked = 0; + for (result = isc_ht_iter_first(iter); result == ISC_R_SUCCESS; + result = isc_ht_iter_next(iter)) + { + walked++; + } + + assert_int_equal(result, ISC_R_NOMORE); + assert_int_equal(walked, 0); + + isc_ht_iter_destroy(&iter); + assert_null(iter); + + isc_ht_destroy(&ht); + assert_null(ht); +} + +/* 20 bit, 200K elements test */ +static void +isc_ht_20(void **state) { + UNUSED(state); + test_ht_full(20, 200000); +} + +/* 8 bit, 20000 elements crowded test */ +static void +isc_ht_8(void **state) { + UNUSED(state); + test_ht_full(8, 20000); +} + +/* 8 bit, 100 elements corner case test */ +static void +isc_ht_1(void **state) { + UNUSED(state); + test_ht_full(1, 100); +} + +/* test hashtable iterator */ +static void +isc_ht_iterator_test(void **state) { + UNUSED(state); + test_ht_iterator(); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(isc_ht_20), + cmocka_unit_test(isc_ht_8), + cmocka_unit_test(isc_ht_1), + cmocka_unit_test(isc_ht_iterator_test), + }; + + return (cmocka_run_group_tests(tests, _setup, _teardown)); +} + +#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 */ diff --git a/lib/isc/tests/isctest.c b/lib/isc/tests/isctest.c new file mode 100644 index 0000000..3b0235b --- /dev/null +++ b/lib/isc/tests/isctest.c @@ -0,0 +1,173 @@ +/* + * 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. + */ + +/*! \file */ + +#include "isctest.h" +#include <inttypes.h> +#include <stdbool.h> +#include <stdlib.h> +#include <time.h> + +#include <isc/buffer.h> +#include <isc/hash.h> +#include <isc/managers.h> +#include <isc/mem.h> +#include <isc/os.h> +#include <isc/socket.h> +#include <isc/string.h> +#include <isc/task.h> +#include <isc/timer.h> +#include <isc/util.h> + +isc_mem_t *test_mctx = NULL; +isc_log_t *test_lctx = NULL; +isc_taskmgr_t *taskmgr = NULL; +isc_timermgr_t *timermgr = NULL; +isc_socketmgr_t *socketmgr = NULL; +isc_nm_t *netmgr = NULL; +isc_task_t *maintask = NULL; +int ncpus; + +static bool test_running = false; + +/* + * Logging categories: this needs to match the list in bin/named/log.c. + */ +static isc_logcategory_t categories[] = { { "", 0 }, + { "client", 0 }, + { "network", 0 }, + { "update", 0 }, + { "queries", 0 }, + { "unmatched", 0 }, + { "update-security", 0 }, + { "query-errors", 0 }, + { NULL, 0 } }; + +static void +cleanup_managers(void) { + if (maintask != NULL) { + isc_task_shutdown(maintask); + isc_task_destroy(&maintask); + } + + isc_managers_destroy(netmgr == NULL ? NULL : &netmgr, + taskmgr == NULL ? NULL : &taskmgr); + + if (socketmgr != NULL) { + isc_socketmgr_destroy(&socketmgr); + } + if (timermgr != NULL) { + isc_timermgr_destroy(&timermgr); + } +} + +static isc_result_t +create_managers(unsigned int workers) { + isc_result_t result; + char *p; + + if (workers == 0) { + workers = isc_os_ncpus(); + } + + p = getenv("ISC_TASK_WORKERS"); + if (p != NULL) { + workers = atoi(p); + } + + CHECK(isc_managers_create(test_mctx, workers, 0, &netmgr, &taskmgr)); + CHECK(isc_task_create_bound(taskmgr, 0, &maintask, 0)); + isc_taskmgr_setexcltask(taskmgr, maintask); + + CHECK(isc_timermgr_create(test_mctx, &timermgr)); + CHECK(isc_socketmgr_create(test_mctx, &socketmgr)); + return (ISC_R_SUCCESS); + +cleanup: + cleanup_managers(); + return (result); +} + +isc_result_t +isc_test_begin(FILE *logfile, bool start_managers, unsigned int workers) { + isc_result_t result; + + INSIST(!test_running); + test_running = true; + + isc_mem_debugging |= ISC_MEM_DEBUGRECORD; + + INSIST(test_mctx == NULL); + isc_mem_create(&test_mctx); + + if (logfile != NULL) { + isc_logdestination_t destination; + isc_logconfig_t *logconfig = NULL; + + INSIST(test_lctx == NULL); + isc_log_create(test_mctx, &test_lctx, &logconfig); + isc_log_registercategories(test_lctx, categories); + isc_log_setcontext(test_lctx); + + destination.file.stream = logfile; + destination.file.name = NULL; + destination.file.versions = ISC_LOG_ROLLNEVER; + destination.file.maximum_size = 0; + isc_log_createchannel(logconfig, "stderr", ISC_LOG_TOFILEDESC, + ISC_LOG_DYNAMIC, &destination, 0); + CHECK(isc_log_usechannel(logconfig, "stderr", NULL, NULL)); + } + + ncpus = isc_os_ncpus(); + + if (start_managers) { + CHECK(create_managers(workers)); + } + + return (ISC_R_SUCCESS); + +cleanup: + isc_test_end(); + return (result); +} + +void +isc_test_end(void) { + if (maintask != NULL) { + isc_task_detach(&maintask); + } + + cleanup_managers(); + + if (test_lctx != NULL) { + isc_log_destroy(&test_lctx); + } + if (test_mctx != NULL) { + isc_mem_destroy(&test_mctx); + } + + test_running = false; +} + +/* + * Sleep for 'usec' microseconds. + */ +void +isc_test_nap(uint32_t usec) { + struct timespec ts; + + ts.tv_sec = usec / 1000000; + ts.tv_nsec = (usec % 1000000) * 1000; + nanosleep(&ts, NULL); +} diff --git a/lib/isc/tests/isctest.h b/lib/isc/tests/isctest.h new file mode 100644 index 0000000..4f88e91 --- /dev/null +++ b/lib/isc/tests/isctest.h @@ -0,0 +1,63 @@ +/* + * 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. + */ + +/*! \file */ + +#include <inttypes.h> +#include <stdbool.h> + +#include <isc/buffer.h> +#include <isc/hash.h> +#include <isc/log.h> +#include <isc/mem.h> +#include <isc/netmgr.h> +#include <isc/print.h> +#include <isc/result.h> +#include <isc/string.h> +#include <isc/task.h> +#include <isc/timer.h> +#include <isc/util.h> + +#define CHECK(r) \ + do { \ + result = (r); \ + if (result != ISC_R_SUCCESS) \ + goto cleanup; \ + } while (0) + +extern isc_mem_t *test_mctx; +extern isc_log_t *test_lctx; +extern isc_taskmgr_t *taskmgr; +extern isc_timermgr_t *timermgr; +extern isc_socketmgr_t *socketmgr; +extern isc_nm_t *netmgr; +extern int ncpus; + +isc_result_t +isc_test_begin(FILE *logfile, bool start_managers, unsigned int workers); +/*%< + * Begin test, logging to 'logfile' or default if not specified. + * + * If 'start_managers' is set, start a task manager, timer manager, + * and socket manager. + * + * If 'workers' is zero, use the number of CPUs on the system as a default; + * otherwise, set up the task manager with the specified number of worker + * threads. The environment variable ISC_TASK_WORKERS overrides this value. + */ + +void +isc_test_end(void); + +void +isc_test_nap(uint32_t usec); diff --git a/lib/isc/tests/lex_test.c b/lib/isc/tests/lex_test.c new file mode 100644 index 0000000..4f8f80d --- /dev/null +++ b/lib/isc/tests/lex_test.c @@ -0,0 +1,432 @@ +/* + * 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 <sched.h> /* IWYU pragma: keep */ +#include <setjmp.h> +#include <stdarg.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/lex.h> +#include <isc/mem.h> +#include <isc/util.h> + +#include "isctest.h" + +#define AS_STR(x) (x).value.as_textregion.base + +static bool debug = false; + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = isc_test_begin(NULL, true, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + isc_test_end(); + + return (0); +} + +/* check handling of 0xff */ +static void +lex_0xff(void **state) { + isc_result_t result; + isc_lex_t *lex = NULL; + isc_buffer_t death_buf; + isc_token_t token; + + unsigned char death[] = { EOF, 'A' }; + + UNUSED(state); + + result = isc_lex_create(test_mctx, 1024, &lex); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_buffer_init(&death_buf, &death[0], sizeof(death)); + isc_buffer_add(&death_buf, sizeof(death)); + + result = isc_lex_openbuffer(lex, &death_buf); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_lex_gettoken(lex, 0, &token); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_lex_destroy(&lex); +} + +/* check setting of source line */ +static void +lex_setline(void **state) { + isc_result_t result; + isc_lex_t *lex = NULL; + unsigned char text[] = "text\nto\nbe\nprocessed\nby\nlexer"; + isc_buffer_t buf; + isc_token_t token; + unsigned long line; + int i; + + UNUSED(state); + + result = isc_lex_create(test_mctx, 1024, &lex); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_buffer_init(&buf, &text[0], sizeof(text)); + isc_buffer_add(&buf, sizeof(text)); + + result = isc_lex_openbuffer(lex, &buf); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_lex_setsourceline(lex, 100); + assert_int_equal(result, ISC_R_SUCCESS); + + for (i = 0; i < 6; i++) { + result = isc_lex_gettoken(lex, 0, &token); + assert_int_equal(result, ISC_R_SUCCESS); + + line = isc_lex_getsourceline(lex); + assert_int_equal(line, 100U + i); + } + + result = isc_lex_gettoken(lex, 0, &token); + assert_int_equal(result, ISC_R_EOF); + + line = isc_lex_getsourceline(lex); + assert_int_equal(line, 105U); + + isc_lex_destroy(&lex); +} + +static struct { + const char *text; + const char *string_value; + isc_result_t string_result; + isc_tokentype_t string_type; + const char *qstring_value; + isc_result_t qstring_result; + isc_tokentype_t qstring_type; + const char *qvpair_value; + isc_result_t qvpair_result; + isc_tokentype_t qvpair_type; +} parse_tests[] = { + { "", "", ISC_R_SUCCESS, isc_tokentype_eof, "", ISC_R_SUCCESS, + isc_tokentype_eof, "", ISC_R_SUCCESS, isc_tokentype_eof }, + { "1234", "1234", ISC_R_SUCCESS, isc_tokentype_string, "1234", + ISC_R_SUCCESS, isc_tokentype_string, "1234", ISC_R_SUCCESS, + isc_tokentype_string }, + { "1234=", "1234=", ISC_R_SUCCESS, isc_tokentype_string, + "1234=", ISC_R_SUCCESS, isc_tokentype_string, "1234=", ISC_R_SUCCESS, + isc_tokentype_vpair }, + { "1234=foo", "1234=foo", ISC_R_SUCCESS, isc_tokentype_string, + "1234=foo", ISC_R_SUCCESS, isc_tokentype_string, "1234=foo", + ISC_R_SUCCESS, isc_tokentype_vpair }, + { "1234=\"foo", "1234=\"foo", ISC_R_SUCCESS, isc_tokentype_string, + "1234=\"foo", ISC_R_SUCCESS, isc_tokentype_string, NULL, + ISC_R_UNEXPECTEDEND, 0 }, + { "1234=\"foo\"", "1234=\"foo\"", ISC_R_SUCCESS, isc_tokentype_string, + "1234=\"foo\"", ISC_R_SUCCESS, isc_tokentype_string, "1234=foo", + ISC_R_SUCCESS, isc_tokentype_qvpair }, + { "key", "key", ISC_R_SUCCESS, isc_tokentype_string, "key", + ISC_R_SUCCESS, isc_tokentype_string, "key", ISC_R_SUCCESS, + isc_tokentype_string }, + { "\"key=", "\"key=", ISC_R_SUCCESS, isc_tokentype_string, NULL, + ISC_R_UNEXPECTEDEND, 0, "\"key=", ISC_R_SUCCESS, + isc_tokentype_vpair }, + { "\"key=\"", "\"key=\"", ISC_R_SUCCESS, isc_tokentype_string, "key=", + ISC_R_SUCCESS, isc_tokentype_qstring, NULL, ISC_R_UNEXPECTEDEND, 0 }, + { "key=\"\"", "key=\"\"", ISC_R_SUCCESS, isc_tokentype_string, + "key=\"\"", ISC_R_SUCCESS, isc_tokentype_string, + "key=", ISC_R_SUCCESS, isc_tokentype_qvpair }, + { "key=\"a b\"", "key=\"a", ISC_R_SUCCESS, isc_tokentype_string, + "key=\"a", ISC_R_SUCCESS, isc_tokentype_string, "key=a b", + ISC_R_SUCCESS, isc_tokentype_qvpair }, + { "key=\"a\tb\"", "key=\"a", ISC_R_SUCCESS, isc_tokentype_string, + "key=\"a", ISC_R_SUCCESS, isc_tokentype_string, "key=a\tb", + ISC_R_SUCCESS, isc_tokentype_qvpair }, + /* double quote not immediately after '=' is not special. */ + { "key=c\"a b\"", "key=c\"a", ISC_R_SUCCESS, isc_tokentype_string, + "key=c\"a", ISC_R_SUCCESS, isc_tokentype_string, "key=c\"a", + ISC_R_SUCCESS, isc_tokentype_vpair }, + /* remove special meaning for '=' by escaping */ + { "key\\=", "key\\=", ISC_R_SUCCESS, isc_tokentype_string, + "key\\=", ISC_R_SUCCESS, isc_tokentype_string, + "key\\=", ISC_R_SUCCESS, isc_tokentype_string }, + { "key\\=\"a\"", "key\\=\"a\"", ISC_R_SUCCESS, isc_tokentype_string, + "key\\=\"a\"", ISC_R_SUCCESS, isc_tokentype_string, "key\\=\"a\"", + ISC_R_SUCCESS, isc_tokentype_string }, + { "key\\=\"a \"", "key\\=\"a", ISC_R_SUCCESS, isc_tokentype_string, + "key\\=\"a", ISC_R_SUCCESS, isc_tokentype_string, "key\\=\"a", + ISC_R_SUCCESS, isc_tokentype_string }, + /* vpair with a key of 'key\=' (would need to be deescaped) */ + { "key\\==", "key\\==", ISC_R_SUCCESS, isc_tokentype_string, + "key\\==", ISC_R_SUCCESS, isc_tokentype_string, + "key\\==", ISC_R_SUCCESS, isc_tokentype_vpair }, + { "key\\==\"\"", "key\\==\"\"", ISC_R_SUCCESS, isc_tokentype_string, + "key\\==\"\"", ISC_R_SUCCESS, isc_tokentype_string, + "key\\==", ISC_R_SUCCESS, isc_tokentype_qvpair }, + { "key=\\\\\\\\", "key=\\\\\\\\", ISC_R_SUCCESS, isc_tokentype_string, + "key=\\\\\\\\", ISC_R_SUCCESS, isc_tokentype_string, "key=\\\\\\\\", + ISC_R_SUCCESS, isc_tokentype_vpair }, + { "key=\\\\\\\"", "key=\\\\\\\"", ISC_R_SUCCESS, isc_tokentype_string, + "key=\\\\\\\"", ISC_R_SUCCESS, isc_tokentype_string, "key=\\\\\\\"", + ISC_R_SUCCESS, isc_tokentype_vpair }, + /* incomplete escape sequence */ + { "key=\\\"\\", NULL, ISC_R_UNEXPECTEDEND, isc_tokentype_string, NULL, + ISC_R_UNEXPECTEDEND, 0, NULL, ISC_R_UNEXPECTEDEND, 0 }, + /* incomplete escape sequence */ + { "key=\\", NULL, ISC_R_UNEXPECTEDEND, isc_tokentype_string, NULL, + ISC_R_UNEXPECTEDEND, 0, NULL, ISC_R_UNEXPECTEDEND, 0 }, +}; + +/*% + * string + */ +static void +lex_string(void **state) { + isc_buffer_t buf; + isc_lex_t *lex = NULL; + isc_result_t result; + isc_token_t token; + size_t i; + + UNUSED(state); + + for (i = 0; i < ARRAY_SIZE(parse_tests); i++) { + result = isc_lex_create(test_mctx, 1024, &lex); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_buffer_constinit(&buf, parse_tests[i].text, + strlen(parse_tests[i].text)); + isc_buffer_add(&buf, strlen(parse_tests[i].text)); + + result = isc_lex_openbuffer(lex, &buf); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_lex_setsourceline(lex, 100); + assert_int_equal(result, ISC_R_SUCCESS); + + memset(&token, 0, sizeof(token)); + result = isc_lex_getmastertoken(lex, &token, + isc_tokentype_string, true); + if (debug) { + fprintf(stdout, "# '%s' -> result=%s/%s, type=%d/%d\n", + parse_tests[i].text, isc_result_toid(result), + isc_result_toid(parse_tests[i].string_result), + token.type, parse_tests[i].string_type); + } + + assert_int_equal(result, parse_tests[i].string_result); + if (result == ISC_R_SUCCESS) { + switch (token.type) { + case isc_tokentype_string: + case isc_tokentype_qstring: + case isc_tokentype_vpair: + case isc_tokentype_qvpair: + if (debug) { + fprintf(stdout, "# value='%s'\n", + AS_STR(token)); + } + assert_int_equal(token.type, + parse_tests[i].string_type); + assert_string_equal( + AS_STR(token), + parse_tests[i].string_value); + break; + default: + assert_int_equal(token.type, + parse_tests[i].string_type); + break; + } + } + + isc_lex_destroy(&lex); + } +} + +/*% + * qstring + */ +static void +lex_qstring(void **state) { + isc_buffer_t buf; + isc_lex_t *lex = NULL; + isc_result_t result; + isc_token_t token; + size_t i; + + UNUSED(state); + + for (i = 0; i < ARRAY_SIZE(parse_tests); i++) { + result = isc_lex_create(test_mctx, 1024, &lex); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_buffer_constinit(&buf, parse_tests[i].text, + strlen(parse_tests[i].text)); + isc_buffer_add(&buf, strlen(parse_tests[i].text)); + + result = isc_lex_openbuffer(lex, &buf); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_lex_setsourceline(lex, 100); + assert_int_equal(result, ISC_R_SUCCESS); + + memset(&token, 0, sizeof(token)); + result = isc_lex_getmastertoken(lex, &token, + isc_tokentype_qstring, true); + if (debug) { + fprintf(stdout, "# '%s' -> result=%s/%s, type=%d/%d\n", + parse_tests[i].text, isc_result_toid(result), + isc_result_toid(parse_tests[i].qstring_result), + token.type, parse_tests[i].qstring_type); + } + + assert_int_equal(result, parse_tests[i].qstring_result); + if (result == ISC_R_SUCCESS) { + switch (token.type) { + case isc_tokentype_string: + case isc_tokentype_qstring: + case isc_tokentype_vpair: + case isc_tokentype_qvpair: + if (debug) { + fprintf(stdout, "# value='%s'\n", + AS_STR(token)); + } + assert_int_equal(token.type, + parse_tests[i].qstring_type); + assert_string_equal( + AS_STR(token), + parse_tests[i].qstring_value); + break; + default: + assert_int_equal(token.type, + parse_tests[i].qstring_type); + break; + } + } + + isc_lex_destroy(&lex); + } +} + +/*% + * keypair is <string>=<qstring>. This has implications double quotes + * in key names. + */ +static void +lex_keypair(void **state) { + isc_buffer_t buf; + isc_lex_t *lex = NULL; + isc_result_t result; + isc_token_t token; + size_t i; + + UNUSED(state); + + for (i = 0; i < ARRAY_SIZE(parse_tests); i++) { + result = isc_lex_create(test_mctx, 1024, &lex); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_buffer_constinit(&buf, parse_tests[i].text, + strlen(parse_tests[i].text)); + isc_buffer_add(&buf, strlen(parse_tests[i].text)); + + result = isc_lex_openbuffer(lex, &buf); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_lex_setsourceline(lex, 100); + assert_int_equal(result, ISC_R_SUCCESS); + + memset(&token, 0, sizeof(token)); + result = isc_lex_getmastertoken(lex, &token, + isc_tokentype_qvpair, true); + if (debug) { + fprintf(stdout, "# '%s' -> result=%s/%s, type=%d/%d\n", + parse_tests[i].text, isc_result_toid(result), + isc_result_toid(parse_tests[i].qvpair_result), + token.type, parse_tests[i].qvpair_type); + } + + assert_int_equal(result, parse_tests[i].qvpair_result); + if (result == ISC_R_SUCCESS) { + switch (token.type) { + case isc_tokentype_string: + case isc_tokentype_qstring: + case isc_tokentype_vpair: + case isc_tokentype_qvpair: + if (debug) { + fprintf(stdout, "# value='%s'\n", + AS_STR(token)); + } + assert_int_equal(token.type, + parse_tests[i].qvpair_type); + assert_string_equal( + AS_STR(token), + parse_tests[i].qvpair_value); + break; + default: + assert_int_equal(token.type, + parse_tests[i].qvpair_type); + break; + } + } + + isc_lex_destroy(&lex); + } +} + +int +main(int argc, char *argv[]) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(lex_0xff), cmocka_unit_test(lex_keypair), + cmocka_unit_test(lex_setline), cmocka_unit_test(lex_string), + cmocka_unit_test(lex_qstring), + }; + + UNUSED(argv); + + if (argc > 1) { + debug = true; + } + + return (cmocka_run_group_tests(tests, _setup, _teardown)); +} + +#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 */ diff --git a/lib/isc/tests/md_test.c b/lib/isc/tests/md_test.c new file mode 100644 index 0000000..83c44c4 --- /dev/null +++ b/lib/isc/tests/md_test.c @@ -0,0 +1,587 @@ +/* + * 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 <setjmp.h> +#include <stdarg.h> +#include <stddef.h> +#include <string.h> + +/* For FIPS_mode() */ +#include <openssl/crypto.h> + +#define UNIT_TESTING +#include <cmocka.h> + +#include <isc/buffer.h> +#include <isc/hex.h> +#include <isc/md.h> +#include <isc/region.h> +#include <isc/result.h> + +#include "../md.c" + +#define TEST_INPUT(x) (x), sizeof(x) - 1 + +static int +_setup(void **state) { + isc_md_t *md = isc_md_new(); + if (md == NULL) { + return (-1); + } + *state = md; + return (0); +} + +static int +_teardown(void **state) { + if (*state == NULL) { + return (-1); + } + isc_md_free(*state); + return (0); +} + +static int +_reset(void **state) { + if (*state == NULL) { + return (-1); + } + if (isc_md_reset(*state) != ISC_R_SUCCESS) { + return (-1); + } + return (0); +} + +static void +isc_md_new_test(void **state) { + UNUSED(state); + + isc_md_t *md = isc_md_new(); + assert_non_null(md); + isc_md_free(md); /* Cleanup */ +} + +static void +isc_md_free_test(void **state) { + UNUSED(state); + + isc_md_t *md = isc_md_new(); + assert_non_null(md); + isc_md_free(md); /* Test freeing valid message digest context */ + isc_md_free(NULL); /* Test freeing NULL argument */ +} + +static void +isc_md_test(isc_md_t *md, const isc_md_type_t *type, const char *buf, + size_t buflen, const char *result, const int repeats) { + assert_non_null(md); + assert_int_equal(isc_md_init(md, type), ISC_R_SUCCESS); + + int i; + + for (i = 0; i < repeats; i++) { + assert_int_equal( + isc_md_update(md, (const unsigned char *)buf, buflen), + ISC_R_SUCCESS); + } + + unsigned char digest[ISC_MAX_MD_SIZE]; + unsigned int digestlen; + assert_int_equal(isc_md_final(md, digest, &digestlen), ISC_R_SUCCESS); + + char hexdigest[ISC_MAX_MD_SIZE * 2 + 3]; + isc_region_t r = { .base = digest, .length = digestlen }; + isc_buffer_t b; + isc_buffer_init(&b, hexdigest, sizeof(hexdigest)); + + assert_return_code(isc_hex_totext(&r, 0, "", &b), ISC_R_SUCCESS); + + assert_memory_equal(hexdigest, result, (result ? strlen(result) : 0)); + assert_int_equal(isc_md_reset(md), ISC_R_SUCCESS); +} + +static void +isc_md_init_test(void **state) { + isc_md_t *md = *state; + assert_non_null(md); + + expect_assert_failure(isc_md_init(NULL, ISC_MD_MD5)); + + assert_int_equal(isc_md_init(md, NULL), ISC_R_NOTIMPLEMENTED); + + assert_int_equal(isc_md_init(md, ISC_MD_MD5), ISC_R_SUCCESS); + assert_int_equal(isc_md_reset(md), ISC_R_SUCCESS); + + assert_int_equal(isc_md_init(md, ISC_MD_SHA1), ISC_R_SUCCESS); + assert_int_equal(isc_md_reset(md), ISC_R_SUCCESS); + + assert_int_equal(isc_md_init(md, ISC_MD_SHA224), ISC_R_SUCCESS); + assert_int_equal(isc_md_reset(md), ISC_R_SUCCESS); + + assert_int_equal(isc_md_init(md, ISC_MD_SHA256), ISC_R_SUCCESS); + assert_int_equal(isc_md_reset(md), ISC_R_SUCCESS); + + assert_int_equal(isc_md_init(md, ISC_MD_SHA384), ISC_R_SUCCESS); + assert_int_equal(isc_md_reset(md), ISC_R_SUCCESS); + + assert_int_equal(isc_md_init(md, ISC_MD_SHA512), ISC_R_SUCCESS); + assert_int_equal(isc_md_reset(md), ISC_R_SUCCESS); +} + +static void +isc_md_update_test(void **state) { + isc_md_t *md = *state; + assert_non_null(md); + + /* Uses message digest context initialized in isc_md_init_test() */ + expect_assert_failure(isc_md_update(NULL, NULL, 0)); + + assert_int_equal(isc_md_update(md, NULL, 100), ISC_R_SUCCESS); + assert_int_equal(isc_md_update(md, (const unsigned char *)"", 0), + ISC_R_SUCCESS); +} + +static void +isc_md_reset_test(void **state) { + isc_md_t *md = *state; +#if 0 + unsigned char digest[ISC_MAX_MD_SIZE] __attribute((unused)); + unsigned int digestlen __attribute((unused)); +#endif /* if 0 */ + + assert_non_null(md); + + assert_int_equal(isc_md_init(md, ISC_MD_SHA512), ISC_R_SUCCESS); + assert_int_equal(isc_md_update(md, (const unsigned char *)"a", 1), + ISC_R_SUCCESS); + assert_int_equal(isc_md_update(md, (const unsigned char *)"b", 1), + ISC_R_SUCCESS); + + assert_int_equal(isc_md_reset(md), ISC_R_SUCCESS); + +#if 0 + /* + * This test would require OpenSSL compiled with mock_assert(), + * so this could be only manually checked that the test will + * segfault when called by hand + */ + expect_assert_failure(isc_md_final(md,digest,&digestlen)); +#endif /* if 0 */ +} + +static void +isc_md_final_test(void **state) { + isc_md_t *md = *state; + assert_non_null(md); + + unsigned char digest[ISC_MAX_MD_SIZE]; + unsigned int digestlen; + + /* Fail when message digest context is empty */ + expect_assert_failure(isc_md_final(NULL, digest, &digestlen)); + /* Fail when output buffer is empty */ + expect_assert_failure(isc_md_final(md, NULL, &digestlen)); + + assert_int_equal(isc_md_init(md, ISC_MD_SHA512), ISC_R_SUCCESS); + assert_int_equal(isc_md_final(md, digest, NULL), ISC_R_SUCCESS); +} + +static void +isc_md_md5_test(void **state) { + isc_md_t *md = *state; + isc_md_test(md, ISC_MD_MD5, NULL, 0, NULL, 0); + isc_md_test(md, ISC_MD_MD5, TEST_INPUT(""), + "D41D8CD98F00B204E9800998ECF8427E", 1); + isc_md_test(md, ISC_MD_MD5, TEST_INPUT("a"), + "0CC175B9C0F1B6A831C399E269772661", 1); + isc_md_test(md, ISC_MD_MD5, TEST_INPUT("abc"), + "900150983CD24FB0D6963F7D28E17F72", 1); + isc_md_test(md, ISC_MD_MD5, TEST_INPUT("message digest"), + "F96B697D7CB7938D525A2F31AAF161D0", 1); + isc_md_test(md, ISC_MD_MD5, TEST_INPUT("abcdefghijklmnopqrstuvwxyz"), + "C3FCD3D76192E4007DFB496CCA67E13B", 1); + isc_md_test(md, ISC_MD_MD5, + TEST_INPUT("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklm" + "nopqrstuvwxyz0123456789"), + "D174AB98D277D9F5A5611C2C9F419D9F", 1); + isc_md_test(md, ISC_MD_MD5, + TEST_INPUT("123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890"), + "57EDF4A22BE3C955AC49DA2E2107B67A", 1); +} + +static void +isc_md_sha1_test(void **state) { + isc_md_t *md = *state; + isc_md_test(md, ISC_MD_SHA1, NULL, 0, NULL, 0); + isc_md_test(md, ISC_MD_SHA1, TEST_INPUT(""), + "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", 1); + isc_md_test(md, ISC_MD_SHA1, TEST_INPUT("abc"), + "A9993E364706816ABA3E25717850C26C9CD0D89D", 1); + isc_md_test(md, ISC_MD_SHA1, + TEST_INPUT("abcdbcdecdefdefgefghfghighijhijkijk" + "ljklmklmnlmnomnopnopq"), + "84983E441C3BD26EBAAE4AA1F95129E5E54670F1", 1); + isc_md_test(md, ISC_MD_SHA1, TEST_INPUT("a"), + "34AA973CD4C4DAA4F61EEB2BDBAD27316534016F", 1000000); + isc_md_test(md, ISC_MD_SHA1, + TEST_INPUT("01234567012345670123456701234567"), + "DEA356A2CDDD90C7A7ECEDC5EBB563934F460452", 20); + isc_md_test(md, ISC_MD_SHA1, TEST_INPUT("\x5e"), + "5E6F80A34A9798CAFC6A5DB96CC57BA4C4DB59C2", 1); + isc_md_test(md, ISC_MD_SHA1, + TEST_INPUT("\x9a\x7d\xfd\xf1\xec\xea\xd0\x6e\xd6\x46" + "\xaa\x55\xfe\x75\x71\x46"), + "82ABFF6605DBE1C17DEF12A394FA22A82B544A35", 1); + isc_md_test(md, ISC_MD_SHA1, + TEST_INPUT("\xf7\x8f\x92\x14\x1b\xcd\x17\x0a\xe8\x9b" + "\x4f\xba\x15\xa1\xd5\x9f\x3f\xd8\x4d\x22" + "\x3c\x92\x51\xbd\xac\xbb\xae\x61\xd0\x5e" + "\xd1\x15\xa0\x6a\x7c\xe1\x17\xb7\xbe\xea" + "\xd2\x44\x21\xde\xd9\xc3\x25\x92\xbd\x57" + "\xed\xea\xe3\x9c\x39\xfa\x1f\xe8\x94\x6a" + "\x84\xd0\xcf\x1f\x7b\xee\xad\x17\x13\xe2" + "\xe0\x95\x98\x97\x34\x7f\x67\xc8\x0b\x04" + "\x00\xc2\x09\x81\x5d\x6b\x10\xa6\x83\x83" + "\x6f\xd5\x56\x2a\x56\xca\xb1\xa2\x8e\x81" + "\xb6\x57\x66\x54\x63\x1c\xf1\x65\x66\xb8" + "\x6e\x3b\x33\xa1\x08\xb0\x53\x07\xc0\x0a" + "\xff\x14\xa7\x68\xed\x73\x50\x60\x6a\x0f" + "\x85\xe6\xa9\x1d\x39\x6f\x5b\x5c\xbe\x57" + "\x7f\x9b\x38\x80\x7c\x7d\x52\x3d\x6d\x79" + "\x2f\x6e\xbc\x24\xa4\xec\xf2\xb3\xa4\x27" + "\xcd\xbb\xfb"), + "CB0082C8F197D260991BA6A460E76E202BAD27B3", 1); +} + +static void +isc_md_sha224_test(void **state) { + isc_md_t *md = *state; + + isc_md_test(md, ISC_MD_SHA224, NULL, 0, NULL, 0); + isc_md_test(md, ISC_MD_SHA224, TEST_INPUT(""), + "D14A028C2A3A2BC9476102BB288234C415A2B01F828EA62AC5B3E42F", + 1); + isc_md_test(md, ISC_MD_SHA224, TEST_INPUT("abc"), + "23097D223405D8228642A477BDA255B32AADBCE4BDA0B3F7" + "E36C9DA7", + 1); + isc_md_test(md, ISC_MD_SHA224, + TEST_INPUT("abcdbcdecdefdefgefghfghighijhijkijklj" + "klmklmnlmnomnopnopq"), + "75388B16512776CC5DBA5DA1FD890150B0C6455CB4F58B" + "1952522525", + 1); + isc_md_test(md, ISC_MD_SHA224, TEST_INPUT("a"), + "20794655980C91D8BBB4C1EA97618A4BF03F42581948B2" + "EE4EE7AD67", + 1000000); + isc_md_test(md, ISC_MD_SHA224, + TEST_INPUT("01234567012345670123456701234567"), + "567F69F168CD7844E65259CE658FE7AADFA25216E68ECA" + "0EB7AB8262", + 20); + isc_md_test(md, ISC_MD_SHA224, TEST_INPUT("\x07"), + "00ECD5F138422B8AD74C9799FD826C531BAD2FCABC7450" + "BEE2AA8C2A", + 1); + isc_md_test(md, ISC_MD_SHA224, + TEST_INPUT("\x18\x80\x40\x05\xdd\x4f\xbd\x15\x56\x29" + "\x9d\x6f\x9d\x93\xdf\x62"), + "DF90D78AA78821C99B40BA4C966921ACCD8FFB1E98AC38" + "8E56191DB1", + 1); + isc_md_test(md, ISC_MD_SHA224, + TEST_INPUT("\x55\xb2\x10\x07\x9c\x61\xb5\x3a\xdd\x52" + "\x06\x22\xd1\xac\x97\xd5\xcd\xbe\x8c\xb3" + "\x3a\xa0\xae\x34\x45\x17\xbe\xe4\xd7\xba" + "\x09\xab\xc8\x53\x3c\x52\x50\x88\x7a\x43" + "\xbe\xbb\xac\x90\x6c\x2e\x18\x37\xf2\x6b" + "\x36\xa5\x9a\xe3\xbe\x78\x14\xd5\x06\x89" + "\x6b\x71\x8b\x2a\x38\x3e\xcd\xac\x16\xb9" + "\x61\x25\x55\x3f\x41\x6f\xf3\x2c\x66\x74" + "\xc7\x45\x99\xa9\x00\x53\x86\xd9\xce\x11" + "\x12\x24\x5f\x48\xee\x47\x0d\x39\x6c\x1e" + "\xd6\x3b\x92\x67\x0c\xa5\x6e\xc8\x4d\xee" + "\xa8\x14\xb6\x13\x5e\xca\x54\x39\x2b\xde" + "\xdb\x94\x89\xbc\x9b\x87\x5a\x8b\xaf\x0d" + "\xc1\xae\x78\x57\x36\x91\x4a\xb7\xda\xa2" + "\x64\xbc\x07\x9d\x26\x9f\x2c\x0d\x7e\xdd" + "\xd8\x10\xa4\x26\x14\x5a\x07\x76\xf6\x7c" + "\x87\x82\x73"), + "0B31894EC8937AD9B91BDFBCBA294D9ADEFAA18E09305E" + "9F20D5C3A4", + 1); +} + +static void +isc_md_sha256_test(void **state) { + isc_md_t *md = *state; + + isc_md_test(md, ISC_MD_SHA256, NULL, 0, NULL, 0); + isc_md_test(md, ISC_MD_SHA256, TEST_INPUT(""), + "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B93" + "4CA495991B7852B855", + 1); + + isc_md_test(md, ISC_MD_SHA256, TEST_INPUT("abc"), + "BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A" + "9CB410FF61F20015AD", + 1); + isc_md_test(md, ISC_MD_SHA256, + TEST_INPUT("abcdbcdecdefdefgefghfghighijhijkijkljk" + "lmklmnlmnomnopnopq"), + "248D6A61D20638B8E5C026930C3E6039A33CE45964FF21" + "67F6ECEDD419DB06C1", + 1); + isc_md_test(md, ISC_MD_SHA256, TEST_INPUT("a"), + "CDC76E5C9914FB9281A1C7E284D73E67F1809A48A49720" + "0E046D39CCC7112CD0", + 1000000); + isc_md_test(md, ISC_MD_SHA256, + TEST_INPUT("01234567012345670123456701234567"), + "594847328451BDFA85056225462CC1D867D877FB388DF0" + "CE35F25AB5562BFBB5", + 20); + isc_md_test(md, ISC_MD_SHA256, TEST_INPUT("\x19"), + "68AA2E2EE5DFF96E3355E6C7EE373E3D6A4E17F75F9518" + "D843709C0C9BC3E3D4", + 1); + isc_md_test(md, ISC_MD_SHA256, + TEST_INPUT("\xe3\xd7\x25\x70\xdc\xdd\x78\x7c\xe3" + "\x88\x7a\xb2\xcd\x68\x46\x52"), + "175EE69B02BA9B58E2B0A5FD13819CEA573F3940A94F82" + "5128CF4209BEABB4E8", + 1); + isc_md_test(md, ISC_MD_SHA256, + TEST_INPUT("\x83\x26\x75\x4e\x22\x77\x37\x2f\x4f\xc1" + "\x2b\x20\x52\x7a\xfe\xf0\x4d\x8a\x05\x69" + "\x71\xb1\x1a\xd5\x71\x23\xa7\xc1\x37\x76" + "\x00\x00\xd7\xbe\xf6\xf3\xc1\xf7\xa9\x08" + "\x3a\xa3\x9d\x81\x0d\xb3\x10\x77\x7d\xab" + "\x8b\x1e\x7f\x02\xb8\x4a\x26\xc7\x73\x32" + "\x5f\x8b\x23\x74\xde\x7a\x4b\x5a\x58\xcb" + "\x5c\x5c\xf3\x5b\xce\xe6\xfb\x94\x6e\x5b" + "\xd6\x94\xfa\x59\x3a\x8b\xeb\x3f\x9d\x65" + "\x92\xec\xed\xaa\x66\xca\x82\xa2\x9d\x0c" + "\x51\xbc\xf9\x33\x62\x30\xe5\xd7\x84\xe4" + "\xc0\xa4\x3f\x8d\x79\xa3\x0a\x16\x5c\xba" + "\xbe\x45\x2b\x77\x4b\x9c\x71\x09\xa9\x7d" + "\x13\x8f\x12\x92\x28\x96\x6f\x6c\x0a\xdc" + "\x10\x6a\xad\x5a\x9f\xdd\x30\x82\x57\x69" + "\xb2\xc6\x71\xaf\x67\x59\xdf\x28\xeb\x39" + "\x3d\x54\xd6"), + "97DBCA7DF46D62C8A422C941DD7E835B8AD3361763F7E9" + "B2D95F4F0DA6E1CCBC", + 1); +} + +static void +isc_md_sha384_test(void **state) { + isc_md_t *md = *state; + + isc_md_test(md, ISC_MD_SHA384, NULL, 0, NULL, 0); + isc_md_test(md, ISC_MD_SHA384, TEST_INPUT(""), + "38B060A751AC96384CD9327EB1B1E36A21FDB71114BE07" + "434C0CC7BF63F6E1DA274EDEBFE76F65FBD51AD2F14898" + "B95B" + "", + 1); + isc_md_test(md, ISC_MD_SHA384, TEST_INPUT("abc"), + "CB00753F45A35E8BB5A03D699AC65007272C32AB0EDED1" + "631A8B605A43FF5BED8086072BA1E7CC2358BAEC" + "A134C825A7", + 1); + isc_md_test(md, ISC_MD_SHA384, + TEST_INPUT("abcdefghbcdefghicdefghijdefghijkefghijkl" + "fghijklmghijklmnhijklmnoijklmnopjklmnopq" + "klmnopqrlmnopqrsmnopqrstnopqrstu"), + "09330C33F71147E83D192FC782CD1B4753111B173B3B05" + "D22FA08086E3B0F712FCC7C71A557E2DB966C3E9" + "FA91746039", + 1); + isc_md_test(md, ISC_MD_SHA384, TEST_INPUT("a"), + "9D0E1809716474CB086E834E310A4A1CED149E9C00F248" + "527972CEC5704C2A5B07B8B3DC38ECC4EBAE97DD" + "D87F3D8985", + 1000000); + isc_md_test(md, ISC_MD_SHA384, + TEST_INPUT("01234567012345670123456701234567"), + "2FC64A4F500DDB6828F6A3430B8DD72A368EB7F3A8322A" + "70BC84275B9C0B3AB00D27A5CC3C2D224AA6B61A" + "0D79FB4596", + 20); + isc_md_test(md, ISC_MD_SHA384, TEST_INPUT("\xb9"), + "BC8089A19007C0B14195F4ECC74094FEC64F01F9092928" + "2C2FB392881578208AD466828B1C6C283D2722CF" + "0AD1AB6938", + 1); + isc_md_test(md, ISC_MD_SHA384, + TEST_INPUT("\xa4\x1c\x49\x77\x79\xc0\x37\x5f\xf1" + "\x0a\x7f\x4e\x08\x59\x17\x39"), + "C9A68443A005812256B8EC76B00516F0DBB74FAB26D665" + "913F194B6FFB0E91EA9967566B58109CBC675CC2" + "08E4C823F7", + 1); + isc_md_test(md, ISC_MD_SHA384, + TEST_INPUT("\x39\x96\x69\xe2\x8f\x6b\x9c\x6d\xbc\xbb" + "\x69\x12\xec\x10\xff\xcf\x74\x79\x03\x49" + "\xb7\xdc\x8f\xbe\x4a\x8e\x7b\x3b\x56\x21" + "\xdb\x0f\x3e\x7d\xc8\x7f\x82\x32\x64\xbb" + "\xe4\x0d\x18\x11\xc9\xea\x20\x61\xe1\xc8" + "\x4a\xd1\x0a\x23\xfa\xc1\x72\x7e\x72\x02" + "\xfc\x3f\x50\x42\xe6\xbf\x58\xcb\xa8\xa2" + "\x74\x6e\x1f\x64\xf9\xb9\xea\x35\x2c\x71" + "\x15\x07\x05\x3c\xf4\xe5\x33\x9d\x52\x86" + "\x5f\x25\xcc\x22\xb5\xe8\x77\x84\xa1\x2f" + "\xc9\x61\xd6\x6c\xb6\xe8\x95\x73\x19\x9a" + "\x2c\xe6\x56\x5c\xbd\xf1\x3d\xca\x40\x38" + "\x32\xcf\xcb\x0e\x8b\x72\x11\xe8\x3a\xf3" + "\x2a\x11\xac\x17\x92\x9f\xf1\xc0\x73\xa5" + "\x1c\xc0\x27\xaa\xed\xef\xf8\x5a\xad\x7c" + "\x2b\x7c\x5a\x80\x3e\x24\x04\xd9\x6d\x2a" + "\x77\x35\x7b\xda\x1a\x6d\xae\xed\x17\x15" + "\x1c\xb9\xbc\x51\x25\xa4\x22\xe9\x41\xde" + "\x0c\xa0\xfc\x50\x11\xc2\x3e\xcf\xfe\xfd" + "\xd0\x96\x76\x71\x1c\xf3\xdb\x0a\x34\x40" + "\x72\x0e\x16\x15\xc1\xf2\x2f\xbc\x3c\x72" + "\x1d\xe5\x21\xe1\xb9\x9b\xa1\xbd\x55\x77" + "\x40\x86\x42\x14\x7e\xd0\x96"), + "4F440DB1E6EDD2899FA335F09515AA025EE177A79F4B4A" + "AF38E42B5C4DE660F5DE8FB2A5B2FBD2A3CBFFD2" + "0CFF1288C0", + 1); +} + +static void +isc_md_sha512_test(void **state) { + isc_md_t *md = *state; + + isc_md_test(md, ISC_MD_SHA512, NULL, 0, NULL, 0); + isc_md_test(md, ISC_MD_SHA512, TEST_INPUT(""), + "CF83E1357EEFB8BDF1542850D66D8007D620E4050B5715" + "DC83F4A921D36CE9CE47D0D13C5D85F2B0FF8318D2877E" + "EC2F63B931BD47417A81A538327AF927DA3E", + 1); + isc_md_test(md, ISC_MD_SHA512, TEST_INPUT("abc"), + "DDAF35A193617ABACC417349AE20413112E6FA4E89A97E" + "A20A9EEEE64B55D39A2192992A274FC1A836BA3C" + "23A3FEEBBD454D4423643CE80E2A9AC94FA54CA49F", + 1); + isc_md_test(md, ISC_MD_SHA512, + TEST_INPUT("abcdefghbcdefghicdefghijdefghijkefghijkl" + "fghijklmghijklmnhijklmnoijklmnopjklmnopq" + "klmnopqrlmnopqrsmnopqrstnopqrstu"), + "8E959B75DAE313DA8CF4F72814FC143F8F7779C6EB9F7F" + "A17299AEADB6889018501D289E4900F7E4331B99" + "DEC4B5433AC7D329EEB6DD26545E96E55B874BE909", + 1); + isc_md_test(md, ISC_MD_SHA512, TEST_INPUT("a"), + "E718483D0CE769644E2E42C7BC15B4638E1F98B13B2044" + "285632A803AFA973EBDE0FF244877EA60A4CB043" + "2CE577C31BEB009C5C2C49AA2E4EADB217AD8CC09B", + 1000000); + isc_md_test(md, ISC_MD_SHA512, + TEST_INPUT("01234567012345670123456701234567"), + "89D05BA632C699C31231DED4FFC127D5A894DAD412C0E0" + "24DB872D1ABD2BA8141A0F85072A9BE1E2AA04CF" + "33C765CB510813A39CD5A84C4ACAA64D3F3FB7BAE9", + 20); + isc_md_test(md, ISC_MD_SHA512, TEST_INPUT("\xD0"), + "9992202938E882E73E20F6B69E68A0A7149090423D93C8" + "1BAB3F21678D4ACEEEE50E4E8CAFADA4C85A54EA" + "8306826C4AD6E74CECE9631BFA8A549B4AB3FBBA15", + 1); + isc_md_test(md, ISC_MD_SHA512, + TEST_INPUT("\x8d\x4e\x3c\x0e\x38\x89\x19\x14\x91\x81" + "\x6e\x9d\x98\xbf\xf0\xa0"), + "CB0B67A4B8712CD73C9AABC0B199E9269B20844AFB75AC" + "BDD1C153C9828924C3DDEDAAFE669C5FDD0BC66F" + "630F6773988213EB1B16F517AD0DE4B2F0C95C90F8", + 1); + isc_md_test(md, ISC_MD_SHA512, + TEST_INPUT("\xa5\x5f\x20\xc4\x11\xaa\xd1\x32\x80\x7a" + "\x50\x2d\x65\x82\x4e\x31\xa2\x30\x54\x32" + "\xaa\x3d\x06\xd3\xe2\x82\xa8\xd8\x4e\x0d" + "\xe1\xde\x69\x74\xbf\x49\x54\x69\xfc\x7f" + "\x33\x8f\x80\x54\xd5\x8c\x26\xc4\x93\x60" + "\xc3\xe8\x7a\xf5\x65\x23\xac\xf6\xd8\x9d" + "\x03\xe5\x6f\xf2\xf8\x68\x00\x2b\xc3\xe4" + "\x31\xed\xc4\x4d\xf2\xf0\x22\x3d\x4b\xb3" + "\xb2\x43\x58\x6e\x1a\x7d\x92\x49\x36\x69" + "\x4f\xcb\xba\xf8\x8d\x95\x19\xe4\xeb\x50" + "\xa6\x44\xf8\xe4\xf9\x5e\xb0\xea\x95\xbc" + "\x44\x65\xc8\x82\x1a\xac\xd2\xfe\x15\xab" + "\x49\x81\x16\x4b\xbb\x6d\xc3\x2f\x96\x90" + "\x87\xa1\x45\xb0\xd9\xcc\x9c\x67\xc2\x2b" + "\x76\x32\x99\x41\x9c\xc4\x12\x8b\xe9\xa0" + "\x77\xb3\xac\xe6\x34\x06\x4e\x6d\x99\x28" + "\x35\x13\xdc\x06\xe7\x51\x5d\x0d\x73\x13" + "\x2e\x9a\x0d\xc6\xd3\xb1\xf8\xb2\x46\xf1" + "\xa9\x8a\x3f\xc7\x29\x41\xb1\xe3\xbb\x20" + "\x98\xe8\xbf\x16\xf2\x68\xd6\x4f\x0b\x0f" + "\x47\x07\xfe\x1e\xa1\xa1\x79\x1b\xa2\xf3" + "\xc0\xc7\x58\xe5\xf5\x51\x86\x3a\x96\xc9" + "\x49\xad\x47\xd7\xfb\x40\xd2"), + "C665BEFB36DA189D78822D10528CBF3B12B3EEF7260399" + "09C1A16A270D48719377966B957A878E72058477" + "9A62825C18DA26415E49A7176A894E7510FD1451F5", + 1); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + /* isc_md_new() */ + cmocka_unit_test(isc_md_new_test), + + /* isc_md_init() */ + cmocka_unit_test_setup_teardown(isc_md_init_test, _reset, + _reset), + + /* isc_md_reset() */ + cmocka_unit_test_setup_teardown(isc_md_reset_test, _reset, + _reset), + + /* isc_md_init() -> isc_md_update() -> isc_md_final() */ + cmocka_unit_test(isc_md_md5_test), + cmocka_unit_test(isc_md_sha1_test), + cmocka_unit_test(isc_md_sha224_test), + cmocka_unit_test(isc_md_sha256_test), + cmocka_unit_test(isc_md_sha384_test), + cmocka_unit_test(isc_md_sha512_test), + + cmocka_unit_test_setup_teardown(isc_md_update_test, _reset, + _reset), + cmocka_unit_test_setup_teardown(isc_md_final_test, _reset, + _reset), + + cmocka_unit_test(isc_md_free_test), + }; + + return (cmocka_run_group_tests(tests, _setup, _teardown)); +} + +#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 */ diff --git a/lib/isc/tests/mem_test.c b/lib/isc/tests/mem_test.c new file mode 100644 index 0000000..9b180cd --- /dev/null +++ b/lib/isc/tests/mem_test.c @@ -0,0 +1,477 @@ +/* + * 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 <fcntl.h> +#include <sched.h> /* IWYU pragma: keep */ +#include <setjmp.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdlib.h> +#include <unistd.h> + +#define UNIT_TESTING +#include <cmocka.h> + +#include <isc/file.h> +#include <isc/mem.h> +#include <isc/mutex.h> +#include <isc/os.h> +#include <isc/print.h> +#include <isc/result.h> +#include <isc/stdio.h> +#include <isc/thread.h> +#include <isc/time.h> +#include <isc/util.h> + +#include "../mem_p.h" +#include "isctest.h" + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = isc_test_begin(NULL, true, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + isc_test_end(); + + return (0); +} + +#define MP1_FREEMAX 10 +#define MP1_FILLCNT 10 +#define MP1_MAXALLOC 30 + +#define MP2_FREEMAX 25 +#define MP2_FILLCNT 25 + +/* general memory system tests */ +static void +isc_mem_test(void **state) { + void *items1[50]; + void *items2[50]; + void *tmp; + isc_mempool_t *mp1 = NULL, *mp2 = NULL; + unsigned int i, j; + int rval; + + UNUSED(state); + + isc_mempool_create(test_mctx, 24, &mp1); + isc_mempool_create(test_mctx, 31, &mp2); + + isc_mempool_setfreemax(mp1, MP1_FREEMAX); + isc_mempool_setfillcount(mp1, MP1_FILLCNT); + isc_mempool_setmaxalloc(mp1, MP1_MAXALLOC); + + /* + * Allocate MP1_MAXALLOC items from the pool. This is our max. + */ + for (i = 0; i < MP1_MAXALLOC; i++) { + items1[i] = isc_mempool_get(mp1); + assert_non_null(items1[i]); + } + + /* + * Try to allocate one more. This should fail. + */ + tmp = isc_mempool_get(mp1); + assert_null(tmp); + + /* + * Free the first 11 items. Verify that there are 10 free items on + * the free list (which is our max). + */ + for (i = 0; i < 11; i++) { + isc_mempool_put(mp1, items1[i]); + items1[i] = NULL; + } + +#if !__SANITIZE_ADDRESS__ + rval = isc_mempool_getfreecount(mp1); + assert_int_equal(rval, 10); +#endif /* !__SANITIZE_ADDRESS__ */ + + rval = isc_mempool_getallocated(mp1); + assert_int_equal(rval, 19); + + /* + * Now, beat up on mp2 for a while. Allocate 50 items, then free + * them, then allocate 50 more, etc. + */ + + isc_mempool_setfreemax(mp2, 25); + isc_mempool_setfillcount(mp2, 25); + + for (j = 0; j < 500000; j++) { + for (i = 0; i < 50; i++) { + items2[i] = isc_mempool_get(mp2); + assert_non_null(items2[i]); + } + for (i = 0; i < 50; i++) { + isc_mempool_put(mp2, items2[i]); + items2[i] = NULL; + } + } + + /* + * Free all the other items and blow away this pool. + */ + for (i = 11; i < MP1_MAXALLOC; i++) { + isc_mempool_put(mp1, items1[i]); + items1[i] = NULL; + } + + isc_mempool_destroy(&mp1); + isc_mempool_destroy(&mp2); + + isc_mempool_create(test_mctx, 2, &mp1); + + tmp = isc_mempool_get(mp1); + assert_non_null(tmp); + + isc_mempool_put(mp1, tmp); + + isc_mempool_destroy(&mp1); +} + +/* test TotalUse calculation */ +static void +isc_mem_total_test(void **state) { + isc_mem_t *mctx2 = NULL; + size_t before, after; + ssize_t diff; + int i; + + UNUSED(state); + + /* Local alloc, free */ + mctx2 = NULL; + isc_mem_create(&mctx2); + + before = isc_mem_total(mctx2); + + for (i = 0; i < 100000; i++) { + void *ptr; + + ptr = isc_mem_allocate(mctx2, 2048); + isc_mem_free(mctx2, ptr); + } + + after = isc_mem_total(mctx2); + diff = after - before; + + /* 2048 +8 bytes extra for size_info */ + assert_int_equal(diff, (2048 + 8) * 100000); + + /* ISC_MEMFLAG_INTERNAL */ + + before = isc_mem_total(test_mctx); + + for (i = 0; i < 100000; i++) { + void *ptr; + + ptr = isc_mem_allocate(test_mctx, 2048); + isc_mem_free(test_mctx, ptr); + } + + after = isc_mem_total(test_mctx); + diff = after - before; + + /* 2048 +8 bytes extra for size_info */ + assert_int_equal(diff, (2048 + 8) * 100000); + + isc_mem_destroy(&mctx2); +} + +/* test InUse calculation */ +static void +isc_mem_inuse_test(void **state) { + isc_mem_t *mctx2 = NULL; + size_t before, after; + ssize_t diff; + void *ptr; + + UNUSED(state); + + mctx2 = NULL; + isc_mem_create(&mctx2); + + before = isc_mem_inuse(mctx2); + ptr = isc_mem_allocate(mctx2, 1024000); + isc_mem_free(mctx2, ptr); + after = isc_mem_inuse(mctx2); + + diff = after - before; + + assert_int_equal(diff, 0); + + isc_mem_destroy(&mctx2); +} + +#if ISC_MEM_TRACKLINES + +/* test mem with no flags */ +static void +isc_mem_noflags_test(void **state) { + isc_result_t result; + isc_mem_t *mctx2 = NULL; + char buf[4096], *p, *q; + FILE *f; + void *ptr; + + result = isc_stdio_open("mem.output", "w", &f); + assert_int_equal(result, ISC_R_SUCCESS); + + UNUSED(state); + + isc_mem_create(&mctx2); + isc_mem_debugging = 0; + ptr = isc_mem_get(mctx2, 2048); + assert_non_null(ptr); + isc__mem_printactive(mctx2, f); + isc_mem_put(mctx2, ptr, 2048); + isc_mem_destroy(&mctx2); + isc_stdio_close(f); + + memset(buf, 0, sizeof(buf)); + result = isc_stdio_open("mem.output", "r", &f); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_stdio_read(buf, sizeof(buf), 1, f, NULL); + assert_int_equal(result, ISC_R_EOF); + isc_stdio_close(f); + isc_file_remove("mem.output"); + + buf[sizeof(buf) - 1] = 0; + + p = strchr(buf, '\n'); + assert_non_null(p); + assert_in_range(p, 0, buf + sizeof(buf) - 3); + p += 2; + q = strchr(p, '\n'); + assert_non_null(q); + *q = '\0'; + assert_string_equal(p, "None."); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; +} + +/* test mem with record flag */ +static void +isc_mem_recordflag_test(void **state) { + isc_result_t result; + isc_mem_t *mctx2 = NULL; + char buf[4096], *p; + FILE *f; + void *ptr; + + result = isc_stdio_open("mem.output", "w", &f); + assert_int_equal(result, ISC_R_SUCCESS); + + UNUSED(state); + + isc_mem_create(&mctx2); + ptr = isc_mem_get(mctx2, 2048); + assert_non_null(ptr); + isc__mem_printactive(mctx2, f); + isc_mem_put(mctx2, ptr, 2048); + isc_mem_destroy(&mctx2); + isc_stdio_close(f); + + memset(buf, 0, sizeof(buf)); + result = isc_stdio_open("mem.output", "r", &f); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_stdio_read(buf, sizeof(buf), 1, f, NULL); + assert_int_equal(result, ISC_R_EOF); + isc_stdio_close(f); + isc_file_remove("mem.output"); + + buf[sizeof(buf) - 1] = 0; + + p = strchr(buf, '\n'); + assert_non_null(p); + assert_in_range(p, 0, buf + sizeof(buf) - 3); + assert_memory_equal(p + 2, "ptr ", 4); + p = strchr(p + 1, '\n'); + assert_non_null(p); + assert_int_equal(strlen(p), 1); +} + +/* test mem with trace flag */ +static void +isc_mem_traceflag_test(void **state) { + isc_result_t result; + isc_mem_t *mctx2 = NULL; + char buf[4096], *p; + FILE *f; + void *ptr; + + /* redirect stderr so we can check trace output */ + f = freopen("mem.output", "w", stderr); + assert_non_null(f); + + UNUSED(state); + + isc_mem_create(&mctx2); + isc_mem_debugging = ISC_MEM_DEBUGTRACE; + ptr = isc_mem_get(mctx2, 2048); + assert_non_null(ptr); + isc__mem_printactive(mctx2, f); + isc_mem_put(mctx2, ptr, 2048); + isc_mem_destroy(&mctx2); + isc_stdio_close(f); + + memset(buf, 0, sizeof(buf)); + result = isc_stdio_open("mem.output", "r", &f); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_stdio_read(buf, sizeof(buf), 1, f, NULL); + assert_int_equal(result, ISC_R_EOF); + isc_stdio_close(f); + isc_file_remove("mem.output"); + + /* return stderr to TTY so we can see errors */ + f = freopen("/dev/tty", "w", stderr); + + buf[sizeof(buf) - 1] = 0; + + assert_memory_equal(buf, "add ", 4); + p = strchr(buf, '\n'); + assert_non_null(p); + p = strchr(p + 1, '\n'); + assert_non_null(p); + assert_in_range(p, 0, buf + sizeof(buf) - 3); + assert_memory_equal(p + 2, "ptr ", 4); + p = strchr(p + 1, '\n'); + assert_non_null(p); + assert_memory_equal(p + 1, "del ", 4); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; +} +#endif /* if ISC_MEM_TRACKLINES */ + +#if !defined(__SANITIZE_THREAD__) + +#define ITERS 512 +#define NUM_ITEMS 1024 /* 768 */ +#define ITEM_SIZE 65534 + +static isc_threadresult_t +mem_thread(isc_threadarg_t arg) { + void *items[NUM_ITEMS]; + size_t size = *((size_t *)arg); + + for (int i = 0; i < ITERS; i++) { + for (int j = 0; j < NUM_ITEMS; j++) { + items[j] = isc_mem_get(test_mctx, size); + } + for (int j = 0; j < NUM_ITEMS; j++) { + isc_mem_put(test_mctx, items[j], size); + } + } + + return ((isc_threadresult_t)0); +} + +static void +isc_mem_benchmark(void **state) { + int nthreads = ISC_MAX(ISC_MIN(isc_os_ncpus(), 32), 1); + isc_thread_t threads[32]; + isc_time_t ts1, ts2; + double t; + isc_result_t result; + size_t size = ITEM_SIZE; + + UNUSED(state); + + result = isc_time_now(&ts1); + assert_int_equal(result, ISC_R_SUCCESS); + + for (int i = 0; i < nthreads; i++) { + isc_thread_create(mem_thread, &size, &threads[i]); + size = size / 2; + } + for (int i = 0; i < nthreads; i++) { + isc_thread_join(threads[i], NULL); + } + + result = isc_time_now(&ts2); + assert_int_equal(result, ISC_R_SUCCESS); + + t = isc_time_microdiff(&ts2, &ts1); + + printf("[ TIME ] isc_mem_benchmark: " + "%d isc_mem_{get,put} calls, %f seconds, %f calls/second\n", + nthreads * ITERS * NUM_ITEMS, t / 1000000.0, + (nthreads * ITERS * NUM_ITEMS) / (t / 1000000.0)); +} + +#endif /* __SANITIZE_THREAD */ + +/* + * Main + */ + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(isc_mem_test, _setup, + _teardown), + cmocka_unit_test_setup_teardown(isc_mem_total_test, _setup, + _teardown), + cmocka_unit_test_setup_teardown(isc_mem_inuse_test, _setup, + _teardown), + +#if !defined(__SANITIZE_THREAD__) + cmocka_unit_test_setup_teardown(isc_mem_benchmark, _setup, + _teardown), +#endif /* __SANITIZE_THREAD__ */ +#if ISC_MEM_TRACKLINES + cmocka_unit_test_setup_teardown(isc_mem_noflags_test, _setup, + _teardown), + cmocka_unit_test_setup_teardown(isc_mem_recordflag_test, _setup, + _teardown), + /* + * traceflag_test closes stderr, which causes weird + * side effects for any next test trying to use libuv. + * This test has to be the last one to avoid problems. + */ + cmocka_unit_test_setup_teardown(isc_mem_traceflag_test, _setup, + _teardown), +#endif /* if ISC_MEM_TRACKLINES */ + }; + + 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 */ diff --git a/lib/isc/tests/netaddr_test.c b/lib/isc/tests/netaddr_test.c new file mode 100644 index 0000000..473e747 --- /dev/null +++ b/lib/isc/tests/netaddr_test.c @@ -0,0 +1,159 @@ +/* + * 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 <setjmp.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define UNIT_TESTING +#include <cmocka.h> + +#include <isc/netaddr.h> +#include <isc/sockaddr.h> +#include <isc/util.h> + +/* test isc_netaddr_isnetzero() */ +static void +netaddr_isnetzero(void **state) { + unsigned int i; + struct in_addr ina; + struct { + const char *address; + bool expect; + } tests[] = { { "0.0.0.0", true }, { "0.0.0.1", true }, + { "0.0.1.2", true }, { "0.1.2.3", true }, + { "10.0.0.0", false }, { "10.9.0.0", false }, + { "10.9.8.0", false }, { "10.9.8.7", false }, + { "127.0.0.0", false }, { "127.0.0.1", false } }; + bool result; + isc_netaddr_t netaddr; + + UNUSED(state); + + for (i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { + ina.s_addr = inet_addr(tests[i].address); + isc_netaddr_fromin(&netaddr, &ina); + result = isc_netaddr_isnetzero(&netaddr); + assert_int_equal(result, tests[i].expect); + } +} + +/* test isc_netaddr_masktoprefixlen() calculates correct prefix lengths */ +static void +netaddr_masktoprefixlen(void **state) { + struct in_addr na_a; + struct in_addr na_b; + struct in_addr na_c; + struct in_addr na_d; + isc_netaddr_t ina_a; + isc_netaddr_t ina_b; + isc_netaddr_t ina_c; + isc_netaddr_t ina_d; + unsigned int plen; + + UNUSED(state); + + assert_true(inet_pton(AF_INET, "0.0.0.0", &na_a) >= 0); + assert_true(inet_pton(AF_INET, "255.255.255.254", &na_b) >= 0); + assert_true(inet_pton(AF_INET, "255.255.255.255", &na_c) >= 0); + assert_true(inet_pton(AF_INET, "255.255.255.0", &na_d) >= 0); + + isc_netaddr_fromin(&ina_a, &na_a); + isc_netaddr_fromin(&ina_b, &na_b); + isc_netaddr_fromin(&ina_c, &na_c); + isc_netaddr_fromin(&ina_d, &na_d); + + assert_int_equal(isc_netaddr_masktoprefixlen(&ina_a, &plen), + ISC_R_SUCCESS); + assert_int_equal(plen, 0); + + assert_int_equal(isc_netaddr_masktoprefixlen(&ina_b, &plen), + ISC_R_SUCCESS); + assert_int_equal(plen, 31); + + assert_int_equal(isc_netaddr_masktoprefixlen(&ina_c, &plen), + ISC_R_SUCCESS); + assert_int_equal(plen, 32); + + assert_int_equal(isc_netaddr_masktoprefixlen(&ina_d, &plen), + ISC_R_SUCCESS); + assert_int_equal(plen, 24); +} + +/* check multicast addresses are detected properly */ +static void +netaddr_multicast(void **state) { + unsigned int i; + struct { + int family; + const char *addr; + bool is_multicast; + } tests[] = { + { AF_INET, "1.2.3.4", false }, { AF_INET, "4.3.2.1", false }, + { AF_INET, "224.1.1.1", true }, { AF_INET, "1.1.1.244", false }, + { AF_INET6, "::1", false }, { AF_INET6, "ff02::1", true } + }; + + UNUSED(state); + + for (i = 0; i < sizeof(tests) / sizeof(tests[0]); i++) { + isc_netaddr_t na; + struct in_addr in; + struct in6_addr in6; + int r; + + if (tests[i].family == AF_INET) { + r = inet_pton(AF_INET, tests[i].addr, + (unsigned char *)&in); + assert_int_equal(r, 1); + isc_netaddr_fromin(&na, &in); + } else { + r = inet_pton(AF_INET6, tests[i].addr, + (unsigned char *)&in6); + assert_int_equal(r, 1); + isc_netaddr_fromin6(&na, &in6); + } + + assert_int_equal(isc_netaddr_ismulticast(&na), + tests[i].is_multicast); + } +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(netaddr_isnetzero), + cmocka_unit_test(netaddr_masktoprefixlen), + cmocka_unit_test(netaddr_multicast), + }; + + 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 */ diff --git a/lib/isc/tests/netmgr_test.c b/lib/isc/tests/netmgr_test.c new file mode 100644 index 0000000..8e30359 --- /dev/null +++ b/lib/isc/tests/netmgr_test.c @@ -0,0 +1,2218 @@ +/* + * 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 <sched.h> /* IWYU pragma: keep */ +#include <setjmp.h> +#include <signal.h> +#include <stdarg.h> +#include <stdlib.h> +#include <unistd.h> +#include <uv.h> + +#define UNIT_TESTING +#include <cmocka.h> + +#include <isc/nonce.h> +#include <isc/os.h> +#include <isc/quota.h> +#include <isc/refcount.h> +#include <isc/sockaddr.h> +#include <isc/thread.h> +#include <isc/util.h> + +#include "uv_wrap.h" +#define KEEP_BEFORE + +#include "../netmgr/netmgr-int.h" +#include "../netmgr/udp.c" +#include "../netmgr/uv-compat.c" +#include "../netmgr/uv-compat.h" +#include "../netmgr_p.h" +#include "isctest.h" + +typedef void (*stream_connect_function)(isc_nm_t *nm); + +static void +connect_connect_cb(isc_nmhandle_t *handle, isc_result_t eresult, void *cbarg); +static void +connect_read_cb(isc_nmhandle_t *handle, isc_result_t eresult, + isc_region_t *region, void *cbarg); + +isc_nm_t *listen_nm = NULL; +isc_nm_t *connect_nm = NULL; + +static isc_sockaddr_t udp_listen_addr; +static isc_sockaddr_t udp_connect_addr; + +static isc_sockaddr_t tcp_listen_addr; +static isc_sockaddr_t tcp_connect_addr; + +static uint64_t send_magic = 0; +static uint64_t stop_magic = 0; + +static isc_region_t send_msg = { .base = (unsigned char *)&send_magic, + .length = sizeof(send_magic) }; + +static isc_region_t stop_msg = { .base = (unsigned char *)&stop_magic, + .length = sizeof(stop_magic) }; + +static atomic_bool do_send = false; +static unsigned int workers = 0; + +static atomic_int_fast64_t nsends; +static int_fast64_t esends; /* expected sends */ + +static atomic_int_fast64_t ssends = 0; +static atomic_int_fast64_t sreads = 0; +static atomic_int_fast64_t saccepts = 0; + +static atomic_int_fast64_t cconnects = 0; +static atomic_int_fast64_t csends = 0; +static atomic_int_fast64_t creads = 0; +static atomic_int_fast64_t ctimeouts = 0; + +static isc_refcount_t active_cconnects; +static isc_refcount_t active_csends; +static isc_refcount_t active_creads; +static isc_refcount_t active_ssends; +static isc_refcount_t active_sreads; + +static isc_quota_t listener_quota; +static atomic_bool check_listener_quota; + +static bool skip_long_tests = false; + +static bool allow_send_back = false; +static bool noanswer = false; + +static isc_nm_recv_cb_t connect_readcb = NULL; + +#define SKIP_IN_CI \ + if (skip_long_tests) { \ + skip(); \ + return; \ + } + +#define NSENDS 100 + +/* Timeout for soft-timeout tests (0.05 seconds) */ +#define T_SOFT 50 + +/* Timeouts in miliseconds */ +#define T_INIT 120 * 1000 +#define T_IDLE 120 * 1000 +#define T_KEEPALIVE 120 * 1000 +#define T_ADVERTISED 120 * 1000 +#define T_CONNECT 30 * 1000 + +/* Wait for 1 second (1000 * 1000 microseconds) */ +#define WAIT_REPEATS 1000 +#define T_WAIT 1000 /* In microseconds */ + +#define WAIT_FOR(v, op, val) \ + { \ + X(v); \ + int_fast64_t __r = WAIT_REPEATS; \ + int_fast64_t __o = 0; \ + do { \ + int_fast64_t __l = atomic_load(&v); \ + if (__l op val) { \ + break; \ + }; \ + if (__o == __l) { \ + __r--; \ + } else { \ + __r = WAIT_REPEATS; \ + } \ + __o = __l; \ + isc_test_nap(T_WAIT); \ + } while (__r > 0); \ + X(v); \ + P(__r); \ + assert_true(atomic_load(&v) op val); \ + } + +#define WAIT_FOR_EQ(v, val) WAIT_FOR(v, ==, val) +#define WAIT_FOR_NE(v, val) WAIT_FOR(v, !=, val) +#define WAIT_FOR_LE(v, val) WAIT_FOR(v, <=, val) +#define WAIT_FOR_LT(v, val) WAIT_FOR(v, <, val) +#define WAIT_FOR_GE(v, val) WAIT_FOR(v, >=, val) +#define WAIT_FOR_GT(v, val) WAIT_FOR(v, >, val) + +#define DONE() atomic_store(&do_send, false); + +#define CHECK_RANGE_FULL(v) \ + { \ + int __v = atomic_load(&v); \ + assert_true(__v > 1); \ + } + +#define CHECK_RANGE_HALF(v) \ + { \ + int __v = atomic_load(&v); \ + assert_true(__v > 1); \ + } + +/* Enable this to print values while running tests */ +#undef PRINT_DEBUG +#ifdef PRINT_DEBUG +#define X(v) \ + fprintf(stderr, "%s:%s:%d:%s = %" PRId64 "\n", __func__, __FILE__, \ + __LINE__, #v, atomic_load(&v)) +#define P(v) fprintf(stderr, #v " = %" PRId64 "\n", v) +#define F() \ + fprintf(stderr, "%s(%p, %s, %p)\n", __func__, handle, \ + isc_result_totext(eresult), cbarg) +#else +#define X(v) +#define P(v) +#define F() +#endif + +#define atomic_assert_int_eq(val, exp) assert_int_equal(atomic_load(&val), exp) +#define atomic_assert_int_ne(val, exp) \ + assert_int_not_equal(atomic_load(&val), exp) +#define atomic_assert_int_le(val, exp) assert_true(atomic_load(&val) <= exp) +#define atomic_assert_int_lt(val, exp) assert_true(atomic_load(&val) > exp) +#define atomic_assert_int_ge(val, exp) assert_true(atomic_load(&val) >= exp) +#define atomic_assert_int_gt(val, exp) assert_true(atomic_load(&val) > exp) + +static int +_setup(void **state __attribute__((unused))) { + char *p = NULL; + + if (workers == 0) { + workers = isc_os_ncpus(); + } + p = getenv("ISC_TASK_WORKERS"); + if (p != NULL) { + workers = atoi(p); + } + INSIST(workers != 0); + + if (isc_test_begin(NULL, false, workers) != ISC_R_SUCCESS) { + return (-1); + } + + signal(SIGPIPE, SIG_IGN); + + if (getenv("CI") == NULL || getenv("CI_ENABLE_ALL_TESTS") != NULL) { + esends = NSENDS * workers; + } else { + esends = workers; + skip_long_tests = true; + } + + return (0); +} + +static int +_teardown(void **state __attribute__((unused))) { + isc_test_end(); + + return (0); +} + +static int +setup_ephemeral_port(isc_sockaddr_t *addr, sa_family_t family) { + socklen_t addrlen = sizeof(*addr); + uv_os_sock_t fd; + 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()"); + isc__nm_closesocket(fd); + return (r); + } + + r = getsockname(fd, (struct sockaddr *)&addr->type.sa, &addrlen); + if (r != 0) { + perror("setup_ephemeral_port: getsockname()"); + isc__nm_closesocket(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"); + isc__nm_closesocket(fd); + return (r); + } +#endif + + return (fd); +} + +static int +nm_setup(void **state __attribute__((unused))) { + uv_os_sock_t tcp_listen_sock = -1; + uv_os_sock_t udp_listen_sock = -1; + + udp_connect_addr = (isc_sockaddr_t){ .length = 0 }; + isc_sockaddr_fromin6(&udp_connect_addr, &in6addr_loopback, 0); + + udp_listen_addr = (isc_sockaddr_t){ .length = 0 }; + udp_listen_sock = setup_ephemeral_port(&udp_listen_addr, SOCK_DGRAM); + if (udp_listen_sock < 0) { + return (-1); + } + isc__nm_closesocket(udp_listen_sock); + + tcp_connect_addr = (isc_sockaddr_t){ .length = 0 }; + isc_sockaddr_fromin6(&tcp_connect_addr, &in6addr_loopback, 0); + + tcp_listen_addr = (isc_sockaddr_t){ .length = 0 }; + tcp_listen_sock = setup_ephemeral_port(&tcp_listen_addr, SOCK_STREAM); + if (tcp_listen_sock < 0) { + return (-1); + } + isc__nm_closesocket(tcp_listen_sock); + + atomic_store(&do_send, true); + atomic_store(&nsends, esends); + + atomic_store(&saccepts, 0); + atomic_store(&sreads, 0); + atomic_store(&ssends, 0); + + atomic_store(&cconnects, 0); + atomic_store(&csends, 0); + atomic_store(&creads, 0); + atomic_store(&ctimeouts, 0); + allow_send_back = false; + + isc_refcount_init(&active_cconnects, 0); + isc_refcount_init(&active_csends, 0); + isc_refcount_init(&active_creads, 0); + isc_refcount_init(&active_ssends, 0); + isc_refcount_init(&active_sreads, 0); + + isc_nonce_buf(&send_magic, sizeof(send_magic)); + isc_nonce_buf(&stop_magic, sizeof(stop_magic)); + if (send_magic == stop_magic) { + return (-1); + } + + isc__netmgr_create(test_mctx, workers, &listen_nm); + assert_non_null(listen_nm); + isc_nm_settimeouts(listen_nm, T_INIT, T_IDLE, T_KEEPALIVE, + T_ADVERTISED); + + isc__netmgr_create(test_mctx, workers, &connect_nm); + assert_non_null(connect_nm); + isc_nm_settimeouts(connect_nm, T_INIT, T_IDLE, T_KEEPALIVE, + T_ADVERTISED); + + isc_quota_init(&listener_quota, 0); + atomic_store(&check_listener_quota, false); + + connect_readcb = connect_read_cb; + noanswer = false; + + return (0); +} + +static int +nm_teardown(void **state __attribute__((unused))) { + UNUSED(state); + + isc__netmgr_destroy(&connect_nm); + assert_null(connect_nm); + + isc__netmgr_destroy(&listen_nm); + assert_null(listen_nm); + + WAIT_FOR_EQ(active_cconnects, 0); + WAIT_FOR_EQ(active_csends, 0); + WAIT_FOR_EQ(active_csends, 0); + WAIT_FOR_EQ(active_ssends, 0); + WAIT_FOR_EQ(active_sreads, 0); + + isc_refcount_destroy(&active_cconnects); + isc_refcount_destroy(&active_csends); + isc_refcount_destroy(&active_creads); + isc_refcount_destroy(&active_ssends); + isc_refcount_destroy(&active_sreads); + + return (0); +} + +/* Callbacks */ + +static void +noop_recv_cb(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region, + void *cbarg) { + UNUSED(handle); + UNUSED(eresult); + UNUSED(region); + UNUSED(cbarg); +} + +static unsigned int +noop_accept_cb(isc_nmhandle_t *handle, unsigned int result, void *cbarg) { + UNUSED(handle); + UNUSED(result); + UNUSED(cbarg); + + return (0); +} + +static void +connect_send_cb(isc_nmhandle_t *handle, isc_result_t eresult, void *cbarg); + +static void +connect_send(isc_nmhandle_t *handle); + +static void +connect_send_cb(isc_nmhandle_t *handle, isc_result_t eresult, void *cbarg) { + isc_nmhandle_t *sendhandle = handle; + + assert_non_null(sendhandle); + + UNUSED(cbarg); + + F(); + + if (eresult != ISC_R_SUCCESS) { + /* Send failed, we need to stop reading too */ + isc_nm_cancelread(handle); + goto unref; + } + + atomic_fetch_add(&csends, 1); +unref: + isc_refcount_decrement(&active_csends); + isc_nmhandle_detach(&sendhandle); +} + +static void +connect_send(isc_nmhandle_t *handle) { + isc_nmhandle_t *sendhandle = NULL; + isc_refcount_increment0(&active_csends); + isc_nmhandle_attach(handle, &sendhandle); + isc_nmhandle_setwritetimeout(handle, T_IDLE); + if (atomic_fetch_sub(&nsends, 1) > 1) { + isc_nm_send(sendhandle, &send_msg, connect_send_cb, NULL); + } else { + isc_nm_send(sendhandle, &stop_msg, connect_send_cb, NULL); + } +} + +static void +connect_read_cb(isc_nmhandle_t *handle, isc_result_t eresult, + isc_region_t *region, void *cbarg) { + uint64_t magic = 0; + + UNUSED(cbarg); + + assert_non_null(handle); + + F(); + + if (eresult != ISC_R_SUCCESS) { + goto unref; + } + + assert_true(region->length >= sizeof(magic)); + + atomic_fetch_add(&creads, 1); + + memmove(&magic, region->base, sizeof(magic)); + + assert_true(magic == stop_magic || magic == send_magic); + + if (magic == send_magic && allow_send_back) { + connect_send(handle); + return; + } + +unref: + isc_refcount_decrement(&active_creads); + isc_nmhandle_detach(&handle); +} + +static void +connect_connect_cb(isc_nmhandle_t *handle, isc_result_t eresult, void *cbarg) { + isc_nmhandle_t *readhandle = NULL; + + UNUSED(cbarg); + + F(); + + isc_refcount_decrement(&active_cconnects); + + if (eresult != ISC_R_SUCCESS || connect_readcb == NULL) { + return; + } + + atomic_fetch_add(&cconnects, 1); + + isc_refcount_increment0(&active_creads); + isc_nmhandle_attach(handle, &readhandle); + isc_nm_read(handle, connect_readcb, NULL); + + connect_send(handle); +} + +static void +listen_send_cb(isc_nmhandle_t *handle, isc_result_t eresult, void *cbarg) { + isc_nmhandle_t *sendhandle = handle; + + UNUSED(cbarg); + UNUSED(eresult); + + assert_non_null(sendhandle); + + F(); + + if (eresult != ISC_R_SUCCESS) { + goto unref; + } + + atomic_fetch_add(&ssends, 1); +unref: + isc_nmhandle_detach(&sendhandle); + isc_refcount_decrement(&active_ssends); +} + +static void +listen_read_cb(isc_nmhandle_t *handle, isc_result_t eresult, + isc_region_t *region, void *cbarg) { + uint64_t magic = 0; + + assert_non_null(handle); + + F(); + + if (eresult != ISC_R_SUCCESS) { + goto unref; + } + + atomic_fetch_add(&sreads, 1); + + assert_true(region->length >= sizeof(magic)); + + memmove(&magic, region->base, sizeof(magic)); + assert_true(magic == stop_magic || magic == send_magic); + + if (magic == send_magic) { + if (!noanswer) { + isc_nmhandle_t *sendhandle = NULL; + isc_nmhandle_attach(handle, &sendhandle); + isc_refcount_increment0(&active_ssends); + isc_nmhandle_setwritetimeout(sendhandle, T_IDLE); + isc_nm_send(sendhandle, &send_msg, listen_send_cb, + cbarg); + } + return; + } + +unref: + if (handle == cbarg) { + isc_refcount_decrement(&active_sreads); + isc_nmhandle_detach(&handle); + } +} + +static isc_result_t +listen_accept_cb(isc_nmhandle_t *handle, isc_result_t eresult, void *cbarg) { + UNUSED(handle); + UNUSED(cbarg); + + F(); + + return (eresult); +} + +static isc_result_t +stream_accept_cb(isc_nmhandle_t *handle, isc_result_t eresult, void *cbarg) { + isc_nmhandle_t *readhandle = NULL; + + UNUSED(cbarg); + + F(); + + if (eresult != ISC_R_SUCCESS) { + return (eresult); + } + + atomic_fetch_add(&saccepts, 1); + + isc_refcount_increment0(&active_sreads); + isc_nmhandle_attach(handle, &readhandle); + isc_nm_read(handle, listen_read_cb, readhandle); + + return (ISC_R_SUCCESS); +} + +typedef void (*connect_func)(isc_nm_t *); + +static isc_threadresult_t +connect_thread(isc_threadarg_t arg) { + connect_func connect = (connect_func)arg; + isc_sockaddr_t connect_addr; + + connect_addr = (isc_sockaddr_t){ .length = 0 }; + isc_sockaddr_fromin6(&connect_addr, &in6addr_loopback, 0); + + while (atomic_load(&do_send)) { + uint_fast32_t active = + isc_refcount_increment0(&active_cconnects); + if (active > workers) { + /* + * If we have more active connections than workers, + * start slowing down the connections to prevent the + * thundering herd problem. + */ + isc_test_nap((active - workers) * 1000); + } + connect(connect_nm); + } + + return ((isc_threadresult_t)0); +} + +/* UDP */ + +static void +udp_connect(isc_nm_t *nm) { + isc_nm_udpconnect(nm, &udp_connect_addr, &udp_listen_addr, + connect_connect_cb, NULL, T_CONNECT, 0); +} + +static void +mock_listenudp_uv_udp_open(void **state __attribute__((unused))) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + + WILL_RETURN(uv_udp_open, UV_ENOMEM); + + result = isc_nm_listenudp(listen_nm, &udp_listen_addr, noop_recv_cb, + NULL, 0, &listen_sock); + assert_int_not_equal(result, ISC_R_SUCCESS); + assert_null(listen_sock); + + RESET_RETURN; +} + +static void +mock_listenudp_uv_udp_bind(void **state __attribute__((unused))) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + + WILL_RETURN(uv_udp_bind, UV_EADDRINUSE); + + result = isc_nm_listenudp(listen_nm, &udp_listen_addr, noop_recv_cb, + NULL, 0, &listen_sock); + assert_int_not_equal(result, ISC_R_SUCCESS); + assert_null(listen_sock); + + RESET_RETURN; +} + +static void +mock_listenudp_uv_udp_recv_start(void **state __attribute__((unused))) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + + WILL_RETURN(uv_udp_recv_start, UV_EADDRINUSE); + + result = isc_nm_listenudp(listen_nm, &udp_listen_addr, noop_recv_cb, + NULL, 0, &listen_sock); + assert_int_not_equal(result, ISC_R_SUCCESS); + assert_null(listen_sock); + + RESET_RETURN; +} + +static void +mock_udpconnect_uv_udp_open(void **state __attribute__((unused))) { + WILL_RETURN(uv_udp_open, UV_ENOMEM); + + connect_readcb = NULL; + isc_refcount_increment0(&active_cconnects); + isc_nm_udpconnect(connect_nm, &udp_connect_addr, &udp_listen_addr, + connect_connect_cb, NULL, T_CONNECT, 0); + isc__netmgr_shutdown(connect_nm); + + RESET_RETURN; +} + +static void +mock_udpconnect_uv_udp_bind(void **state __attribute__((unused))) { + WILL_RETURN(uv_udp_bind, UV_ENOMEM); + + connect_readcb = NULL; + isc_refcount_increment0(&active_cconnects); + isc_nm_udpconnect(connect_nm, &udp_connect_addr, &udp_listen_addr, + connect_connect_cb, NULL, T_CONNECT, 0); + isc__netmgr_shutdown(connect_nm); + + RESET_RETURN; +} + +#if UV_VERSION_HEX >= UV_VERSION(1, 27, 0) +static void +mock_udpconnect_uv_udp_connect(void **state __attribute__((unused))) { + WILL_RETURN(uv_udp_connect, UV_ENOMEM); + + connect_readcb = NULL; + isc_refcount_increment0(&active_cconnects); + isc_nm_udpconnect(connect_nm, &udp_connect_addr, &udp_listen_addr, + connect_connect_cb, NULL, T_CONNECT, 0); + isc__netmgr_shutdown(connect_nm); + + RESET_RETURN; +} +#endif + +static void +mock_udpconnect_uv_recv_buffer_size(void **state __attribute__((unused))) { + WILL_RETURN(uv_recv_buffer_size, UV_ENOMEM); + + connect_readcb = NULL; + isc_refcount_increment0(&active_cconnects); + isc_nm_udpconnect(connect_nm, &udp_connect_addr, &udp_listen_addr, + connect_connect_cb, NULL, T_CONNECT, 0); + isc__netmgr_shutdown(connect_nm); + + RESET_RETURN; +} + +static void +mock_udpconnect_uv_send_buffer_size(void **state __attribute__((unused))) { + WILL_RETURN(uv_send_buffer_size, UV_ENOMEM); + + connect_readcb = NULL; + isc_refcount_increment0(&active_cconnects); + isc_nm_udpconnect(connect_nm, &udp_connect_addr, &udp_listen_addr, + connect_connect_cb, NULL, T_CONNECT, 0); + isc__netmgr_shutdown(connect_nm); + + RESET_RETURN; +} + +static void +udp_noop(void **state __attribute__((unused))) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + + result = isc_nm_listenudp(listen_nm, &udp_listen_addr, noop_recv_cb, + NULL, 0, &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + + connect_readcb = NULL; + isc_refcount_increment0(&active_cconnects); + isc_nm_udpconnect(connect_nm, &udp_connect_addr, &udp_listen_addr, + connect_connect_cb, NULL, T_CONNECT, 0); + isc__netmgr_shutdown(connect_nm); + + atomic_assert_int_eq(cconnects, 0); + atomic_assert_int_eq(csends, 0); + atomic_assert_int_eq(creads, 0); + atomic_assert_int_eq(sreads, 0); + atomic_assert_int_eq(ssends, 0); +} + +static void +udp_noresponse(void **state __attribute__((unused))) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + + result = isc_nm_listenudp(listen_nm, &udp_listen_addr, noop_recv_cb, + NULL, 0, &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_refcount_increment0(&active_cconnects); + isc_nm_udpconnect(connect_nm, &udp_connect_addr, &udp_listen_addr, + connect_connect_cb, NULL, T_CONNECT, 0); + + WAIT_FOR_EQ(cconnects, 1); + WAIT_FOR_EQ(csends, 1); + + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + isc__netmgr_shutdown(connect_nm); + + X(cconnects); + X(csends); + X(creads); + X(sreads); + X(ssends); + + atomic_assert_int_eq(cconnects, 1); + atomic_assert_int_eq(csends, 1); + atomic_assert_int_eq(creads, 0); + atomic_assert_int_eq(sreads, 0); + atomic_assert_int_eq(ssends, 0); +} + +static void +timeout_retry_cb(isc_nmhandle_t *handle, isc_result_t eresult, + isc_region_t *region, void *cbarg) { + UNUSED(region); + UNUSED(cbarg); + + assert_non_null(handle); + + F(); + + if (eresult == ISC_R_TIMEDOUT && atomic_load(&csends) < 5) { + isc_nmhandle_settimeout(handle, T_SOFT); + connect_send(handle); + return; + } + + atomic_fetch_add(&ctimeouts, 1); + + isc_refcount_decrement(&active_creads); + isc_nmhandle_detach(&handle); +} + +static void +udp_timeout_recovery(void **state __attribute__((unused))) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + + SKIP_IN_CI; + + /* + * Listen using the noop callback so that client reads will time out. + */ + result = isc_nm_listenudp(listen_nm, &udp_listen_addr, noop_recv_cb, + NULL, 0, &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + /* + * Connect with client timeout set to 0.05 seconds, then sleep for at + * least a second for each 'tick'. timeout_retry_cb() will give up + * after five timeouts. + */ + connect_readcb = timeout_retry_cb; + isc_refcount_increment0(&active_cconnects); + isc_nm_udpconnect(connect_nm, &udp_connect_addr, &udp_listen_addr, + connect_connect_cb, NULL, T_SOFT, 0); + + WAIT_FOR_EQ(cconnects, 1); + WAIT_FOR_GE(csends, 1); + WAIT_FOR_GE(csends, 2); + WAIT_FOR_GE(csends, 3); + WAIT_FOR_GE(csends, 4); + WAIT_FOR_EQ(csends, 5); + WAIT_FOR_EQ(ctimeouts, 1); + + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + isc__netmgr_shutdown(connect_nm); +} + +static void +udp_recv_one(void **state __attribute__((unused))) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + + atomic_store(&nsends, 1); + + result = isc_nm_listenudp(listen_nm, &udp_listen_addr, listen_read_cb, + NULL, 0, &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_refcount_increment0(&active_cconnects); + isc_nm_udpconnect(connect_nm, &udp_connect_addr, &udp_listen_addr, + connect_connect_cb, NULL, T_CONNECT, 0); + + WAIT_FOR_EQ(cconnects, 1); + WAIT_FOR_LE(nsends, 0); + WAIT_FOR_EQ(csends, 1); + WAIT_FOR_EQ(sreads, 1); + WAIT_FOR_EQ(ssends, 0); + WAIT_FOR_EQ(creads, 0); + + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + isc__netmgr_shutdown(connect_nm); + + X(cconnects); + X(csends); + X(creads); + X(sreads); + X(ssends); + + atomic_assert_int_eq(cconnects, 1); + atomic_assert_int_eq(csends, 1); + atomic_assert_int_eq(creads, 0); + atomic_assert_int_eq(sreads, 1); + atomic_assert_int_eq(ssends, 0); +} + +static void +udp_recv_two(void **state __attribute__((unused))) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + + atomic_store(&nsends, 2); + + result = isc_nm_listenudp(listen_nm, &udp_listen_addr, listen_read_cb, + NULL, 0, &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_refcount_increment0(&active_cconnects); + isc_nm_udpconnect(connect_nm, &udp_connect_addr, &udp_listen_addr, + connect_connect_cb, NULL, T_CONNECT, 0); + + WAIT_FOR_EQ(cconnects, 1); + + isc_refcount_increment0(&active_cconnects); + isc_nm_udpconnect(connect_nm, &udp_connect_addr, &udp_listen_addr, + connect_connect_cb, NULL, T_CONNECT, 0); + + WAIT_FOR_EQ(cconnects, 2); + WAIT_FOR_LE(nsends, 0); + WAIT_FOR_EQ(csends, 2); + WAIT_FOR_EQ(sreads, 2); + WAIT_FOR_EQ(ssends, 1); + WAIT_FOR_EQ(creads, 1); + + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + isc__netmgr_shutdown(connect_nm); + + X(cconnects); + X(csends); + X(creads); + X(sreads); + X(ssends); + + atomic_assert_int_eq(cconnects, 2); + atomic_assert_int_eq(csends, 2); + atomic_assert_int_eq(creads, 1); + atomic_assert_int_eq(sreads, 2); + atomic_assert_int_eq(ssends, 1); +} + +static void +udp_recv_send(void **state __attribute__((unused))) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + isc_thread_t threads[workers]; + + SKIP_IN_CI; + + result = isc_nm_listenudp(listen_nm, &udp_listen_addr, listen_read_cb, + NULL, 0, &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + memset(threads, 0, sizeof(threads)); + for (size_t i = 0; i < workers; i++) { + isc_thread_create(connect_thread, udp_connect, &threads[i]); + } + + WAIT_FOR_GE(cconnects, esends); + WAIT_FOR_GE(csends, esends); + WAIT_FOR_GE(sreads, esends); + WAIT_FOR_GE(ssends, esends / 2); + WAIT_FOR_GE(creads, esends / 2); + + DONE(); + for (size_t i = 0; i < workers; i++) { + isc_thread_join(threads[i], NULL); + } + + isc__netmgr_shutdown(connect_nm); + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + + X(cconnects); + X(csends); + X(creads); + X(sreads); + X(ssends); + + CHECK_RANGE_FULL(csends); + CHECK_RANGE_FULL(creads); + CHECK_RANGE_FULL(sreads); + CHECK_RANGE_FULL(ssends); +} + +static void +udp_recv_half_send(void **state __attribute__((unused))) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + isc_thread_t threads[workers]; + + SKIP_IN_CI; + + result = isc_nm_listenudp(listen_nm, &udp_listen_addr, listen_read_cb, + NULL, 0, &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + memset(threads, 0, sizeof(threads)); + for (size_t i = 0; i < workers; i++) { + isc_thread_create(connect_thread, udp_connect, &threads[i]); + } + + WAIT_FOR_GE(cconnects, esends / 2); + WAIT_FOR_GE(csends, esends / 2); + WAIT_FOR_GE(sreads, esends / 2); + WAIT_FOR_GE(ssends, esends / 2); + WAIT_FOR_GE(creads, esends / 2); + + isc__netmgr_shutdown(connect_nm); + + DONE(); + for (size_t i = 0; i < workers; i++) { + isc_thread_join(threads[i], NULL); + } + + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + + X(cconnects); + X(csends); + X(creads); + X(sreads); + X(ssends); + + CHECK_RANGE_FULL(csends); + CHECK_RANGE_HALF(creads); + CHECK_RANGE_HALF(sreads); + CHECK_RANGE_HALF(ssends); +} + +static void +udp_half_recv_send(void **state __attribute__((unused))) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + isc_thread_t threads[workers]; + + SKIP_IN_CI; + + result = isc_nm_listenudp(listen_nm, &udp_listen_addr, listen_read_cb, + NULL, 0, &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + memset(threads, 0, sizeof(threads)); + for (size_t i = 0; i < workers; i++) { + isc_thread_create(connect_thread, udp_connect, &threads[i]); + } + + WAIT_FOR_GE(cconnects, esends / 2); + WAIT_FOR_GE(csends, esends / 2); + WAIT_FOR_GE(sreads, esends / 2); + WAIT_FOR_GE(ssends, esends / 2); + WAIT_FOR_GE(creads, esends / 2); + + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + + /* Try to send a little while longer */ + isc_test_nap((esends / 2) * 10000); + + isc__netmgr_shutdown(connect_nm); + + DONE(); + for (size_t i = 0; i < workers; i++) { + isc_thread_join(threads[i], NULL); + } + + X(cconnects); + X(csends); + X(creads); + X(sreads); + X(ssends); + + CHECK_RANGE_FULL(csends); + CHECK_RANGE_HALF(creads); + CHECK_RANGE_HALF(sreads); + CHECK_RANGE_HALF(ssends); +} + +static void +udp_half_recv_half_send(void **state __attribute__((unused))) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + isc_thread_t threads[workers]; + + SKIP_IN_CI; + + result = isc_nm_listenudp(listen_nm, &udp_listen_addr, listen_read_cb, + NULL, 0, &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + memset(threads, 0, sizeof(threads)); + for (size_t i = 0; i < workers; i++) { + isc_thread_create(connect_thread, udp_connect, &threads[i]); + } + + WAIT_FOR_GE(cconnects, esends / 2); + WAIT_FOR_GE(csends, esends / 2); + WAIT_FOR_GE(sreads, esends / 2); + WAIT_FOR_GE(ssends, esends / 2); + WAIT_FOR_GE(creads, esends / 2); + + isc__netmgr_shutdown(connect_nm); + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + + DONE(); + for (size_t i = 0; i < workers; i++) { + isc_thread_join(threads[i], NULL); + } + + X(cconnects); + X(csends); + X(creads); + X(sreads); + X(ssends); + + CHECK_RANGE_FULL(csends); + CHECK_RANGE_HALF(creads); + CHECK_RANGE_HALF(sreads); + CHECK_RANGE_HALF(ssends); +} + +/* Common stream protocols code */ + +static isc_quota_t * +tcp_listener_init_quota(size_t nthreads) { + isc_quota_t *quotap = NULL; + if (atomic_load(&check_listener_quota)) { + unsigned max_quota = ISC_MAX(nthreads / 2, 1); + isc_quota_max(&listener_quota, max_quota); + quotap = &listener_quota; + } + return (quotap); +} + +static void +tcp_connect(isc_nm_t *nm) { + isc_nm_tcpconnect(nm, &tcp_connect_addr, &tcp_listen_addr, + connect_connect_cb, NULL, T_CONNECT, 0); +} + +static stream_connect_function +get_stream_connect_function(void) { + return (tcp_connect); +} + +static isc_result_t +stream_listen(isc_nm_accept_cb_t accept_cb, void *accept_cbarg, + size_t extrahandlesize, int backlog, isc_quota_t *quota, + isc_nmsocket_t **sockp) { + isc_result_t result = ISC_R_SUCCESS; + + result = isc_nm_listentcp(listen_nm, &tcp_listen_addr, accept_cb, + accept_cbarg, extrahandlesize, backlog, quota, + sockp); + + return (result); +} + +static void +stream_connect(isc_nm_cb_t cb, void *cbarg, unsigned int timeout, + size_t extrahandlesize) { + isc_nm_tcpconnect(connect_nm, &tcp_connect_addr, &tcp_listen_addr, cb, + cbarg, timeout, extrahandlesize); +} + +static void +stream_noop(void **state __attribute__((unused))) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + + result = stream_listen(noop_accept_cb, NULL, 0, 0, NULL, &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + + connect_readcb = NULL; + isc_refcount_increment0(&active_cconnects); + stream_connect(connect_connect_cb, NULL, T_CONNECT, 0); + isc__netmgr_shutdown(connect_nm); + + atomic_assert_int_eq(cconnects, 0); + atomic_assert_int_eq(csends, 0); + atomic_assert_int_eq(creads, 0); + atomic_assert_int_eq(sreads, 0); + atomic_assert_int_eq(ssends, 0); +} + +static void +stream_noresponse(void **state __attribute__((unused))) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + + result = stream_listen(noop_accept_cb, NULL, 0, 0, NULL, &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_refcount_increment0(&active_cconnects); + stream_connect(connect_connect_cb, NULL, T_CONNECT, 0); + + WAIT_FOR_EQ(cconnects, 1); + WAIT_FOR_EQ(csends, 1); + + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + isc__netmgr_shutdown(connect_nm); + + X(cconnects); + X(csends); + X(creads); + X(sreads); + X(ssends); + + atomic_assert_int_eq(cconnects, 1); + atomic_assert_int_eq(csends, 1); + atomic_assert_int_eq(creads, 0); + atomic_assert_int_eq(sreads, 0); + atomic_assert_int_eq(ssends, 0); +} + +static void +stream_timeout_recovery(void **state __attribute__((unused))) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + + SKIP_IN_CI; + + /* + * Accept connections but don't send responses, forcing client + * reads to time out. + */ + noanswer = true; + result = stream_listen(stream_accept_cb, NULL, 0, 0, NULL, + &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + /* + * Shorten all the client timeouts to 0.05 seconds. + */ + isc_nm_settimeouts(connect_nm, T_SOFT, T_SOFT, T_SOFT, T_SOFT); + connect_readcb = timeout_retry_cb; + isc_refcount_increment0(&active_cconnects); + stream_connect(connect_connect_cb, NULL, T_SOFT, 0); + + WAIT_FOR_EQ(cconnects, 1); + WAIT_FOR_GE(csends, 1); + WAIT_FOR_GE(csends, 2); + WAIT_FOR_GE(csends, 3); + WAIT_FOR_GE(csends, 4); + WAIT_FOR_EQ(csends, 5); + WAIT_FOR_EQ(ctimeouts, 1); + + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + isc__netmgr_shutdown(connect_nm); +} + +static void +stream_recv_one(void **state __attribute__((unused))) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + isc_quota_t *quotap = tcp_listener_init_quota(1); + + atomic_store(&nsends, 1); + + result = stream_listen(stream_accept_cb, NULL, 0, 0, quotap, + &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_refcount_increment0(&active_cconnects); + stream_connect(connect_connect_cb, NULL, T_CONNECT, 0); + + WAIT_FOR_EQ(cconnects, 1); + WAIT_FOR_LE(nsends, 0); + WAIT_FOR_EQ(csends, 1); + WAIT_FOR_EQ(sreads, 1); + WAIT_FOR_EQ(ssends, 0); + WAIT_FOR_EQ(creads, 0); + + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + isc__netmgr_shutdown(connect_nm); + + X(cconnects); + X(csends); + X(creads); + X(sreads); + X(ssends); + + atomic_assert_int_eq(cconnects, 1); + atomic_assert_int_eq(csends, 1); + atomic_assert_int_eq(creads, 0); + atomic_assert_int_eq(sreads, 1); + atomic_assert_int_eq(ssends, 0); +} + +static void +stream_recv_two(void **state __attribute__((unused))) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + isc_quota_t *quotap = tcp_listener_init_quota(1); + + atomic_store(&nsends, 2); + + result = stream_listen(stream_accept_cb, NULL, 0, 0, quotap, + &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_refcount_increment0(&active_cconnects); + stream_connect(connect_connect_cb, NULL, T_CONNECT, 0); + + WAIT_FOR_EQ(cconnects, 1); + + isc_refcount_increment0(&active_cconnects); + stream_connect(connect_connect_cb, NULL, T_CONNECT, 0); + + WAIT_FOR_EQ(cconnects, 2); + WAIT_FOR_LE(nsends, 0); + WAIT_FOR_EQ(csends, 2); + WAIT_FOR_EQ(sreads, 2); + WAIT_FOR_EQ(ssends, 1); + WAIT_FOR_EQ(creads, 1); + + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + isc__netmgr_shutdown(connect_nm); + + X(cconnects); + X(csends); + X(creads); + X(sreads); + X(ssends); + + atomic_assert_int_eq(cconnects, 2); + atomic_assert_int_eq(csends, 2); + atomic_assert_int_eq(creads, 1); + atomic_assert_int_eq(sreads, 2); + atomic_assert_int_eq(ssends, 1); +} + +static void +stream_recv_send(void **state __attribute__((unused))) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + isc_thread_t threads[workers]; + isc_quota_t *quotap = tcp_listener_init_quota(workers); + + SKIP_IN_CI; + + result = stream_listen(stream_accept_cb, NULL, 0, 0, quotap, + &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + memset(threads, 0, sizeof(threads)); + for (size_t i = 0; i < workers; i++) { + isc_thread_create(connect_thread, get_stream_connect_function(), + &threads[i]); + } + + if (allow_send_back) { + WAIT_FOR_GE(cconnects, 1); + } else { + WAIT_FOR_GE(cconnects, esends); + } + WAIT_FOR_GE(csends, esends); + WAIT_FOR_GE(sreads, esends); + WAIT_FOR_GE(ssends, esends / 2); + WAIT_FOR_GE(creads, esends / 2); + + DONE(); + for (size_t i = 0; i < workers; i++) { + isc_thread_join(threads[i], NULL); + } + + isc__netmgr_shutdown(connect_nm); + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + + X(cconnects); + X(csends); + X(creads); + X(sreads); + X(ssends); + + CHECK_RANGE_FULL(csends); + CHECK_RANGE_FULL(creads); + CHECK_RANGE_FULL(sreads); + CHECK_RANGE_FULL(ssends); +} + +static void +stream_recv_half_send(void **state __attribute__((unused))) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + isc_thread_t threads[workers]; + isc_quota_t *quotap = tcp_listener_init_quota(workers); + + SKIP_IN_CI; + + result = stream_listen(stream_accept_cb, NULL, 0, 0, quotap, + &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + memset(threads, 0, sizeof(threads)); + for (size_t i = 0; i < workers; i++) { + isc_thread_create(connect_thread, get_stream_connect_function(), + &threads[i]); + } + + if (allow_send_back) { + WAIT_FOR_GE(cconnects, 1); + } else { + WAIT_FOR_GE(cconnects, esends / 2); + } + WAIT_FOR_GE(csends, esends / 2); + WAIT_FOR_GE(sreads, esends / 2); + WAIT_FOR_GE(ssends, esends / 2); + WAIT_FOR_GE(creads, esends / 2); + + isc__netmgr_shutdown(connect_nm); + + DONE(); + for (size_t i = 0; i < workers; i++) { + isc_thread_join(threads[i], NULL); + } + + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + + X(cconnects); + X(csends); + X(creads); + X(sreads); + X(ssends); + + CHECK_RANGE_HALF(csends); + CHECK_RANGE_HALF(creads); + CHECK_RANGE_HALF(sreads); + CHECK_RANGE_HALF(ssends); +} + +static void +stream_half_recv_send(void **state __attribute__((unused))) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + isc_thread_t threads[workers]; + isc_quota_t *quotap = tcp_listener_init_quota(workers); + + SKIP_IN_CI; + + result = stream_listen(stream_accept_cb, NULL, 0, 0, quotap, + &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + memset(threads, 0, sizeof(threads)); + for (size_t i = 0; i < workers; i++) { + isc_thread_create(connect_thread, get_stream_connect_function(), + &threads[i]); + } + + if (allow_send_back) { + WAIT_FOR_GE(cconnects, 1); + } else { + WAIT_FOR_GE(cconnects, esends / 2); + } + WAIT_FOR_GE(csends, esends / 2); + WAIT_FOR_GE(sreads, esends / 2); + WAIT_FOR_GE(ssends, esends / 2); + WAIT_FOR_GE(creads, esends / 2); + + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + + /* Try to send a little while longer */ + isc_test_nap((esends / 2) * 10000); + + isc__netmgr_shutdown(connect_nm); + + DONE(); + for (size_t i = 0; i < workers; i++) { + isc_thread_join(threads[i], NULL); + } + + X(cconnects); + X(csends); + X(creads); + X(sreads); + X(ssends); + + CHECK_RANGE_HALF(csends); + CHECK_RANGE_HALF(creads); + CHECK_RANGE_HALF(sreads); + CHECK_RANGE_HALF(ssends); +} + +static void +stream_half_recv_half_send(void **state __attribute__((unused))) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + isc_thread_t threads[workers]; + isc_quota_t *quotap = tcp_listener_init_quota(workers); + + SKIP_IN_CI; + + result = stream_listen(stream_accept_cb, NULL, 0, 0, quotap, + &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + memset(threads, 0, sizeof(threads)); + for (size_t i = 0; i < workers; i++) { + isc_thread_create(connect_thread, get_stream_connect_function(), + &threads[i]); + } + + if (allow_send_back) { + WAIT_FOR_GE(cconnects, 1); + } else { + WAIT_FOR_GE(cconnects, esends / 2); + } + WAIT_FOR_GE(csends, esends / 2); + WAIT_FOR_GE(sreads, esends / 2); + WAIT_FOR_GE(ssends, esends / 2); + WAIT_FOR_GE(creads, esends / 2); + + isc__netmgr_shutdown(connect_nm); + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + + DONE(); + for (size_t i = 0; i < workers; i++) { + isc_thread_join(threads[i], NULL); + } + + X(cconnects); + X(csends); + X(creads); + X(sreads); + X(ssends); + + CHECK_RANGE_HALF(csends); + CHECK_RANGE_HALF(creads); + CHECK_RANGE_HALF(sreads); + CHECK_RANGE_HALF(ssends); +} + +/* TCP */ +static void +tcp_noop(void **state) { + stream_noop(state); +} + +static void +tcp_noresponse(void **state) { + stream_noresponse(state); +} + +static void +tcp_timeout_recovery(void **state) { + stream_timeout_recovery(state); +} + +static void +tcp_recv_one(void **state) { + stream_recv_one(state); +} + +static void +tcp_recv_two(void **state) { + stream_recv_two(state); +} + +static void +tcp_recv_send(void **state) { + SKIP_IN_CI; + stream_recv_send(state); +} + +static void +tcp_recv_half_send(void **state) { + SKIP_IN_CI; + stream_recv_half_send(state); +} + +static void +tcp_half_recv_send(void **state) { + SKIP_IN_CI; + stream_half_recv_send(state); +} + +static void +tcp_half_recv_half_send(void **state) { + SKIP_IN_CI; + stream_half_recv_half_send(state); +} + +static void +tcp_recv_send_sendback(void **state) { + SKIP_IN_CI; + stream_recv_send(state); +} + +static void +tcp_recv_half_send_sendback(void **state) { + SKIP_IN_CI; + stream_recv_half_send(state); +} + +static void +tcp_half_recv_send_sendback(void **state) { + SKIP_IN_CI; + stream_half_recv_send(state); +} + +static void +tcp_half_recv_half_send_sendback(void **state) { + SKIP_IN_CI; + stream_half_recv_half_send(state); +} + +/* TCP Quota */ + +static void +tcp_recv_one_quota(void **state) { + atomic_store(&check_listener_quota, true); + stream_recv_one(state); +} + +static void +tcp_recv_two_quota(void **state) { + atomic_store(&check_listener_quota, true); + stream_recv_two(state); +} + +static void +tcp_recv_send_quota(void **state) { + SKIP_IN_CI; + atomic_store(&check_listener_quota, true); + stream_recv_send(state); +} + +static void +tcp_recv_half_send_quota(void **state) { + SKIP_IN_CI; + atomic_store(&check_listener_quota, true); + stream_recv_half_send(state); +} + +static void +tcp_half_recv_send_quota(void **state) { + SKIP_IN_CI; + atomic_store(&check_listener_quota, true); + stream_half_recv_send(state); +} + +static void +tcp_half_recv_half_send_quota(void **state) { + SKIP_IN_CI; + atomic_store(&check_listener_quota, true); + stream_half_recv_half_send(state); +} + +static void +tcp_recv_send_quota_sendback(void **state) { + SKIP_IN_CI; + atomic_store(&check_listener_quota, true); + allow_send_back = true; + stream_recv_send(state); +} + +static void +tcp_recv_half_send_quota_sendback(void **state) { + SKIP_IN_CI; + atomic_store(&check_listener_quota, true); + allow_send_back = true; + stream_recv_half_send(state); +} + +static void +tcp_half_recv_send_quota_sendback(void **state) { + SKIP_IN_CI; + atomic_store(&check_listener_quota, true); + allow_send_back = true; + stream_half_recv_send(state); +} + +static void +tcp_half_recv_half_send_quota_sendback(void **state) { + SKIP_IN_CI; + atomic_store(&check_listener_quota, true); + allow_send_back = true; + stream_half_recv_half_send(state); +} + +/* TCPDNS */ + +static void +tcpdns_connect(isc_nm_t *nm) { + isc_nm_tcpdnsconnect(nm, &tcp_connect_addr, &tcp_listen_addr, + connect_connect_cb, NULL, T_CONNECT, 0); +} + +static void +tcpdns_noop(void **state __attribute__((unused))) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + + result = isc_nm_listentcpdns(listen_nm, &tcp_listen_addr, noop_recv_cb, + NULL, noop_accept_cb, NULL, 0, 0, NULL, + &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + + connect_readcb = NULL; + isc_refcount_increment0(&active_cconnects); + isc_nm_tcpdnsconnect(connect_nm, &tcp_connect_addr, &tcp_listen_addr, + connect_connect_cb, NULL, T_CONNECT, 0); + isc__netmgr_shutdown(connect_nm); + + atomic_assert_int_eq(cconnects, 0); + atomic_assert_int_eq(csends, 0); + atomic_assert_int_eq(creads, 0); + atomic_assert_int_eq(sreads, 0); + atomic_assert_int_eq(ssends, 0); +} + +static void +tcpdns_noresponse(void **state __attribute__((unused))) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + + isc_refcount_increment0(&active_cconnects); + result = isc_nm_listentcpdns(listen_nm, &tcp_listen_addr, noop_recv_cb, + NULL, noop_accept_cb, NULL, 0, 0, NULL, + &listen_sock); + if (result != ISC_R_SUCCESS) { + isc_refcount_decrement(&active_cconnects); + isc_test_nap(1000); + } + assert_int_equal(result, ISC_R_SUCCESS); + + isc_nm_tcpdnsconnect(connect_nm, &tcp_connect_addr, &tcp_listen_addr, + connect_connect_cb, NULL, T_CONNECT, 0); + + WAIT_FOR_EQ(cconnects, 1); + WAIT_FOR_EQ(csends, 1); + + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + isc__netmgr_shutdown(connect_nm); + + X(cconnects); + X(csends); + X(creads); + X(sreads); + X(ssends); + + atomic_assert_int_eq(cconnects, 1); + atomic_assert_int_eq(csends, 1); + atomic_assert_int_eq(creads, 0); + atomic_assert_int_eq(sreads, 0); + atomic_assert_int_eq(ssends, 0); +} + +static void +tcpdns_timeout_recovery(void **state __attribute__((unused))) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + + SKIP_IN_CI; + + /* + * Accept connections but don't send responses, forcing client + * reads to time out. + */ + noanswer = true; + result = isc_nm_listentcpdns(listen_nm, &tcp_listen_addr, + listen_read_cb, NULL, listen_accept_cb, + NULL, 0, 0, NULL, &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + /* + * Shorten all the TCP client timeouts to 0.05 seconds, connect, + * then sleep for at least a second for each 'tick'. + * timeout_retry_cb() will give up after five timeouts. + */ + connect_readcb = timeout_retry_cb; + isc_nm_settimeouts(connect_nm, T_SOFT, T_SOFT, T_SOFT, T_SOFT); + isc_refcount_increment0(&active_cconnects); + isc_nm_tcpdnsconnect(connect_nm, &tcp_connect_addr, &tcp_listen_addr, + connect_connect_cb, NULL, T_SOFT, 0); + + WAIT_FOR_EQ(cconnects, 1); + WAIT_FOR_GE(csends, 1); + WAIT_FOR_GE(csends, 2); + WAIT_FOR_GE(csends, 3); + WAIT_FOR_GE(csends, 4); + WAIT_FOR_EQ(csends, 5); + WAIT_FOR_EQ(ctimeouts, 1); + + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + isc__netmgr_shutdown(connect_nm); +} + +static void +tcpdns_recv_one(void **state __attribute__((unused))) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + + atomic_store(&nsends, 1); + + result = isc_nm_listentcpdns(listen_nm, &tcp_listen_addr, + listen_read_cb, NULL, listen_accept_cb, + NULL, 0, 0, NULL, &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_refcount_increment0(&active_cconnects); + isc_nm_tcpdnsconnect(connect_nm, &tcp_connect_addr, &tcp_listen_addr, + connect_connect_cb, NULL, T_CONNECT, 0); + + WAIT_FOR_EQ(cconnects, 1); + WAIT_FOR_LE(nsends, 0); + WAIT_FOR_EQ(csends, 1); + WAIT_FOR_EQ(sreads, 1); + WAIT_FOR_EQ(ssends, 0); + WAIT_FOR_EQ(creads, 0); + + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + isc__netmgr_shutdown(connect_nm); + + X(cconnects); + X(csends); + X(creads); + X(sreads); + X(ssends); + + atomic_assert_int_eq(cconnects, 1); + atomic_assert_int_eq(csends, 1); + atomic_assert_int_eq(creads, 0); + atomic_assert_int_eq(sreads, 1); + atomic_assert_int_eq(ssends, 0); +} + +static void +tcpdns_recv_two(void **state __attribute__((unused))) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + + atomic_store(&nsends, 2); + + result = isc_nm_listentcpdns(listen_nm, &tcp_listen_addr, + listen_read_cb, NULL, listen_accept_cb, + NULL, 0, 0, NULL, &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_refcount_increment0(&active_cconnects); + isc_nm_tcpdnsconnect(connect_nm, &tcp_connect_addr, &tcp_listen_addr, + connect_connect_cb, NULL, T_CONNECT, 0); + + WAIT_FOR_EQ(cconnects, 1); + + isc_refcount_increment0(&active_cconnects); + isc_nm_tcpdnsconnect(connect_nm, &tcp_connect_addr, &tcp_listen_addr, + connect_connect_cb, NULL, T_CONNECT, 0); + + WAIT_FOR_EQ(cconnects, 2); + + WAIT_FOR_LE(nsends, 0); + WAIT_FOR_EQ(csends, 2); + WAIT_FOR_EQ(sreads, 2); + WAIT_FOR_EQ(ssends, 1); + WAIT_FOR_EQ(creads, 1); + + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + isc__netmgr_shutdown(connect_nm); + + X(cconnects); + X(csends); + X(creads); + X(sreads); + X(ssends); + + atomic_assert_int_eq(cconnects, 2); + atomic_assert_int_eq(csends, 2); + atomic_assert_int_eq(creads, 1); + atomic_assert_int_eq(sreads, 2); + atomic_assert_int_eq(ssends, 1); +} + +static void +tcpdns_recv_send(void **state __attribute__((unused))) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + isc_thread_t threads[workers]; + + SKIP_IN_CI; + + result = isc_nm_listentcpdns(listen_nm, &tcp_listen_addr, + listen_read_cb, NULL, listen_accept_cb, + NULL, 0, 0, NULL, &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + memset(threads, 0, sizeof(threads)); + for (size_t i = 0; i < workers; i++) { + isc_thread_create(connect_thread, tcpdns_connect, &threads[i]); + } + + WAIT_FOR_GE(cconnects, esends); + WAIT_FOR_GE(csends, esends); + WAIT_FOR_GE(sreads, esends); + WAIT_FOR_GE(ssends, esends / 2); + WAIT_FOR_GE(creads, esends / 2); + + DONE(); + for (size_t i = 0; i < workers; i++) { + isc_thread_join(threads[i], NULL); + } + + isc__netmgr_shutdown(connect_nm); + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + + X(cconnects); + X(csends); + X(creads); + X(sreads); + X(ssends); + + CHECK_RANGE_FULL(csends); + CHECK_RANGE_FULL(creads); + CHECK_RANGE_FULL(sreads); + CHECK_RANGE_FULL(ssends); +} + +static void +tcpdns_recv_half_send(void **state __attribute__((unused))) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + isc_thread_t threads[workers]; + + SKIP_IN_CI; + + result = isc_nm_listentcpdns(listen_nm, &tcp_listen_addr, + listen_read_cb, NULL, listen_accept_cb, + NULL, 0, 0, NULL, &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + memset(threads, 0, sizeof(threads)); + for (size_t i = 0; i < workers; i++) { + isc_thread_create(connect_thread, tcpdns_connect, &threads[i]); + } + + WAIT_FOR_GE(cconnects, esends / 2); + WAIT_FOR_GE(csends, esends / 2); + WAIT_FOR_GE(sreads, esends / 2); + WAIT_FOR_GE(ssends, esends / 2); + WAIT_FOR_GE(creads, esends / 2); + + isc__netmgr_shutdown(connect_nm); + + DONE(); + for (size_t i = 0; i < workers; i++) { + isc_thread_join(threads[i], NULL); + } + + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + + X(cconnects); + X(csends); + X(creads); + X(sreads); + X(ssends); + + CHECK_RANGE_HALF(csends); + CHECK_RANGE_HALF(creads); + CHECK_RANGE_HALF(sreads); + CHECK_RANGE_HALF(ssends); +} + +static void +tcpdns_half_recv_send(void **state __attribute__((unused))) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + isc_thread_t threads[workers]; + + SKIP_IN_CI; + + result = isc_nm_listentcpdns(listen_nm, &tcp_listen_addr, + listen_read_cb, NULL, listen_accept_cb, + NULL, 0, 0, NULL, &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + memset(threads, 0, sizeof(threads)); + for (size_t i = 0; i < workers; i++) { + isc_thread_create(connect_thread, tcpdns_connect, &threads[i]); + } + + WAIT_FOR_GE(cconnects, esends / 2); + WAIT_FOR_GE(csends, esends / 2); + WAIT_FOR_GE(sreads, esends / 2); + WAIT_FOR_GE(ssends, esends / 2); + WAIT_FOR_GE(creads, esends / 2); + + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + + /* Try to send a little while longer */ + isc_test_nap((esends / 2) * 10000); + + isc__netmgr_shutdown(connect_nm); + + DONE(); + for (size_t i = 0; i < workers; i++) { + isc_thread_join(threads[i], NULL); + } + + X(cconnects); + X(csends); + X(creads); + X(sreads); + X(ssends); + + CHECK_RANGE_HALF(csends); + CHECK_RANGE_HALF(creads); + CHECK_RANGE_HALF(sreads); + CHECK_RANGE_HALF(ssends); +} + +static void +tcpdns_half_recv_half_send(void **state __attribute__((unused))) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *listen_sock = NULL; + isc_thread_t threads[workers]; + + SKIP_IN_CI; + + result = isc_nm_listentcpdns(listen_nm, &tcp_listen_addr, + listen_read_cb, NULL, listen_accept_cb, + NULL, 0, 0, NULL, &listen_sock); + assert_int_equal(result, ISC_R_SUCCESS); + + memset(threads, 0, sizeof(threads)); + for (size_t i = 0; i < workers; i++) { + isc_thread_create(connect_thread, tcpdns_connect, &threads[i]); + } + + WAIT_FOR_GE(cconnects, esends / 2); + WAIT_FOR_GE(csends, esends / 2); + WAIT_FOR_GE(sreads, esends / 2); + WAIT_FOR_GE(ssends, esends / 2); + WAIT_FOR_GE(creads, esends / 2); + + isc__netmgr_shutdown(connect_nm); + isc_nm_stoplistening(listen_sock); + isc_nmsocket_close(&listen_sock); + assert_null(listen_sock); + + DONE(); + for (size_t i = 0; i < workers; i++) { + isc_thread_join(threads[i], NULL); + } + + X(cconnects); + X(csends); + X(creads); + X(sreads); + X(ssends); + + CHECK_RANGE_HALF(csends); + CHECK_RANGE_HALF(creads); + CHECK_RANGE_HALF(sreads); + CHECK_RANGE_HALF(ssends); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + /* UDP */ + cmocka_unit_test_setup_teardown(mock_listenudp_uv_udp_open, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown(mock_listenudp_uv_udp_bind, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown( + mock_listenudp_uv_udp_recv_start, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(mock_udpconnect_uv_udp_open, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown(mock_udpconnect_uv_udp_bind, + nm_setup, nm_teardown), +#if UV_VERSION_HEX >= UV_VERSION(1, 27, 0) + cmocka_unit_test_setup_teardown(mock_udpconnect_uv_udp_connect, + nm_setup, nm_teardown), +#endif + cmocka_unit_test_setup_teardown( + mock_udpconnect_uv_recv_buffer_size, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown( + mock_udpconnect_uv_send_buffer_size, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(udp_noop, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(udp_noresponse, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(udp_timeout_recovery, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(udp_recv_one, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(udp_recv_two, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(udp_recv_send, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(udp_recv_half_send, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(udp_half_recv_send, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(udp_half_recv_half_send, + nm_setup, nm_teardown), + + /* TCP */ + cmocka_unit_test_setup_teardown(tcp_noop, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(tcp_noresponse, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(tcp_timeout_recovery, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(tcp_recv_one, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(tcp_recv_two, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(tcp_recv_send, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(tcp_recv_half_send, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(tcp_half_recv_send, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(tcp_half_recv_half_send, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown(tcp_recv_send_sendback, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown(tcp_recv_half_send_sendback, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown(tcp_half_recv_send_sendback, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown( + tcp_half_recv_half_send_sendback, nm_setup, + nm_teardown), + + /* TCP Quota */ + cmocka_unit_test_setup_teardown(tcp_recv_one_quota, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(tcp_recv_two_quota, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(tcp_recv_send_quota, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(tcp_recv_half_send_quota, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown(tcp_half_recv_send_quota, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown(tcp_half_recv_half_send_quota, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown(tcp_recv_send_quota_sendback, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown( + tcp_recv_half_send_quota_sendback, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown( + tcp_half_recv_send_quota_sendback, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown( + tcp_half_recv_half_send_quota_sendback, nm_setup, + nm_teardown), + + /* TCPDNS */ + cmocka_unit_test_setup_teardown(tcpdns_recv_one, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(tcpdns_recv_two, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(tcpdns_noop, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(tcpdns_noresponse, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(tcpdns_timeout_recovery, + nm_setup, nm_teardown), + cmocka_unit_test_setup_teardown(tcpdns_recv_send, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(tcpdns_recv_half_send, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(tcpdns_half_recv_send, nm_setup, + nm_teardown), + cmocka_unit_test_setup_teardown(tcpdns_half_recv_half_send, + nm_setup, nm_teardown), + + }; + + return (cmocka_run_group_tests(tests, _setup, _teardown)); +} + +#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 */ diff --git a/lib/isc/tests/parse_test.c b/lib/isc/tests/parse_test.c new file mode 100644 index 0000000..378690d --- /dev/null +++ b/lib/isc/tests/parse_test.c @@ -0,0 +1,96 @@ +/* + * 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. + */ + +/*! \file */ + +#if HAVE_CMOCKA + +#include <inttypes.h> +#include <sched.h> /* IWYU pragma: keep */ +#include <setjmp.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#define UNIT_TESTING +#include <cmocka.h> + +#include <isc/parseint.h> +#include <isc/util.h> + +#include "isctest.h" + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = isc_test_begin(NULL, true, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + isc_test_end(); + + return (0); +} + +/* Test for 32 bit overflow on 64 bit machines in isc_parse_uint32 */ +static void +parse_overflow(void **state) { + isc_result_t result; + uint32_t output; + + UNUSED(state); + + result = isc_parse_uint32(&output, "1234567890", 10); + assert_int_equal(ISC_R_SUCCESS, result); + assert_int_equal(1234567890, output); + + result = isc_parse_uint32(&output, "123456789012345", 10); + assert_int_equal(ISC_R_RANGE, result); + + result = isc_parse_uint32(&output, "12345678901234567890", 10); + assert_int_equal(ISC_R_RANGE, result); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(parse_overflow, _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 */ diff --git a/lib/isc/tests/pool_test.c b/lib/isc/tests/pool_test.c new file mode 100644 index 0000000..1ce51f6 --- /dev/null +++ b/lib/isc/tests/pool_test.c @@ -0,0 +1,194 @@ +/* + * 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 <sched.h> /* IWYU pragma: keep */ +#include <setjmp.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define UNIT_TESTING +#include <cmocka.h> + +#include <isc/mem.h> +#include <isc/pool.h> +#include <isc/util.h> + +#include "isctest.h" + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = isc_test_begin(NULL, true, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + isc_test_end(); + + return (0); +} + +static isc_result_t +poolinit(void **target, void *arg) { + isc_result_t result; + + isc_taskmgr_t *mgr = (isc_taskmgr_t *)arg; + isc_task_t *task = NULL; + result = isc_task_create(mgr, 0, &task); + if (result != ISC_R_SUCCESS) { + return (result); + } + + *target = (void *)task; + return (ISC_R_SUCCESS); +} + +static void +poolfree(void **target) { + isc_task_t *task = *(isc_task_t **)target; + isc_task_destroy(&task); + *target = NULL; +} + +/* Create a pool */ +static void +create_pool(void **state) { + isc_result_t result; + isc_pool_t *pool = NULL; + + UNUSED(state); + + result = isc_pool_create(test_mctx, 8, poolfree, poolinit, taskmgr, + &pool); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(isc_pool_count(pool), 8); + + isc_pool_destroy(&pool); + assert_null(pool); +} + +/* Resize a pool */ +static void +expand_pool(void **state) { + isc_result_t result; + isc_pool_t *pool1 = NULL, *pool2 = NULL, *hold = NULL; + + UNUSED(state); + + result = isc_pool_create(test_mctx, 10, poolfree, poolinit, taskmgr, + &pool1); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(isc_pool_count(pool1), 10); + + /* resizing to a smaller size should have no effect */ + hold = pool1; + result = isc_pool_expand(&pool1, 5, &pool2); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(isc_pool_count(pool2), 10); + assert_ptr_equal(pool2, hold); + assert_null(pool1); + pool1 = pool2; + pool2 = NULL; + + /* resizing to the same size should have no effect */ + hold = pool1; + result = isc_pool_expand(&pool1, 10, &pool2); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(isc_pool_count(pool2), 10); + assert_ptr_equal(pool2, hold); + assert_null(pool1); + pool1 = pool2; + pool2 = NULL; + + /* resizing to larger size should make a new pool */ + hold = pool1; + result = isc_pool_expand(&pool1, 20, &pool2); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(isc_pool_count(pool2), 20); + assert_ptr_not_equal(pool2, hold); + assert_null(pool1); + + isc_pool_destroy(&pool2); + assert_null(pool2); +} + +/* Get objects */ +static void +get_objects(void **state) { + isc_result_t result; + isc_pool_t *pool = NULL; + void *item; + isc_task_t *task1 = NULL, *task2 = NULL, *task3 = NULL; + + UNUSED(state); + + result = isc_pool_create(test_mctx, 2, poolfree, poolinit, taskmgr, + &pool); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(isc_pool_count(pool), 2); + + item = isc_pool_get(pool); + assert_non_null(item); + isc_task_attach((isc_task_t *)item, &task1); + + item = isc_pool_get(pool); + assert_non_null(item); + isc_task_attach((isc_task_t *)item, &task2); + + item = isc_pool_get(pool); + assert_non_null(item); + isc_task_attach((isc_task_t *)item, &task3); + + isc_task_detach(&task1); + isc_task_detach(&task2); + isc_task_detach(&task3); + + isc_pool_destroy(&pool); + assert_null(pool); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(create_pool, _setup, _teardown), + cmocka_unit_test_setup_teardown(expand_pool, _setup, _teardown), + cmocka_unit_test_setup_teardown(get_objects, _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 */ diff --git a/lib/isc/tests/quota_test.c b/lib/isc/tests/quota_test.c new file mode 100644 index 0000000..854afd9 --- /dev/null +++ b/lib/isc/tests/quota_test.c @@ -0,0 +1,358 @@ +/* + * 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 <sched.h> /* IWYU pragma: keep */ +#include <setjmp.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define UNIT_TESTING +#include <cmocka.h> + +#include <isc/quota.h> +#include <isc/result.h> +#include <isc/thread.h> +#include <isc/util.h> + +#include "isctest.h" + +static void +isc_quota_get_set_test(void **state) { + UNUSED(state); + isc_quota_t quota; + isc_quota_t *quota2 = NULL; + isc_quota_init("a, 100); + + assert_int_equal(isc_quota_getmax("a), 100); + assert_int_equal(isc_quota_getsoft("a), 0); + + isc_quota_max("a, 50); + isc_quota_soft("a, 30); + + assert_int_equal(isc_quota_getmax("a), 50); + assert_int_equal(isc_quota_getsoft("a), 30); + + assert_int_equal(isc_quota_getused("a), 0); + isc_quota_attach("a, "a2); + assert_int_equal(isc_quota_getused("a), 1); + isc_quota_detach("a2); + assert_int_equal(isc_quota_getused("a), 0); + isc_quota_destroy("a); +} + +static void +add_quota(isc_quota_t *source, isc_quota_t **target, + isc_result_t expected_result, int expected_used) { + isc_result_t result; + + *target = NULL; + + result = isc_quota_attach(source, target); + assert_int_equal(result, expected_result); + + switch (expected_result) { + case ISC_R_SUCCESS: + case ISC_R_SOFTQUOTA: + assert_ptr_equal(*target, source); + break; + default: + assert_null(*target); + break; + } + + assert_int_equal(isc_quota_getused(source), expected_used); +} + +static void +isc_quota_hard_test(void **state) { + isc_quota_t quota; + isc_quota_t *quotas[110]; + int i; + UNUSED(state); + + isc_quota_init("a, 100); + + for (i = 0; i < 100; i++) { + add_quota("a, "as[i], ISC_R_SUCCESS, i + 1); + } + + add_quota("a, "as[100], ISC_R_QUOTA, 100); + + assert_int_equal(isc_quota_getused("a), 100); + + isc_quota_detach("as[0]); + assert_null(quotas[0]); + + add_quota("a, "as[100], ISC_R_SUCCESS, 100); + add_quota("a, "as[101], ISC_R_QUOTA, 100); + + for (i = 100; i > 0; i--) { + isc_quota_detach("as[i]); + assert_null(quotas[i]); + assert_int_equal(isc_quota_getused("a), i - 1); + } + assert_int_equal(isc_quota_getused("a), 0); + isc_quota_destroy("a); +} + +static void +isc_quota_soft_test(void **state) { + isc_quota_t quota; + isc_quota_t *quotas[110]; + int i; + UNUSED(state); + + isc_quota_init("a, 100); + isc_quota_soft("a, 50); + + for (i = 0; i < 50; i++) { + add_quota("a, "as[i], ISC_R_SUCCESS, i + 1); + } + for (i = 50; i < 100; i++) { + add_quota("a, "as[i], ISC_R_SOFTQUOTA, i + 1); + } + + add_quota("a, "as[i], ISC_R_QUOTA, 100); + + for (i = 99; i >= 0; i--) { + isc_quota_detach("as[i]); + assert_null(quotas[i]); + assert_int_equal(isc_quota_getused("a), i); + } + assert_int_equal(isc_quota_getused("a), 0); + isc_quota_destroy("a); +} + +static atomic_uint_fast32_t cb_calls = 0; +static isc_quota_cb_t cbs[30]; +static isc_quota_t *qp; + +static void +callback(isc_quota_t *quota, void *data) { + int val = *(int *)data; + /* Callback is not called if we get the quota directly */ + assert_int_not_equal(val, -1); + + /* We get the proper quota pointer */ + assert_ptr_equal(quota, qp); + + /* Verify that the callbacks are called in order */ + int v = atomic_fetch_add_relaxed(&cb_calls, 1); + assert_int_equal(v, val); + + /* + * First 5 will be detached by the test function, + * for the last 5 - do a 'chain detach'. + */ + if (v >= 5) { + isc_quota_detach("a); + } +} + +static void +isc_quota_callback_test(void **state) { + isc_result_t result; + isc_quota_t quota; + isc_quota_t *quotas[30]; + qp = "a; + /* + * - 10 calls that end with SUCCESS + * - 10 calls that end with SOFTQUOTA + * - 10 callbacks + */ + int ints[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + int i; + UNUSED(state); + + isc_quota_init("a, 20); + isc_quota_soft("a, 10); + + for (i = 0; i < 10; i++) { + quotas[i] = NULL; + isc_quota_cb_init(&cbs[i], callback, &ints[i]); + result = isc_quota_attach_cb("a, "as[i], &cbs[i]); + assert_int_equal(result, ISC_R_SUCCESS); + assert_ptr_equal(quotas[i], "a); + assert_int_equal(isc_quota_getused("a), i + 1); + } + for (i = 10; i < 20; i++) { + quotas[i] = NULL; + isc_quota_cb_init(&cbs[i], callback, &ints[i]); + result = isc_quota_attach_cb("a, "as[i], &cbs[i]); + assert_int_equal(result, ISC_R_SOFTQUOTA); + assert_ptr_equal(quotas[i], "a); + assert_int_equal(isc_quota_getused("a), i + 1); + } + + for (i = 20; i < 30; i++) { + quotas[i] = NULL; + isc_quota_cb_init(&cbs[i], callback, &ints[i]); + result = isc_quota_attach_cb("a, "as[i], &cbs[i]); + assert_int_equal(result, ISC_R_QUOTA); + assert_ptr_equal(quotas[i], NULL); + assert_int_equal(isc_quota_getused("a), 20); + } + assert_int_equal(atomic_load(&cb_calls), 0); + + for (i = 0; i < 5; i++) { + isc_quota_detach("as[i]); + assert_null(quotas[i]); + assert_int_equal(isc_quota_getused("a), 20); + assert_int_equal(atomic_load(&cb_calls), i + 1); + } + /* That should cause a chain reaction */ + isc_quota_detach("as[5]); + assert_int_equal(atomic_load(&cb_calls), 10); + + /* Release the quotas that we did not released in the callback */ + for (i = 0; i < 5; i++) { + qp = "a; + isc_quota_detach(&qp); + } + + for (i = 6; i < 20; i++) { + isc_quota_detach("as[i]); + assert_null(quotas[i]); + assert_int_equal(isc_quota_getused("a), 19 - i); + } + assert_int_equal(atomic_load(&cb_calls), 10); + + assert_int_equal(isc_quota_getused("a), 0); + isc_quota_destroy("a); +} + +/* + * Multithreaded quota callback test: + * - quota set to 100 + * - 10 threads, each trying to get 100 quotas. + * - creates a separate thread to release it after 10ms + */ + +typedef struct qthreadinfo { + atomic_uint_fast32_t direct; + atomic_uint_fast32_t callback; + isc_quota_t *quota; + isc_quota_cb_t callbacks[100]; +} qthreadinfo_t; + +static atomic_uint_fast32_t g_tnum = 0; +/* at most 10 * 100 quota_detach threads */ +isc_thread_t g_threads[10 * 100]; + +static void * +quota_detach(void *quotap) { + isc_quota_t *quota = (isc_quota_t *)quotap; + isc_test_nap(10000); + isc_quota_detach("a); + return ((isc_threadresult_t)0); +} + +static void +quota_callback(isc_quota_t *quota, void *data) { + qthreadinfo_t *qti = (qthreadinfo_t *)data; + atomic_fetch_add_relaxed(&qti->callback, 1); + int tnum = atomic_fetch_add_relaxed(&g_tnum, 1); + isc_thread_create(quota_detach, quota, &g_threads[tnum]); +} + +static isc_threadresult_t +quota_thread(void *qtip) { + qthreadinfo_t *qti = (qthreadinfo_t *)qtip; + for (int i = 0; i < 100; i++) { + isc_quota_cb_init(&qti->callbacks[i], quota_callback, qti); + isc_quota_t *quota = NULL; + isc_result_t result = isc_quota_attach_cb(qti->quota, "a, + &qti->callbacks[i]); + if (result == ISC_R_SUCCESS) { + atomic_fetch_add_relaxed(&qti->direct, 1); + int tnum = atomic_fetch_add_relaxed(&g_tnum, 1); + isc_thread_create(quota_detach, quota, + &g_threads[tnum]); + } + } + return ((isc_threadresult_t)0); +} + +static void +isc_quota_callback_mt_test(void **state) { + UNUSED(state); + isc_quota_t quota; + int i; + + isc_quota_init("a, 100); + static qthreadinfo_t qtis[10]; + isc_thread_t threads[10]; + for (i = 0; i < 10; i++) { + atomic_init(&qtis[i].direct, 0); + atomic_init(&qtis[i].callback, 0); + qtis[i].quota = "a; + isc_thread_create(quota_thread, &qtis[i], &threads[i]); + } + for (i = 0; i < 10; i++) { + isc_thread_join(threads[i], NULL); + } + + for (i = 0; i < (int)atomic_load(&g_tnum); i++) { + isc_thread_join(g_threads[i], NULL); + } + int direct = 0, callback = 0; + + for (i = 0; i < 10; i++) { + direct += atomic_load(&qtis[i].direct); + callback += atomic_load(&qtis[i].callback); + } + /* Total quota gained must be 10 threads * 100 tries */ + assert_int_equal(direct + callback, 10 * 100); + /* + * At least 100 must be direct, the rest is virtually random: + * - in a regular run I'm constantly getting 100:900 ratio + * - under rr - usually around ~120:880 + * - under rr -h - 1000:0 + */ + assert_true(direct >= 100); + + isc_quota_destroy("a); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(isc_quota_get_set_test), + cmocka_unit_test(isc_quota_hard_test), + cmocka_unit_test(isc_quota_soft_test), + cmocka_unit_test(isc_quota_callback_test), + cmocka_unit_test(isc_quota_callback_mt_test), + }; + + 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 */ diff --git a/lib/isc/tests/radix_test.c b/lib/isc/tests/radix_test.c new file mode 100644 index 0000000..132b64d --- /dev/null +++ b/lib/isc/tests/radix_test.c @@ -0,0 +1,124 @@ +/* + * 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 <sched.h> /* IWYU pragma: keep */ +#include <setjmp.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +#define UNIT_TESTING +#include <cmocka.h> + +#include <isc/mem.h> +#include <isc/netaddr.h> +#include <isc/radix.h> +#include <isc/result.h> +#include <isc/util.h> + +#include "isctest.h" + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = isc_test_begin(NULL, true, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + isc_test_end(); + + return (0); +} + +/* test radix searching */ +static void +isc_radix_search_test(void **state) { + isc_radix_tree_t *radix = NULL; + isc_radix_node_t *node; + isc_prefix_t prefix; + isc_result_t result; + struct in_addr in_addr; + isc_netaddr_t netaddr; + + UNUSED(state); + + result = isc_radix_create(test_mctx, &radix, 32); + assert_int_equal(result, ISC_R_SUCCESS); + + in_addr.s_addr = inet_addr("3.3.3.0"); + isc_netaddr_fromin(&netaddr, &in_addr); + NETADDR_TO_PREFIX_T(&netaddr, prefix, 24); + + node = NULL; + result = isc_radix_insert(radix, &node, NULL, &prefix); + assert_int_equal(result, ISC_R_SUCCESS); + node->data[0] = (void *)1; + isc_refcount_destroy(&prefix.refcount); + + in_addr.s_addr = inet_addr("3.3.0.0"); + isc_netaddr_fromin(&netaddr, &in_addr); + NETADDR_TO_PREFIX_T(&netaddr, prefix, 16); + + node = NULL; + result = isc_radix_insert(radix, &node, NULL, &prefix); + assert_int_equal(result, ISC_R_SUCCESS); + node->data[0] = (void *)2; + isc_refcount_destroy(&prefix.refcount); + + in_addr.s_addr = inet_addr("3.3.3.3"); + isc_netaddr_fromin(&netaddr, &in_addr); + NETADDR_TO_PREFIX_T(&netaddr, prefix, 22); + + node = NULL; + result = isc_radix_search(radix, &node, &prefix); + assert_int_equal(result, ISC_R_SUCCESS); + assert_ptr_equal(node->data[0], (void *)2); + + isc_refcount_destroy(&prefix.refcount); + + isc_radix_destroy(radix, NULL); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(isc_radix_search_test, _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 */ diff --git a/lib/isc/tests/random_test.c b/lib/isc/tests/random_test.c new file mode 100644 index 0000000..8025583 --- /dev/null +++ b/lib/isc/tests/random_test.c @@ -0,0 +1,894 @@ +/* + * 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. + */ + +/* + * IMPORTANT NOTE: + * These tests work by generating a large number of pseudo-random numbers + * and then statistically analyzing them to determine whether they seem + * random. The test is expected to fail on occasion by random happenstance. + */ + +#if HAVE_CMOCKA + +#include <inttypes.h> +#include <math.h> +#include <sched.h> /* IWYU pragma: keep */ +#include <setjmp.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +#define UNIT_TESTING +#include <cmocka.h> + +#include <isc/commandline.h> +#include <isc/mem.h> +#include <isc/nonce.h> +#include <isc/print.h> +#include <isc/random.h> +#include <isc/result.h> +#include <isc/util.h> + +#include "isctest.h" + +#define REPS 25000 + +typedef double(pvalue_func_t)(isc_mem_t *mctx, uint16_t *values, size_t length); + +/* igamc(), igam(), etc. were adapted (and cleaned up) from the Cephes + * math library: + * + * Cephes Math Library Release 2.8: June, 2000 + * Copyright 1985, 1987, 2000 by Stephen L. Moshier + * + * The Cephes math library was released into the public domain as part + * of netlib. + */ + +static double MACHEP = 1.11022302462515654042E-16; +static double MAXLOG = 7.09782712893383996843E2; +static double big = 4.503599627370496e15; +static double biginv = 2.22044604925031308085e-16; + +static double +igamc(double a, double x); +static double +igam(double a, double x); + +/* Set to true (or use -v option) for verbose output */ +static bool verbose = false; + +typedef enum { + ISC_RANDOM8, + ISC_RANDOM16, + ISC_RANDOM32, + ISC_RANDOM_BYTES, + ISC_RANDOM_UNIFORM, + ISC_NONCE_BYTES +} isc_random_func; + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = isc_test_begin(NULL, true, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + isc_test_end(); + + return (0); +} + +static double +igamc(double a, double x) { + double ans, ax, c, yc, r, t, y, z; + double pk, pkm1, pkm2, qk, qkm1, qkm2; + + if ((x <= 0) || (a <= 0)) { + return (1.0); + } + + if ((x < 1.0) || (x < a)) { + return (1.0 - igam(a, x)); + } + + ax = a * log(x) - x - lgamma(a); + if (ax < -MAXLOG) { + print_error("# igamc: UNDERFLOW, ax=%f\n", ax); + return (0.0); + } + ax = exp(ax); + + /* continued fraction */ + y = 1.0 - a; + z = x + y + 1.0; + c = 0.0; + pkm2 = 1.0; + qkm2 = x; + pkm1 = x + 1.0; + qkm1 = z * x; + ans = pkm1 / qkm1; + + do { + c += 1.0; + y += 1.0; + z += 2.0; + yc = y * c; + pk = pkm1 * z - pkm2 * yc; + qk = qkm1 * z - qkm2 * yc; + if (qk != 0) { + r = pk / qk; + t = fabs((ans - r) / r); + ans = r; + } else { + t = 1.0; + } + + pkm2 = pkm1; + pkm1 = pk; + qkm2 = qkm1; + qkm1 = qk; + + if (fabs(pk) > big) { + pkm2 *= biginv; + pkm1 *= biginv; + qkm2 *= biginv; + qkm1 *= biginv; + } + } while (t > MACHEP); + + return (ans * ax); +} + +static double +igam(double a, double x) { + double ans, ax, c, r; + + if ((x <= 0) || (a <= 0)) { + return (0.0); + } + + if ((x > 1.0) && (x > a)) { + return (1.0 - igamc(a, x)); + } + + /* Compute x**a * exp(-x) / md_gamma(a) */ + ax = a * log(x) - x - lgamma(a); + if (ax < -MAXLOG) { + print_error("# igam: UNDERFLOW, ax=%f\n", ax); + return (0.0); + } + ax = exp(ax); + + /* power series */ + r = a; + c = 1.0; + ans = 1.0; + + do { + r += 1.0; + c *= x / r; + ans += c; + } while (c / ans > MACHEP); + + return (ans * ax / a); +} + +static int8_t scounts_table[65536]; +static uint8_t bitcounts_table[65536]; + +static int8_t +scount_calculate(uint16_t n) { + int i; + int8_t sc; + + sc = 0; + for (i = 0; i < 16; i++) { + uint16_t lsb; + + lsb = n & 1; + if (lsb != 0) { + sc += 1; + } else { + sc -= 1; + } + + n >>= 1; + } + + return (sc); +} + +static uint8_t +bitcount_calculate(uint16_t n) { + int i; + uint8_t bc; + + bc = 0; + for (i = 0; i < 16; i++) { + uint16_t lsb; + + lsb = n & 1; + if (lsb != 0) { + bc += 1; + } + + n >>= 1; + } + + return (bc); +} + +static void +tables_init(void) { + uint32_t i; + + for (i = 0; i < 65536; i++) { + scounts_table[i] = scount_calculate(i); + bitcounts_table[i] = bitcount_calculate(i); + } +} + +/* + * The following code for computing Marsaglia's rank is based on the + * implementation in cdbinrnk.c from the diehard tests by George + * Marsaglia. + * + * This function destroys (modifies) the data passed in bits. + */ +static uint32_t +matrix_binaryrank(uint32_t *bits, size_t rows, size_t cols) { + size_t i, j, k; + unsigned int rt = 0; + uint32_t rank = 0; + uint32_t tmp; + + for (k = 0; k < rows; k++) { + i = k; + + while (rt >= cols || ((bits[i] >> rt) & 1) == 0) { + i++; + + if (i < rows) { + continue; + } else { + rt++; + if (rt < cols) { + i = k; + continue; + } + } + + return (rank); + } + + rank++; + if (i != k) { + tmp = bits[i]; + bits[i] = bits[k]; + bits[k] = tmp; + } + + for (j = i + 1; j < rows; j++) { + if (((bits[j] >> rt) & 1) == 0) { + continue; + } else { + bits[j] ^= bits[k]; + } + } + + rt++; + } + + return (rank); +} + +static void +random_test(pvalue_func_t *func, isc_random_func test_func) { + uint32_t m; + uint32_t j; + uint32_t histogram[11] = { 0 }; + uint32_t passed; + double proportion; + double p_hat; + double lower_confidence, higher_confidence; + double chi_square; + double p_value_t; + double alpha; + + tables_init(); + + m = 1000; + passed = 0; + + for (j = 0; j < m; j++) { + uint32_t i; + uint32_t values[REPS]; + uint16_t *uniform_values; + double p_value; + + switch (test_func) { + case ISC_RANDOM8: + for (i = 0; i < (sizeof(values) / sizeof(*values)); i++) + { + values[i] = isc_random8(); + } + break; + case ISC_RANDOM16: + for (i = 0; i < (sizeof(values) / sizeof(*values)); i++) + { + values[i] = isc_random16(); + } + break; + case ISC_RANDOM32: + for (i = 0; i < (sizeof(values) / sizeof(*values)); i++) + { + values[i] = isc_random32(); + } + break; + case ISC_RANDOM_BYTES: + isc_random_buf(values, sizeof(values)); + break; + case ISC_RANDOM_UNIFORM: + uniform_values = (uint16_t *)values; + for (i = 0; + i < (sizeof(values) / (sizeof(*uniform_values))); + i++) + { + uniform_values[i] = + isc_random_uniform(UINT16_MAX); + } + break; + case ISC_NONCE_BYTES: + isc_nonce_buf(values, sizeof(values)); + break; + } + + p_value = (*func)(test_mctx, (uint16_t *)values, REPS * 2); + if (p_value >= 0.01) { + passed++; + } + + assert_in_range(p_value, 0.0, 1.0); + + i = (int)floor(p_value * 10); + histogram[i]++; + } + + /* + * Check proportion of sequences passing a test (see section + * 4.2.1 in NIST SP 800-22). + */ + alpha = 0.01; /* the significance level */ + proportion = (double)passed / (double)m; + p_hat = 1.0 - alpha; + lower_confidence = p_hat - (3.0 * sqrt((p_hat * (1.0 - p_hat)) / m)); + higher_confidence = p_hat + (3.0 * sqrt((p_hat * (1.0 - p_hat)) / m)); + + if (verbose) { + print_message("# passed=%u/1000\n", passed); + print_message("# higher_confidence=%f, lower_confidence=%f, " + "proportion=%f\n", + higher_confidence, lower_confidence, proportion); + } + + assert_in_range(proportion, lower_confidence, higher_confidence); + + /* + * Check uniform distribution of p-values (see section 4.2.2 in + * NIST SP 800-22). + */ + + /* Fold histogram[10] (p_value = 1.0) into histogram[9] for + * interval [0.9, 1.0] + */ + histogram[9] += histogram[10]; + histogram[10] = 0; + + /* Pre-requisite that at least 55 sequences are processed. */ + assert_true(m >= 55); + + if (verbose) { + print_message("# "); + } + + chi_square = 0.0; + for (j = 0; j < 10; j++) { + double numer; + double denom; + + if (verbose) { + print_message("hist%u=%u ", j, histogram[j]); + } + + numer = (histogram[j] - (m / 10.0)) * + (histogram[j] - (m / 10.0)); + denom = m / 10.0; + chi_square += numer / denom; + } + + if (verbose) { + print_message("\n"); + } + + p_value_t = igamc(9 / 2.0, chi_square / 2.0); + + assert_true(p_value_t >= 0.0001); +} + +/* + * This is a frequency (monobits) test taken from the NIST SP 800-22 + * RANDOM test suite. + */ +static double +monobit(isc_mem_t *mctx, uint16_t *values, size_t length) { + size_t i; + int32_t scount; + uint32_t numbits; + double s_obs; + double p_value; + + UNUSED(mctx); + + numbits = length * sizeof(*values) * 8; + scount = 0; + + for (i = 0; i < length; i++) { + scount += scounts_table[values[i]]; + } + + /* Preconditions (section 2.1.7 in NIST SP 800-22) */ + assert_true(numbits >= 100); + + if (verbose) { + print_message("# numbits=%u, scount=%d\n", numbits, scount); + } + + s_obs = abs(scount) / sqrt(numbits); + p_value = erfc(s_obs / sqrt(2.0)); + + return (p_value); +} + +/* + * This is the runs test taken from the NIST SP 800-22 RNG test suite. + */ +static double +runs(isc_mem_t *mctx, uint16_t *values, size_t length) { + size_t i; + uint32_t bcount; + uint32_t numbits; + double pi; + double tau; + uint32_t j; + uint32_t b; + uint8_t bit_this; + uint8_t bit_prev; + uint32_t v_obs; + double numer; + double denom; + double p_value; + + UNUSED(mctx); + + numbits = length * sizeof(*values) * 8; + bcount = 0; + + for (i = 0; i < length; i++) { + bcount += bitcounts_table[values[i]]; + } + + if (verbose) { + print_message("# numbits=%u, bcount=%u\n", numbits, bcount); + } + + pi = (double)bcount / (double)numbits; + tau = 2.0 / sqrt(numbits); + + /* Preconditions (section 2.3.7 in NIST SP 800-22) */ + assert_true(numbits >= 100); + + /* + * Pre-condition implied from the monobit test. This can fail + * for some sequences, and the p-value is taken as 0 in these + * cases. + */ + if (fabs(pi - 0.5) >= tau) { + return (0.0); + } + + /* Compute v_obs */ + j = 0; + b = 14; + bit_prev = (values[j] & (1U << 15)) == 0 ? 0 : 1; + + v_obs = 0; + + for (i = 1; i < numbits; i++) { + bit_this = (values[j] & (1U << b)) == 0 ? 0 : 1; + if (b == 0) { + b = 15; + j++; + } else { + b--; + } + + v_obs += bit_this ^ bit_prev; + + bit_prev = bit_this; + } + + v_obs += 1; + + numer = fabs(v_obs - (2.0 * numbits * pi * (1.0 - pi))); + denom = 2.0 * sqrt(2.0 * numbits) * pi * (1.0 - pi); + + p_value = erfc(numer / denom); + + return (p_value); +} + +/* + * This is the block frequency test taken from the NIST SP 800-22 RNG + * test suite. + */ +static double +blockfrequency(isc_mem_t *mctx, uint16_t *values, size_t length) { + uint32_t i; + uint32_t numbits; + uint32_t mbits; + uint32_t mwords; + uint32_t numblocks; + double *pi; + double chi_square; + double p_value; + + numbits = length * sizeof(*values) * 8; + mbits = 32000; + mwords = mbits / 16; + numblocks = numbits / mbits; + + if (verbose) { + print_message("# numblocks=%u\n", numblocks); + } + + /* Preconditions (section 2.2.7 in NIST SP 800-22) */ + assert_true(numbits >= 100); + assert_true(mbits >= 20); + assert_true((double)mbits > (0.01 * numbits)); + assert_true(numblocks < 100); + assert_true(numbits >= (mbits * numblocks)); + + pi = isc_mem_get(mctx, numblocks * sizeof(double)); + assert_non_null(pi); + + for (i = 0; i < numblocks; i++) { + uint32_t j; + pi[i] = 0.0; + for (j = 0; j < mwords; j++) { + uint32_t idx; + + idx = i * mwords + j; + pi[i] += bitcounts_table[values[idx]]; + } + pi[i] /= mbits; + } + + /* Compute chi_square */ + chi_square = 0.0; + for (i = 0; i < numblocks; i++) { + chi_square += (pi[i] - 0.5) * (pi[i] - 0.5); + } + + chi_square *= 4 * mbits; + + isc_mem_put(mctx, pi, numblocks * sizeof(double)); + + if (verbose) { + print_message("# chi_square=%f\n", chi_square); + } + + p_value = igamc(numblocks * 0.5, chi_square * 0.5); + + return (p_value); +} + +/* + * This is the binary matrix rank test taken from the NIST SP 800-22 RNG + * test suite. + */ +static double +binarymatrixrank(isc_mem_t *mctx, uint16_t *values, size_t length) { + uint32_t i; + size_t matrix_m; + size_t matrix_q; + uint32_t num_matrices; + size_t numbits; + uint32_t fm_0; + uint32_t fm_1; + uint32_t fm_rest; + double term1; + double term2; + double term3; + double chi_square; + double p_value; + + UNUSED(mctx); + + matrix_m = 32; + matrix_q = 32; + num_matrices = length / ((matrix_m * matrix_q) / 16); + numbits = num_matrices * matrix_m * matrix_q; + + /* Preconditions (section 2.5.7 in NIST SP 800-22) */ + assert_int_equal(matrix_m, 32); + assert_int_equal(matrix_q, 32); + assert_true(numbits >= (38 * matrix_m * matrix_q)); + + fm_0 = 0; + fm_1 = 0; + fm_rest = 0; + for (i = 0; i < num_matrices; i++) { + /* + * Each uint32_t supplies 32 bits, so a 32x32 bit matrix + * takes up uint32_t array of size 32. + */ + uint32_t bits[32]; + int j; + uint32_t rank; + + for (j = 0; j < 32; j++) { + size_t idx; + uint32_t r1; + uint32_t r2; + + idx = i * ((matrix_m * matrix_q) / 16); + idx += j * 2; + + r1 = values[idx]; + r2 = values[idx + 1]; + bits[j] = (r1 << 16) | r2; + } + + rank = matrix_binaryrank(bits, matrix_m, matrix_q); + + if (rank == matrix_m) { + fm_0++; + } else if (rank == (matrix_m - 1)) { + fm_1++; + } else { + fm_rest++; + } + } + + /* Compute chi_square */ + term1 = ((fm_0 - (0.2888 * num_matrices)) * + (fm_0 - (0.2888 * num_matrices))) / + (0.2888 * num_matrices); + term2 = ((fm_1 - (0.5776 * num_matrices)) * + (fm_1 - (0.5776 * num_matrices))) / + (0.5776 * num_matrices); + term3 = ((fm_rest - (0.1336 * num_matrices)) * + (fm_rest - (0.1336 * num_matrices))) / + (0.1336 * num_matrices); + + chi_square = term1 + term2 + term3; + + if (verbose) { + print_message("# fm_0=%u, fm_1=%u, fm_rest=%u, chi_square=%f\n", + fm_0, fm_1, fm_rest, chi_square); + } + + p_value = exp(-chi_square * 0.5); + + return (p_value); +} + +/*** + *** Tests for isc_random32() function + ***/ + +/* Monobit test for the RANDOM */ +static void +isc_random32_monobit(void **state) { + UNUSED(state); + + random_test(monobit, ISC_RANDOM32); +} + +/* Runs test for the RANDOM */ +static void +isc_random32_runs(void **state) { + UNUSED(state); + + random_test(runs, ISC_RANDOM32); +} + +/* Block frequency test for the RANDOM */ +static void +isc_random32_blockfrequency(void **state) { + UNUSED(state); + + random_test(blockfrequency, ISC_RANDOM32); +} + +/* Binary matrix rank test for the RANDOM */ +static void +isc_random32_binarymatrixrank(void **state) { + UNUSED(state); + + random_test(binarymatrixrank, ISC_RANDOM32); +} + +/*** + *** Tests for isc_random_bytes() function + ***/ + +/* Monobit test for the RANDOM */ +static void +isc_random_bytes_monobit(void **state) { + UNUSED(state); + + random_test(monobit, ISC_RANDOM_BYTES); +} + +/* Runs test for the RANDOM */ +static void +isc_random_bytes_runs(void **state) { + UNUSED(state); + + random_test(runs, ISC_RANDOM_BYTES); +} + +/* Block frequency test for the RANDOM */ +static void +isc_random_bytes_blockfrequency(void **state) { + UNUSED(state); + + random_test(blockfrequency, ISC_RANDOM_BYTES); +} + +/* Binary matrix rank test for the RANDOM */ +static void +isc_random_bytes_binarymatrixrank(void **state) { + UNUSED(state); + + random_test(binarymatrixrank, ISC_RANDOM_BYTES); +} + +/*** + *** Tests for isc_random_uniform() function: + ***/ + +/* Monobit test for the RANDOM */ +static void +isc_random_uniform_monobit(void **state) { + UNUSED(state); + + random_test(monobit, ISC_RANDOM_UNIFORM); +} + +/* Runs test for the RANDOM */ +static void +isc_random_uniform_runs(void **state) { + UNUSED(state); + + random_test(runs, ISC_RANDOM_UNIFORM); +} + +/* Block frequency test for the RANDOM */ +static void +isc_random_uniform_blockfrequency(void **state) { + UNUSED(state); + + random_test(blockfrequency, ISC_RANDOM_UNIFORM); +} + +/* Binary matrix rank test for the RANDOM */ +static void +isc_random_uniform_binarymatrixrank(void **state) { + UNUSED(state); + + random_test(binarymatrixrank, ISC_RANDOM_UNIFORM); +} + +/* Tests for isc_nonce_bytes() function */ + +/* Monobit test for the RANDOM */ +static void +isc_nonce_bytes_monobit(void **state) { + UNUSED(state); + + random_test(monobit, ISC_NONCE_BYTES); +} + +/* Runs test for the RANDOM */ +static void +isc_nonce_bytes_runs(void **state) { + UNUSED(state); + + random_test(runs, ISC_NONCE_BYTES); +} + +/* Block frequency test for the RANDOM */ +static void +isc_nonce_bytes_blockfrequency(void **state) { + UNUSED(state); + + random_test(blockfrequency, ISC_NONCE_BYTES); +} + +/* Binary matrix rank test for the RANDOM */ +static void +isc_nonce_bytes_binarymatrixrank(void **state) { + UNUSED(state); + + random_test(binarymatrixrank, ISC_NONCE_BYTES); +} + +int +main(int argc, char **argv) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(isc_random32_monobit), + cmocka_unit_test(isc_random32_runs), + cmocka_unit_test(isc_random32_blockfrequency), + cmocka_unit_test(isc_random32_binarymatrixrank), + cmocka_unit_test(isc_random_bytes_monobit), + cmocka_unit_test(isc_random_bytes_runs), + cmocka_unit_test(isc_random_bytes_blockfrequency), + cmocka_unit_test(isc_random_bytes_binarymatrixrank), + cmocka_unit_test(isc_random_uniform_monobit), + cmocka_unit_test(isc_random_uniform_runs), + cmocka_unit_test(isc_random_uniform_blockfrequency), + cmocka_unit_test(isc_random_uniform_binarymatrixrank), + cmocka_unit_test(isc_nonce_bytes_monobit), + cmocka_unit_test(isc_nonce_bytes_runs), + cmocka_unit_test(isc_nonce_bytes_blockfrequency), + cmocka_unit_test(isc_nonce_bytes_binarymatrixrank), + }; + int c; + + while ((c = isc_commandline_parse(argc, argv, "v")) != -1) { + switch (c) { + case 'v': + verbose = true; + break; + default: + break; + } + } + + return (cmocka_run_group_tests(tests, _setup, _teardown)); +} + +#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 */ diff --git a/lib/isc/tests/regex_test.c b/lib/isc/tests/regex_test.c new file mode 100644 index 0000000..700df92 --- /dev/null +++ b/lib/isc/tests/regex_test.c @@ -0,0 +1,2374 @@ +/* + * 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 <setjmp.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#ifdef HAVE_REGEX_H +#include <regex.h> +#endif /* ifdef HAVE_REGEX_H */ + +#define UNIT_TESTING +#include <cmocka.h> + +#include <isc/commandline.h> +#include <isc/print.h> +#include <isc/regex.h> +#include <isc/util.h> + +/* Set to true (or use -v option) for verbose output */ +static bool verbose = false; + +/* test isc_regex_validate() */ +static void +regex_validate(void **state) { + /* + * test regex were generated using http://code.google.com/p/regfuzz/ + * modified to use only printable characters + */ + struct { + const char *expression; + int expect; + int exception; /* regcomp accepts but is + * disallowed. */ + } tests[] = { + { "", -1, 0 }, + { "*", -1, 0 }, + { ".*", 0, 0 }, + { ".**", -1, 0 }, + { ".*\?", -1, 0 }, + { ".*+", -1, 0 }, + { "+", -1, 0 }, + { ".+", 0, 0 }, + { ".++", -1, 0 }, + { ".+\?", -1, 0 }, + { ".+*", -1, 0 }, + { "\?", -1, 0 }, + { ".\?", 0, 0 }, + { ".\?\?", -1, 0 }, + { ".\?*", -1, 0 }, + { ".\?+", -1, 0 }, + { "(", -1, 0 }, + { "()", 1, 0 }, + { "(|)", -1, 0 }, + { "(a|)", -1, 0 }, + { "(|b)", -1, 0 }, + { ".{", 0, 0 }, + { ".{1", -1, 0 }, + { ".\\{1", 0, 0 }, + { ".{1}", 0, 0 }, + { ".\\{1}", 0, 0 }, + { ".{,", 0, 0 }, + { ".{,}", 0, 0 }, + { ".{1,}", 0, 0 }, + { ".\\{1,}", 0, 0 }, + { ".{1,\\}", -1, 0 }, + { ".{1,", -1, 0 }, + { ".\\{1,", 0, 0 }, + { ".{1,2}", 0, 0 }, + { ".{1,2}*", -1, 0 }, + { ".{1,2}+", -1, 0 }, + { ".{1,2}\?", -1, 0 }, + { ".{1,2", -1, 0 }, + { ".{2,1}", -1, 0 }, + { "[", -1, 0 }, + { "[]", -1, 0 }, + { "[]]", 0, 0 }, + { "[[]", 0, 0 }, + { "[^]", -1, 0 }, + { "[1-2-3]", -1, 0 }, + { "[1-22-3]", 0, 0 }, + { "[+--23]", 0, 0 }, + { "[+--]", 0, 0 }, + { "[-1]", 0, 0 }, + { "[1-]", 0, 0 }, + { "[[.^.]]", 0, 0 }, + { "[^]]", 0, 0 }, + { "[^^]", 0, 0 }, + { "[]]\?", 0, 0 }, + { "[[]\?", 0, 0 }, + { "[[..]]", -1, 0 }, + { "[[...]]", 0, 0 }, + { "[[..5.]--]", -1, 0 }, + { "[[.+.]--]", 0, 0 }, + { "[[..+.]--]", -1, 0 }, + { "[[.5.]--]", -1, 0 }, + { "[1-[=x=]]", -1, 0 }, + { "[[:alpha:]]", 0, 0 }, + { "[[:alpha:]", -1, 0 }, + { "[[:alnum:]]", 0, 0 }, + { "[[:alnum:]", -1, 0 }, + { "[[:digit:]]", 0, 0 }, + { "[[:digit:]", -1, 0 }, + { "[[:punct:]]", 0, 0 }, + { "[[:punct:]", -1, 0 }, + { "[[:graph:]]", 0, 0 }, + { "[[:graph:]", -1, 0 }, + { "[[:space:]]", 0, 0 }, + { "[[:space:]", -1, 0 }, + { "[[:blank:]]", 0, 0 }, + { "[[:blank:]", -1, 0 }, + { "[[:upper:]]", 0, 0 }, + { "[[:upper:]", -1, 0 }, + { "[[:cntrl:]]", 0, 0 }, + { "[[:cntrl:]", -1, 0 }, + { "[[:print:]]", 0, 0 }, + { "[[:print:]", -1, 0 }, + { "[[:xdigit:]]", 0, 0 }, + { "[[:xdigit:]", -1, 0 }, + { "[[:unknown:]]", -1, 0 }, + { "\\[", 0, 0 }, + { "(a)\\1", 1, 0 }, + { "(a)\\2", -1, 1 }, + { "\\0", 0, 0 }, + { "[[][:g(\?(raph:][:alnu)(\?{m:][:space:]h]<Z3})AAA)S[:space:]" + "{176,}", + 0, 0 }, + { "(()IIIIIIII(III[[[[[[[[[[[[[[[[[[^[[[[[[[[ [^ " + " " + "fX][:ascii:].)N[:a(\?<!lpha:])][:punct:]e*y+)a{-124,223}", + 3, 0 }, + { "(pP\\\\\\(\?<!" + "\\\\\\\\\\\\\\\\\\\\\\lRRRRRRRRRRRRRRRRBBBBBBBBBBBBBBBB))" + "kkkkkkkkkkkkkkkkkkkkk|^", + 1, 0 }, + { "[^[^[{111}(\?=(\?:(\?>/" + "r(\?<(\?=!(\?(\?!<!Q(\?:=0_{Meqipm`(\?((\?{x|N)))))|))+]+]Z)" + "O{,-215}])}))___________________{}", + 0, 0 }, + { "[C{,-218(\?=}E^< ]PP-Ga)t``````````````````````````{138}", 0, + 0 }, + { "[^h(\?<!(\?>Nn(\?#])))", 0, 0 }, + { "[(\?!(\?<=[^{,37}AAAA(AAAAAAAAAAAAA])", 0, 0 }, + { "[^((\?(\?:ms(\?<!xims:A{}(\?{*</H(\?=xL " + "$(\?<!,[})))*)qqqqqqqqqqqqqqqqqq)]" + "33333333333333333333333333333{[:graph:]p)-+( " + "oqD]){-10,}-{247}_______________________X-e[:alpha:][:" + "upperword:]_(______wwwwwwwww " + "/c[:upperword:][:alnum:][:alnum:][:pun(\?{ct:])[:blankcntrl:" + "]})*_*", + 2, 0 }, + { "[(\?<!:lowerprin(\?{t:]{}}){113,})[:punct:]" + "IIIIIIIIIIIIIIIIIIIIIIII", + 0, 0 }, + { "PP)", 0, 0 }, + { "(([^(\?<!((\?>\?=[])p.]}8X[:blankcntrl:],{-119,94})XmF1.{)-)" + "[:upperword:])[:digit:]{zg-q", + 2, 0 }, + { "[^[({(\?#254}))Z[l][x50]=444444444444(4444444444u[:punct:]" + "\?[:punct:(\?!])])", + 1, 0 }, + { "[^[^[^([^((*4[(^((\?<=])Ec)", 0, 0 }, + { "(0)Y:8biiiiiiiiiiiiiiiiiii", 1, 0 }, + { "[^w(\?!)P::::::::::::::(\?#::(\?<=:::::::::]\"\"{}[" + "3333333333333333(\?<=33333(\?!)9Xja][:alph(\?<=a:])xB1)(" + "PX8Cf\?4444)qq[:digit:])", + 1, 0 }, + { "([U[^[^].]^m]/306KS7JJJJJJJJ{})", 1, 0 }, + { "[^[^([^[(\?!(\?>8j`Wg2(\?{,(\?>!#N++++(\?<![++++++)+" + "44444444bA:K(\?<!O3([:digit:]3]}}}}}}}}}}}}}}}}}}}}}}}}LP})" + "S", + 0, 0 }, + { "[({(\?{,(\?(=213}*))})]WWWWWWWWWWWWWWW[:alnum:])", 0, 0 }, + { "[:(\?<=ascii:])", 0, 0 }, + { "[U(\?#)(\?<=+HzE])[:punct:]{-207,170}\?s.!", 0, 0 }, + { "{}z=jU75~n#soD\"&\?UL`X{xxxxxxxxxxxxxxxxxxxx(xxxxxx${-246," + "27}[:graph:]g\"{_bX)[:alnum:][:punct:]{-79,}-", + 1, 0 }, + { "[^{,-186}@@@@[^(\?{@@(\?>@+(\?>l.]}))*\\BCYX]^W{52,123}(" + "lXislccccccccccccccccc)-*)", + 1, 0 }, + { "(x42+,)7=]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]", 1, 0 }, + { "[^(*[:graph:]q/TH\?B(\?{P)]})uZn[:digit:]+2", 0, 0 }, + { "([XXXXXXXXXXXXXXXXXXXXX[(:alnum:][:space:]i%[:upperw(\?=o(" + "\?#rd:])) ", + 1, 0 }, + { "(@@@@)", 1, 0 }, + { "{-18,}[:as[(\?>^[cii:]]{}>+{-46,}{,95}[:punct:]{}" + "99999999999999])-{-134}'sK$" + "wCKjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj", + 0, 0 }, + { "(l[:alpha:(\?!]))", 1, 0 }, + { "[[^(\?{]|JJ[:alph(a:]X{})B^][:lowerprint:]n-219{-32}{19,105}" + "k4P}){,-144}", + 0, 0 }, + { "[[^]P[:punct:][:alpha:][:xdigit:]syh]|W#JS*(m<2,P-RK)cA@", 1, + 0 }, + { "([^((\?({\?<=)}){[^}^]{}])^P4[:punct:[]$)]", 1, 0 }, + { "([(\?#:(\?{space:]}):{}{-242,}n)F[:alpha:]3$)d4H3up6qS[:" + "blankcntrl:]B:C{}[:upperword:]r", + 1, 0 }, + { "([(\?:]))[:digit:]mLV.{}", 1, 0 }, + { "[^PPP-[]{[,50}{128,}]111111111111111]p", 0, 0 }, + { "[^([^([^([[^[([^[^[[2[[[[[[[[[[[[[^[[[[(\?(\?{:[[[[[[(\?([-[" + ":ascii:]--*)", + -1, 0 }, + { ")!F^DA/ZZZZZZZZZZZZZZZZZZ", 0, 0 }, + { "[[[[[[[((\?=\?(\?>([[[[[[[^[[[[(\?()[[[K(\?#))])))]7Y[:" + "space:]{,-96}pP)[:ascii:]u{-88}:N{-251}uo", + 0, 0 }, + { "t[:x(\?<=digit:])eYYYYYYYYYYYYYYYYYY{,-220}A", 0, 0 }, + { "[[({10,}[:graph:]Pdddddd(\?#X)])[:alnum:(]]L-C){,50}[:" + "blankcntrl:]p[:gra(ph:]){66,}", + 0, 0 }, + { "[^[^]*4br]w[:digit(\?::]n99999999999999999)P[:punct:]pP", 0, + 0 }, + { "[:digit:]{67,247}!N{122})VrXe", 0, 0 }, + { "[:xdigit:]^[:xdigit:]Z[:alnum:]^^^^1[:upperword:(\?=])[:" + "lowerprint:]*JJ-", + 0, 0 }, + { "[[(\?imsximsx:^*e(){,3[6}](V~\?^[:asc(\?!ii:]I.dZ))]$^" + "AAAAAAAAAAAAAAAAAAAAAAAA[:space:]k)]", + 1, 0 }, + { "W{,112}[:lowerp(\?<!rint:]$#GT>R7~t'" + "\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"9,O).", + 0, 0 }, + { "[^{6((\?>\?:4}(\?<=G))f)" + "KKKKKKKKKKKKKKKKKKKKKKKKKKKKKpppppppp(\?=ppppp]{,-101}|[:" + "blankcntrl:]Z{-182})", + 0, 0 }, + { "([:punct:]@^,,,,,,,,,,,,,,,,,,,,,,,,,,0\?:-o8NPIIIIIIIII)" + "pPKKKKKKKKKKKKKKKKKKKK", + 1, 0 }, + { "([^[[^[^]]]])", 1, 0 }, + { "[([^[(333\"(\?#\\\\[)(\?isx-x:\"Tx]')", 0, 0 }, + { "[[n>^>T%.zzzzzzzzzzzzzzzzz$&|Fk.1o7^o, " + "^8{202,-12}$[:alnum:]]G[:upperword:]V[:xdigit:]L|[:" + "upperword:]KKKKKKKKKKKKYX\"\")xJ " + "~B@[{,-68}/][:upperword:]QI.", + 0, 0 }, + { "[^[]tN^hy3\"d@v T[GE\?^~{124,10(\?{2}]})\?[:upperword:]O", 0, + 0 }, + { "d.``````````````````````````[:up(\?=perword:]" + "RRRRRRRRRRRRRRR)", + 0, 0 }, + { "[Z{{{{{{{{{{{{{(\?={(\?<!{{{{{{{{{(\?>{{J6N:H[tA+mN3Zmf:p\?]" + "\?){-181,82}S4n.b[:lowerpri(\?{nt:]|" + "ggggggggggggggggggggggggggggggg}))4)", + 0, 0 }, + { "[^((/////[^////[^/////////[(^/////]fI{240}{-120}+]R]GA)", 0, + 0 }, + { "[-(\?#.)(\?())[:alpha:](\?={(\?#}r)[:space:]PPW]o)", 0, 0 }, + { "[:lowerp(\?{rint:]})201{46,}[:a[^scii:]0Q{37,}][:blankcntrl:" + "]1331", + 0, 0 }, + { "[^(\?!(\?#)\\GIwxKKKKKKKKKK'$KKKKKKKK]l)bbb^&\?", 0, 0 }, + { "[:ascii:]*[:sp(\?<=ace:])", 0, 0 }, + { "({-66,}Z{})0I{-111,}[:punct(\?():])", 1, 0 }, + { "[[^(\?!()%%%%%%%%%%%%%(\?:%%%%%%%%%%%%%%%%)t(\?{VX>B#6sUU(" + "\?<!UUUUUU(\?=UUU[^UUUUUUUUUUUU(\?((\?:UPPPPPPPPPPP)" + "PPPPPPPPPPPPPPP]ffffffffffffffffffffffff)^[:space:]" + "wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww{243}9[:lowerprint:]Dv[:" + "graph:])][:blankcntrl:]V%E[:graph:]})[:space:]{-83,}cQZ{}4{-" + "23,135}", + 0, 0 }, + { "({,-76[}]O[:xdi(\?<!git:])\?5))))))))\?d[:lowerprint:]" + "b666666[:graph:]c", + 1, 0 }, + { "{}{-145,}[:(\?(spa)ce:])f", 0, 0 }, + { "[([^].{116,243}]T*[[^:punct(\?[{[^:(\?<!]]8()])[:alnum:])})]" + "N{}{,243}*[n]][:graph:]", + 1, 0 }, + { "[^w]8888888888888888_________(__________[:ascii:]BdqTE$^0|" + "MNto*i#############[^#################])", + 1, 0 }, + { "[^[[[<[()\?]GGG{,26[}[:alnum:]SSSSS.gggggggg[:graph:]" + "CCCCCCCCCCC{79,}{138,191}][:di(git:]u]@]" + "JJJJJJJJJJJJJJJJJJJJJJJ[:graph:(\?:][:alnum:]])[:alnum:])]", + 0, 0 }, + { "[^(((BBBBBBBBBB(\?>BBBZvvvvvvvvvv(\?m(sximsx:vvv)iiiiiiii)))" + "j>Rs:Sm]0MMMMMMMMMMM|@F)Y]*^#EEEEEEE)*", + 0, 0 }, + { "([^([(U(\?!)<<<<<<<<<<(\?#<<<<(\?<!<<<)(\?=L.{73,})+]n9U}fk%" + "Jn}'b Na<%yyyyyyyyyyyy)){-198,}]))[:space:].pP361U]3s@u_9AU " + "Te/{s`6=IMZdL1|.ySRo", + 1, 0 }, + { "[[((\?<=\?>(\?#){}]{}`){1,82}){-143[,}]^G", 0, 0 }, + { "[:digit:]W|[:up(\?<!perword:]{,-101}llllllllllllllllll[:" + "upperword:])mmYYYYYYYYYYYYYYYYYYYYYYY*", + 0, 0 }, + { "@NHy)", 0, 0 }, + { "([^[^]][:alnum:]222[^22222222(\?{2222222222222222][:lo(\?:" + "werprint:][:xdigit:]^[:blankcntrl:]s+N)[:alpha:]-" + "NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNWxxxxxxxxxxxxxxxxxxxxxxxxxxD[" + ":space:]U)TTTTTTTTTTfffffffffffzzzzzzzzzzzzzzzzzzzzzzzzz})", + 1, 0 }, + { "[^[^[[^[][^[]pP([^\?[^<=(\?=]){158,})]]]][:digit:]]" + "K22222222222p^dUKJ`\">@]", + 1, 0 }, + { "[^[^[(\?imsximsx::p(\?{unct:][(\?>:ascii:]5w)]{159}\\Q\?@C]" + "4(44444444}[^)|)[:graph:]]C:b)", + 1, 0 }, + { "[^[[(tYri[W<8%1(\?='yt][:lowerprint:[]))1r]][:alnum:][:" + "digit:]{48}{-52,-183}+][:alpha:]r][:upperword:]\?{-105,155}{" + "-55,-87}pPN#############################{63,232}]", + 0, 0 }, + { "[*(\?>L(\?<(\?>=))]&&&&&&&(&&&&&&&&&&&&&&&&&&))[|WIX]{-62,-" + "114}S K=HW60XE<2+W", + 1, 0 }, + { "(00000000000)z\\\\*t{}R{88}[:alnum:]*", 1, 0 }, + { "(([^(\?=\?gggggg[gLw)]{-250,}[:xdigit:]yZ[:g(raph:]8QNr[:" + "space:][:blankcntrl:]A)][:digit:]D)[:xdigit:])", + 2, 0 }, + { "[^([^,(\?<!]*))]", 0, 0 }, + { "[^(\?{[:alnum:]]}}}}}}}}}}}}}}}}}}}}}}}){-83}", 0, 0 }, + { "WWWWWWWW[:alnum(\?<=(\?#:]{,-1})@OSSS)[:digit:]", 0, 0 }, + { "[^(\?!*]+G)", 0, 0 }, + { "[LLLLLLLLLLLLLLLLLLLLLLLLLLLLLL>s8.>[^{}$(\?(]]XXXXXXX)" + "XXXXXXXXXXXXXX[:alpha:]Whii\?p[:xdigit:])+", + 0, 0 }, + { "(7777[:blankcntrl:])", 1, 0 }, + { "[^C[:digit:]]{}YYYY(YYYYYYYYYYYYYYYY)", 1, 0 }, + { "on|,#tve%F(w-::::::::::::::::::::::::::::*=->)", 1, 0 }, + { "([((\?=(\?!((\?=')))27(<{})S-vvvvvvvvvv(\?=" + "vvvvvvvvvvvvvvvvv[:punct:][:alnum:]}}}}}}}}}}}}}}}}}}}}}}}" + "PPPPPPPPPPPPPPPPPPPPPPPPPPPPPgggggggggggggggggggggggggg(\?#(" + "\?#gggggg<X){}]{-164,61})>+))uQ)W>[:punct:][:xdigit:][:" + "digit:][:punct:]{}[:digit:][:space:]){,-105}=xiAyf}o[:alpha:" + "]akZSYK+sl{", + 1, 0 }, + { "[^[^]/S:Hq<[:upperword:(\?<=]W[:alnum:]X])1973", 0, 0 }, + { "[[^[[^([^VVVV(\?!(VVVVVVVVVVVVVVVVVVVVV[VVVVX][^]2))" + "98ppppppppppppppppppppppppppppppp/////////////////////" + "b.]G{-101,}[:[ascii:]P].=~])AAAAAAAAAAAAA2{-153,}]]]]]]]]]]]" + "]]]]]]]]]]]]]]]]]]]]][:alnum:][:lowerprint:]WN/" + "D!rD]|4444{180}]V_@3lW#lat]", + 0, 0 }, + { "[^[^([^TTTTT(\?:T(\?:T7777{,59}])[:graph:][:ascii(\?<=:]))f]" + "AD{,-43}%%%%%%%%%%%%%%%%)S|[:digit:]FZm<[:blankcntrl:]QT&xj*" + "{-114,}$[:xdigit:]042][:xdig[it:]{-180}027[:alpha:][:ascii:]" + "[:lowerprint:][:xdigit:]^|[:alnum:][^Mi]z!suQ{-44,-32}[:" + "digit:]]", + 0, 0 }, + { ")", 0, 0 }, + { "''''''''''[:a(\?imsxisx:lnum:])P", 0, 0 }, + { "(([{20(\?<=8}[:alnum:]pP$`(\?#N)wRH[:graph:]aaaaaaaaaaaaaa(" + "\?=aaaaaaaaaaaaaaaaP]a)))[:punct:]-\?)A^", + 2, 0 }, + { "[^(.//" + "[:punct:]&-333333333333333333333333333(\?<!33)" + "LLLLLLLLLLLLLLLLL[:alnum:]$1]~8]|^\"A[:xdigit:]\?[:ascii:]{" + "128,}{,-74}[:graph:]{157}3N){-196,184}D", + 0, 0 }, + { "[^($(\?{(\?<=)[#)]})[:space:]]nWML0D{}", 0, 0 }, + { ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,[^]x{213,-93}(\?{A7]V{}})", 0, + 0 }, + { "[k(\?=*)+^[f(])r_H6", 0, 0 }, + { "[(\?#(\?{)]q})", 0, 0 }, + { "([GLLLLLLLLLL(\?!((\?:LLLLLLLL]))C#T$Y))^|>W90DDDDDDDDDDD[^" + "DDDDDDDDDDDDDDDDDDDD]B[:punct:]c/", + 1, 0 }, + { "[^(\?<!)(\?{b}){,199}A[:space:]+++++++(\?!++++++++{36}Tn])", + 0, 0 }, + { "()[:alpha:]a", 1, 0 }, + { "[(\?(:blan)kcntrl:])lUUUUUUUUUUUUUUUUUUUUUUU", 0, 0 }, + { "[^[^(s[[[[[[[[[[[[[[(\?#[[[[[[[)\?`````][:blankcntrl:(\?>]|)" + "p1EmmmmmmmmmmmmmmmmmmmmmmmmmmmmL{-241}666666666666666666666)" + "]^bLDDDDDDDDDDDDD]", + 0, 0 }, + { "[nn(\?<!nnnnn(\?#n8)=````````````````````{41,}]U,cb*%Y[:" + "graph:]).[:alnum:]\\\\\\\\\\gt", + 0, 0 }, + { "()\?5{,-195}lm*Ga[:space:]Y", 1, 0 }, + { "[(\?:].di)c", 0, 0 }, + { "([([^([\?{})Za,$S(\?!p(\?{++(\?##V(\?<!Evuil.2(\?<![^[h|[^']" + "C)*\"]5]", + 1, 0 }, + { "[((^24(\?#4[^Kkj{}))]]{232}47)077[:alpha:]zzzzzzzz{}", 0, + 0 }, + { "[^(\?:[^F]o$h)-iV%]", 0, 0 }, + { "[[^[([((([^(\?{[^((\?=)kaSx(\?imsximsx:w3A[`%+A$I{,62}ns&Y!#" + "ay " + "o9YAo{Y>1((\?>\?#45)Z{,108}{}11111111111111111111111111qqqq)" + "\?][:lowerprint:]mbo#)@", + 0, 0 }, + { "[^iii8(888888(\?<!8^]))s", 0, 0 }, + { "([[(\?(\?:({^]}[)[(r)])G]{,-87}", 1, 0 }, + { "([[^{249,}(\?>(\?=)]]T()[:bl(\?!ankcntrl:]=jjjjjjjjjjjjjjjj-" + ")))t{}[:alpha:]-\":i! Gn[A4Ym7<<<<<<<<<<<<<<<<]", + 2, 0 }, + { "^{}{[^,241(\?#}(\?m(\?ixim:sximsx:]t))+oD)", 0, 0 }, + { "5[(\?#:xdigit:])", 0, 0 }, + { "[^f{(\?>,22(9}[^[^])6KKKKKKKKKKKKK)]RRRRRRRRfuK99999999C}" + "osnNR]BgCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC[:blankcntrl:]", + 0, 0 }, + { "[^(\?=U){24,}W-{,17(\?:3[^}]q.nQ#PU_|i$$$$$$$$$$$$$$+)[:dig(" + "\?<!it:]){-98}\?[:upperword:]]", + -1, 0 }, + { "[(\?<=[0(\?!72])euE.]{,-159}[:alnum:]t-:l\?)$" + "yyyyyyyyyyyyyyyyyyyyyyyyyyfffffffffffffffffffffffffff", + 0, 0 }, + { "[^[^]q[:asc(\?imsxmsx:ii:]JJJJJJJJJJJJJJJJJJJJ[:graph:]]$)`#" + "DdY^qqqqqqqqqqqqqqqqqqqqqqqqqqqu>4^4ta[:alpha:]", + 0, 0 }, + { "(((b0HN)q))p5<T())`7JJv{'cv'#L8BNz", 4, 0 }, + { "[pFp2VttBg(\?<=7777777777777|TTTTTTTTTTTTTTT[:space:]Z]^p\"[" + ":blankcntrl:])", + 0, 0 }, + { ")aM@@@@@@@@@@@@@", 0, 0 }, + { "([^[(\?<![^])", 1, 0 }, + { "()Z[:ascii:]", 1, 0 }, + { "(fuPPo)..........................[:xdigit:]{}{,4}*kkkkkkkCx#" + ",_=&~)|.2x", + 1, 0 }, + { "[+(\?<=){}++++++[:alnum:](\?=+]s)[:alnum:]~~~~~~" + "XXXXXXXXXXXXXXX.[:digit:]", + 0, 0 }, + { "[{}[^^(\?(]))CCCCCCCCCCCCCCCCCCCCEg2cF]{}3", 0, 0 }, + { "([[[^[^[^([[^[^([(\?<=G[[)=(\?!===(\?isximsx:==(\?#==[^=====" + "(\?{==================$T[[^^u_TiC.Fo.02>X)uH]$})354b[:alnum:" + "]]]EVVVVVVVVVVVVVVVVVVVVVVVVVVVVVz[:digi(\?(t:][:upperword:]" + ")", + 1, 0 }, + { "([:blankcntrl:]t-){121,}[:ascii:]444444{}[:graph:]E040", 1, + 0 }, + { "[^{134,}]DzQ\?{-30,191})z,\?1Vfq!z}cgv)ERK)1T/=f\?>'", 0, + 0 }, + { "@v)<yN]'l-/" + "KKKKKKKBBBBBBBBBBBBBMa2eLA[:digit(\?<!:])\"\"e|l$&m`_yn[:" + "blankcntrl:]uuuuuuuuuuuuuuuuuuu[:punct:]", + 0, 0 }, + { "[[999999999999999(\?<=(\?:(\?ixmx:(\?>))])Y]|){,10}\?{}", 0, + 0 }, + { "([[[(\?!^]P-AA[AAAAAA[A[^A)r]+B]])", 1, 0 }, + { "3}|[:ascii:][:punct:]()", 1, 0 }, + { "()dw", 1, 0 }, + { "[N]{})))))))))))))))))))))))", 0, 0 }, + { "[[[^([[(\?()(\?#)++([^\?{+++[^+++++++++++(\?!+(\?=+++++++r9/" + "n]N7{-219}{-91}pP[:punct:]T]mROm+~[:digit:][:digit:])Y:", + 0, 0 }, + { "[^'Pu[(\?<!D&]_a[:alnum:]E<,F%4&[:xdigit:])][:lowerprint:]", + 0, 0 }, + { "tttt(tttttttttt*uKKUUUUU)", 1, 0 }, + { "([:ascii:]GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG)+kX______________{" + "}GGGGG\?TUH3,{67,77}|[:graph:]C{,-136}{}[:upperword:[]{,-6}&" + "]T84]n={C", + 1, 0 }, + { "[:upperword:]DC[:u(\?<=pperword:]*d`H0\?m>~\?N|z#Ar--SO{,-" + "141}076)G\?{,-110}M+-[:alpha:]", + 0, 0 }, + { "{,-214}{,10(9})", 1, 0 }, + { "([^xxxxxxxxxxxxxxxxxMMMMMMMMMMMMMMXW])].[:punct:]Q`{-63,63}" + "Uua[:alnum:]\?OQssb#L@@@@@@@@(@@@)[:graph:]", + 2, 0 }, + { "[[^(\?!```[^``````````````(\?<=``(\?>````````M/////(\?!/////" + "///////////////" + "[^GD!|#li]~)*.$]))Tq!]C[:lowerprint:]Qk[{}]]" + "JJJJJJJJJJJJJJJJJJJJJJJ{e])c", + 0, 0 }, + { "$[5(7ES])[:xdigit:]%{MRMtYD&aS&g6jp&ghJ@:!I~4%{" + "P\?0vvvvvvvvvvvvvvvvvvvv\\\\\\\\\\\\\\\\\\\\\\\\x54[:" + "lowerprint:][:upperword:]", + 0, 0 }, + { "[((([(\?((\?>[:alnum:][):as(\?<!cii:(\?:]Re))K|)|^){-28,89}" + "l<H.<H:N)QKuuuuuuuuw8E136P)^)[:ascii:]][:xdigit:]-", + 0, 0 }, + { "(pjvA'x]=D\"qUby\\+'R)r\?C22[:ascii:]", 1, 0 }, + { "[]*b~y C=#P\"6(gD%#-[^FBt{}]${-244}", 0, 0 }, + { "[:up(\?!pe(\?=rword:])lA-'yb\"Xk|K_V\"/" + "@}:&zUA-)W#{-178,-142}(){-202,}", + 1, 0 }, + { "()1.WldRA-!!!!!!!!!!!!!!!!!", 1, 0 }, + { "lZZZZZZZZZZZZZZZ(Z[:al(\?:num:])" + "ttttttttttttttttttttttttttttttg.)6$yyy", + 1, 0 }, + { "[([^([^[^(([([^[^(([[$(\?{P(\?=(\?<(\?!=(\?#P[^Y])<GA[:" + "ascii:][(\?#(\?<!:alpha:](B{100,})]}))\?)XU=", + 1, 0 }, + { "[[dVw{6(\?{9,}2222kkkkkkkkkkkkkkkkkkkkkkkkkk|{}*E]]{}SB{35}-" + "w%{eh})<{-178,}", + 0, 0 }, + { "(D(~))", 2, 0 }, + { "[(:alpha:]{,90}Z|)[:ascii:]Du\?[:grap[^h:]^w+|{}][:ascii:]", + 0, 0 }, + { "[:p(\?<=unct:]kkkkkkkkkkkkkkkkkkkk)", 0, 0 }, + { "{}[:((\?<!dig((\?#it(\?#:]())p))ZZZZZZZZZZ[:blankcntrl:]){}{" + "-124,})[:ascii:]", + 1, 0 }, + { "[[:graph:]{168}lRRRRRRRRRRRRR(\?#RRRRRRRRRRRRRRRRR)rrrr(\?(" + "rrrrrr)rrrrrrrS[(\?<!@f)6>{,-49})q${98,}J\?]){", + 0, 0 }, + { "([:pu(\?(nc)t:]F{-32,-102}+)\?cpP[:lowerprint:].^)", 1, 0 }, + { "([{}{210,-238}]1:h)", 1, 0 }, + { "([]QQQQ[QQQQQQQQQQQQQQQQQQ][:digit:]Z{-20,}Slllllll[:space:]" + "C^(@{-174,-156}fx{cf2c}{-242,}rBBBBBBBBBBBBBBBBBBc[:alpha:]" + "N\?))$[:graph:][:ascii:]P+nnnnnnnnnnnnnnnnnnnnnnn1N$r>>>>>>>" + ">>>>>>>>>>>>>>>>>(>>{,88}{,-234}__________)[:upperword:]R.[:" + "alnum:][:lowerprint:]^}\"", + 3, 0 }, + { "([^(\?=]-))$", 1, 0 }, + { "([:ascii:]\?,D[:upperword:][:xdigit:]tttttttttttt[^tt(\?<!" + "ttttttttt21f|.(pP[:punct:])])rrrrrrrr)", + 1, 0 }, + { "([{1(\?=16}iiiiiiiiii((\?<=iiiiiiiiiiiiiiiiii|ZZZZZZZZZZZ(" + "\?(\?#{ZZZZZZZ))c}))<<<<<(\?#<<<<<<<<<<<d7CVq8]w{-148,-168}" + "\\Gp){-230,}D3", + 1, 0 }, + { "[^8888(88888888888EX].[:alnum:]){}", 0, 0 }, + { "([^][^)2]-[:lower(\?=print:]{,79}[:graph:]n)", 1, 0 }, + { "[bSi\?x_mp(C)0{64}[:space:]hhh(\?(hhh)hhL){5,130}'w\"$l&[:" + "xdigit:][:alpha:]IIIIIIIIIIIIIIIIIIIIIII+-SOOOOOOOOOOOO " + " (\?( ) ]f)ed", + 0, 0 }, + { "[[^[(^(C.Jl[^X&Rb64a+Sd])'m[:alpha:])]]]{134,}", 0, 0 }, + { "()L", 1, 0 }, + { "[[(({224,(\?#88})@======(\?!=========(\?{=)PPP)i^@p(\?([:" + "punct:]})^^[^^^^^^^^^^^^^^^^^^^^^@)m]|{CS{,-3}168)-[:xdigit:" + "][:upperword:]hnD=Bns)z)AAAAAAAAAAAAAAAAAAAAAAA[^A{}" + "ccccccccccc)SZ]Q-p.sD]]+P", + 0, 0 }, + { "[[^[^]{135,}66666666666666666666[6(666i2M9.!uhmT\?JMm.*(\?!+" + ")[:alpha:]eeeeeeeeeeeeeeeeeeeeeeeeeee]]])ZZ[:blankcntrl:][:" + "ascii:]", + 0, 0 }, + { "(13[3Ux>{,10}[(\?<=:xdigit:]))PL9{-89,-181}F'''''''''", 1, + 0 }, + { "[^.|(\?{af]})^$XE!$", 0, 0 }, + { "(WWWWWWWWWWWWWWWWWWWWWWWWWWWW#J)", 1, 0 }, + { "({}}M7we-216)L[:digit:][:upperword:]", 1, 0 }, + { "([:aln[^u(\?=m:]))].z", 1, 0 }, + { "([:alpha:]{(92})%6{41,136})Vij@[:alnum:][:lowerprint:]", 2, + 0 }, + { "[[[++(\?{+++{}})n{{137,}{51,-177}Z[]M*[:ascii:]{(-29,-47}2)$" + "e^{,-195}{-156,}^]{}{-225,69}A]{-222,}{,20}m[:blankcntrl:]", + 1, 0 }, + { ")l)[:alnum:][:graph:]g8TTTTTTTTTTTTTTTTLLLLLLLLLLLLLLLLL", 0, + 0 }, + { "[([(\?<=.(\?{)/})mmmmmmmm(\?(mmmmm]{-154,-176}*S)I]", 0, 0 }, + { "[(([{(\?(\?<!im(\?imsix:sim(sx:,141}])D)l{,42}ttttt[(\?::" + "punct:])){-162,-141}{-26,})dU@@@@@@@@@@@@@@@ " + "S)\\A\?w|VVVVVVVVV)X.kN{,21}{-208,-52}>[:lowerprint:][:" + "ascii:]e-]]]]]]]]]]]]]]]]]]]]]", + 0, 0 }, + { "[^({}(){(66(\?=,}[^]'''''QQQQQQQQQ).P#>^){86,168}Z[(\?<!:" + "lowerprint:]{-166,-70}<k", + 0, 0 }, + { "APP[:alpha:][:alnum:]nd[:upperword:(\?(]^" + "xxxxxxxxxxxxxxxxxxx)xxxxxxxxx{-70}[:punct:]l)U-", + 0, 0 }, + { "[^(.\"od~(6({[^(\?<!228}\?)\?)######(\?:#########z " + ")c(\?<!aQ`(\?{UKSwu[})][^-17]{11,}}][:ascii:]))^RiH+WyspP[" + "qi&)=p6])[:space:]{-221,}]6p", + 0, 0 }, + { "{-78}()[:xdigit:]{155}{,-92}", 1, 0 }, + { "[(\?>Q{,147}_____________(\?!______uuuuuuuuuuuuuTr]){74,179}" + "{}){,103}{-209,16}*RRRRRRRRRRRRRRRRw{,87}9{144}[:ascii:]'<" + "Ab", + 0, 0 }, + { "([666c] {-171}yc,8-k_)EEEEEEEEEEEEEEEEEEEEE<", 1, 0 }, + { "[^(\?>(\?<!)2(\?imim:)6HwN)^|fc!(\?(d]75))065)G", 0, 0 }, + { "[[^xDB[:alnum:][:xdigit:]][:digit:]jW]([:alpha:])", 1, 0 }, + { "[ds~T+[x55[:digit:]X[JJJJJJJ.[(\?::upperword:]){,-14}][:" + "xdigit:]bbbbbbbbbbb", + 0, 0 }, + { "[qqqqq(\?<=qqqq(\?(qqq)^G[):ascii:]])W", 0, 0 }, + { "[:space:]JJJJJJ[:alph(\?<!a:]|[:ascii:(\?(])[:x)digit:]- " + "XSstG[:g(\?>raph:])^)Ny6RF_ndoU9@*rxW{4,41}4{}", + 0, 0 }, + { "[:punct:]{162,}j[:aln(um:].....................[^...]\?>z[:" + "l[owerprint:]){55,222}]", + 0, 0 }, + { "(>vWa)OXcccccccccccccccccccccccc[:alpha:]C{,-10}81|m1D^T)[:" + "lowerprint:]''''[:alpha:]l", + 1, 0 }, + { "(XZcgM/UI-/" + "mZq-222){-85,-196}[:alpha:]{114}rrrrrrrrrrrrrrrrrrrrrrrr{," + "157}ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZLkD-&&&&&&&&&&&&&&&-][:" + "alnum:]{}{,111}[:digit:]", + 1, 0 }, + { "[^(\?:]MMMMMMMMMMMMMMMMMMMMMMMMMMM)cK[" + "KKKKKKKKKKKKKKKKKKKKKKKK]P{146}", + 0, 0 }, + { "([^[^wqesa)n\?L(\?<=FH+G[^rCGmfD]w)m1D\"%}]])", 1, 0 }, + { "[((\?:[^.HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH|S)xd)*[:space:](])[" + ":xdigit:]ngr'G#/B]-----------------------------", + 0, 0 }, + { ")[:lowerprint(\?<=(:]l))G p", 0, 0 }, + { "[^[^(\?<(\?<(=(\?imsximx:![(((\?<!\?(^))\?]^)[:xdigit:][:" + "graph:]{-104,})Gf+GD*qc)c]f))])", + 0, 0 }, + { "[^([\?())P[:alnum:]w]{-186,-139}-[:space:]RN3w[Fmvpl[:space:" + "][:digit:]&&&&&&&&&&&&}(\?#}}}}}}}}}}}}}}}}}}}])z", + 0, 0 }, + { "([[^^*C[()f][(\?=:punct([\?#:]o)]V)]%%%%%%%%%%%%%%%%%%%%%%%%" + "%%%%%%[^x{1f948})]]", + 1, 0 }, + { "[(:xdigit:])zE", 0, 0 }, + { "[:pu(\?(nc)t:])(a*){-51}", 1, 0 }, + { "[^(.NKKKKKKKKKKKKKKKKKKKKKKKK-[:upperword:][:space:]`MPi>", + -1, 0 }, + { "Nvvv[vv.][:alnu[^m:]+|Crrrrrrrrrrrrrrrrrrrrr[:xdigit:]j1n)v#" + "]", + 0, 0 }, + { "[^#}[(\?>:alnum:]).QQQQ[^QQQQQQ!!![!!!!!!!-s.n]se]{-238,}Tf]" + "p4721", + 0, 0 }, + { "([((\?#\?<=)+)Hr:-H]z[:graph:].{}oooooo(ooooooooo][:punct:]" + "k<gXG@@@@@@@@@@@@@@@@@@@{,-176}){}L`)$", + 2, 0 }, + { "({,249}{-73,}Z&&&&&&&&Ds35MB<v)qqqqqqqqqqqqqqqqqqqqqqqqq", 1, + 0 }, + { "[^.N][:blankcntrl:]))))))))))))))))))))))))))))))", 0, 0 }, + { "(()*){198,}", 2, 0 }, + { "{-237,}220{}[:ascii:]```````(`````````````\?{-115,185}){,-" + "18}[:punct:]'|Kk", + 1, 0 }, + { "[(\?()])", 0, 0 }, + { "([(\?#[:alnum:]CQ)}}}}}}}}(\?>}}}}}}}(}}}}}\?310[|))xA5r][[^" + ":ascii:]^{,-156}{])CCCCCCCCCCC-145]FzwOD_u\?", + 1, 0 }, + { "[^[^[]{-163}{(-203}[(\?!:upperword:]PPGjZ[:xdi(\?=git(\?#:]{" + "-73}s)qqqq(qqqqqqqqqqqqqqqqqq{173,210}[:xdigit:(\?<(\?>=]WW[" + "^WWWWWWW\?*O)))Q){}08)[(\?(\?<=#:blankcntrl:]{90,}]U)])L)" + "ooooooooooooooooooooooooooox--^c[:ascii:]])s)", + 2, 0 }, + { "[(\?!:punc(\?imximx:t[^:]4F<}!)]'M-)tKKKa4904", 0, 0 }, + { "[^^{}\\(\?<!\\\\\\\\\\\\\\\\\\(\?#\\\\\\\\[:punct:](\?>)" + "T000000000(\?(000)00000))+])", + 0, 0 }, + { "L[:p(\?#unct:])", 0, 0 }, + { "[:upperw(\?<!ord:])", 0, 0 }, + { "@$\"\"\"\"\"\"\"[\"\"\"\"\"\"\"\"\"\"[^(\"\"\"\"\"(\"\"][]))" + "*U{223,138}*o```````````````(\?=[```````````````]{238}" + "mmmPPPPPPPPPPPPPPP&&&&&&&&&&&&&&&&&&)sF$[:digit:[]]", + 0, 0 }, + { "[^#Txx[xxxlPB(\?><[^U/)]]{}X3333333333(3333333f*])", 1, 0 }, + { "<<<<<<<<<<<<<<<[^<<<<<<<<<.][(\?#:ascii:])[:xdigit:]|^", 0, + 0 }, + { "([:punct:]{}){-167,}{-59,}Pd\"", 1, 0 }, + { "[((\?#{,214})t$)VVV[:xdigit:]{104(\?<=}D][:graph:])|H){1,}{-" + "176,}", + 0, 0 }, + { "[[([[^N,,,,,(\?=,,(\?#(\?:,,,,,,,,,,,[^,,,,,,,,,,]<,~4::_.A]" + "){-52,}-[:alnum:]Pnnnnnnnnnnnnnnnnnn)d", + 0, 0 }, + { "{-18(3,})uT{4,}", 1, 0 }, + { "[^[^[(p+c(\?<!b$))(\?:EU(\?(.][^{}]3[:xdigi[^t):][:punct(\?>" + ":])[])][:s[^pace:]][:alnum:][:alpha:]]kw06E", + 0, 0 }, + { "[^^^^^^JJJJJJJJ(JJ(\?=JJ(.6[:space:]H]{231,}A^eqqq)[:ascii:(" + "\?>(])[(\?>:spa(\?:ce:]xxxxxxxxx)@_t-))" + "138GNNNNNNNNNNNNNNNNNNNNNNNNNN[:digit:]no!`#E\?&[:" + "lowerprint:].)[:graph:]{86,}[:digit:][:alnum:]", + 0, 0 }, + { "[:g(\?<=raph:]a{114,146}(){}0Y[:bl(ankcntrl:])D)\?", 1, 0 }, + { "[^[^]*H{-192,96}S|]G)6B-kLB", 0, 0 }, + { "[[^[^][/" + "NS8`um(\?{82&{((\?{\?<!-[110,-88}]m)})kkkkkkkk$$$$$$$$$$$$[^" + "$$$$$@n%BuK@X!P)y0v!^]YY[YYY[YYYYYYYYYYYYYYYYYY///////" + "{}{{{{{{{{{{{{{oiiii})]8{-2[53}w{82,}]{,245}]{-134}]" + "fffffffffffffffffff]\"I>DW>9tN%{113}{unE", + 0, 0 }, + { "[:(\?(alpha:]`))Y2sCqWQ104", 0, 0 }, + { "(([^()Wcccccccc(\?{cccccccccccccccccc(\?<!c(ccccc[:space:]$)" + "(\?>)FZ{}{}`|||||||||||||*````````````````````````````'=" + "dLQmx/" + "Y.A7j'o}jn{}:})][:punct:]$|,-)!&Y:Ys#" + "ykL7JJJJJJJJJJJJJJJJJJJJJJJJJ8yex>#mv[:punct:](x@)$[:uppe(" + "\?<!rword:])_)", + 3, 0 }, + { "[[(^HHHHHHHHHHHH(\?imsximx:HH(HHHHHH(\?{HH[HH])qjR>9))i})]a!" + "lBW3p{A=or)ShE%[:punct:]{}]5r", + 0, 0 }, + { "[:pu[nc[^t:]]]}}}}}}}[}}}}}}}(\?#}])@@@@@@@@@@@@@@@@@@" + "DDDDDDDDDDDDDDDDDDD\?]xA2\?", + 0, 0 }, + { "(.[:alpha:]xB7[:alnu(\?{m:]})RRRRRRRRRRRRRRRRRRRRRRRRRRRL)[:" + "space:]G\?", + 1, 0 }, + { "[:blan(\?<!(\?=kcntrl:]){71,})!ooooooooooooN", 0, 0 }, + { "()e$$$$$$$$$$$$$$$$$$$$iiiiiiii", 1, 0 }, + { "(b[:ascii:]67777777777777777777777777)({-106}kkk^F----------" + "---------------------{13}A)f00000000sBAddddd{-66}kd!D'", + 2, 0 }, + { "(Q ^])[^lf][:space:][:lowerprint:]\?", + 1, 0 }, + { "[[^]\\S{152}W![:digit:][[^:space:(\?(]=pEhwY][:alnum:][:" + "digit):][:graph:]])QQIC9h-oowf[:xdigit:]{-52}{,190}" + "1111111111111111111fX{-189,226}W", + 0, 0 }, + { "[^(\?!(\?<=)]).h[:as(\?>cii:])[:alnum:]$$$$$[:space:]3$$$$$$" + "$$$$$$$$$$$$$$$$$$$$$$$$$1", + 0, 0 }, + { "[[$zQ================(\?<!=(\?>=========(\?====D[^))|i{}" + "\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?)][:s(pace:]])" + ")]", + 0, 0 }, + { "[^{,-[15(\?#6}]Vwjjjjjjjj[jjjjjjjjjjjjjjjjjjS9999)]q]" + "rWWWWWWWWWWWWWWWW[:punct:]@@@@@@@@@@@@@@@@@@@@@@@@gO[:" + "blankcntrl:]>L[:ascii:]:::::::::::::::::::" + "x11uuuuuuuuuuuuuuuuuuuuuuuuuuuuu{-124,114}[:graph:]C#{tcg[:" + "xdigit:]gZZZZ[:lowerprint:]nA(_{{{{{{{{{{{{{{{{{{{{SS)\\D[:" + "alpha:]", + 1, 0 }, + { "[^(\?())]!T\?[:asc[^ii:]E:4},,]I[^b(\?:n4(njj~+{\?'k{7}{189," + "-194}{ig.[[[[[[(\?#[[[_bs6,JD`1(\?<!WBo]F+{d*VO22z2K1][:" + "xdigit:]))Suuuuuuuuuuu[^u{,117}\?YYYYYYYYYYYYYYYYYYYYYYYYB^]" + "|q]:eY1GGGGGGGGGGGGGGGGGGGGGGGGGGGGe\?)bU[:punct:]", + 0, 0 }, + { "[\?UA(\?:]\?)[:xdigit:]A^mmmmmmmmmmmmmm>>>>>>>>>>>>>>>>>>>>>" + ">>>>>>>[^>>>(\?(>)){,-165}]", + 0, 0 }, + { "([^[][^n(\?{[[p]#})|][^]L|66666666666[:graph:]][:graph:]2[:" + "xdigit:][:space:]9b})[:digit(\?imsximsx::]+PZ):{}|E)[:" + "xdigit[^:]|>]^[:alpha:]::::::::[:ascii:]````[:ascii:]:", + 1, 0 }, + { "[:lowerprint(\?<!:])", 0, 0 }, + { "[[^[]{-47}[:lowerprint:][:punct:]L[(\?::g(raph:]lY[:alnum:])" + "qWYU)}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}[c%$dp5[:alnum:]DDDDDDDD^" + "^%&{,-94}E]{-8,175}[:alpha:]-.^[:digi(t:]CCCC(CCCCCCCCC])." + "ax72)", + 1, 0 }, + { "[[^($$$$$$$$$$$$$$$$$$[^$I((\?{\?(u)\"YuK " + "ZpOHq[!(\?>t|LQT(|)L[(:ascii:])", + 0, 0 }, + { "[^[^([:graph:](QpPdyDQ`[:alpha:](.X[:digit:]wwwwwwwwwwwwww(" + "\?imxims:wwwwwwwe(\?<!z)ONNN(\?#)[^])[:space:](KKKKKKKKK{" + "113,}327[:xdigit:]k)]CeeeeeeeeeeeeeeeeeMMMMMMMMMMMMMMMMM)[:" + "lowerprint:]]HHHHHHHHHHHHHHHHHHH]]]]]]]]]]]]]", + 1, 0 }, + { "[Q(r(\?=)v]dm[:alnum:][:b(\?{lankcntrl:][:xdigit(\?=:])})P[:" + "graph:]bd/Rx){50}{-150,-172}", + 0, 0 }, + { "[(\?(im(\?:sxims:))9]))L", 0, 0 }, + { "[[^[(\?{^Z][^0[:alpha:]]\\XB*{-151}t})][:alnum:]]", 0, 0 }, + { "[([(D\?/////////////////////.'yvYysU&5AU-]kV)*){,123}z]", 0, + 0 }, + { "[:alnu(\?{m:][:a(\?=lpha:][:alpha:])n}))7[:ascii:][:xdigit:]" + "[:punct:]-", + 0, 0 }, + { "[^[:graph:]IIIIIIIIIIIIIIIIIIIIIII][:sp(\?<!ace:])", 0, 0 }, + { "[[[(\?=[[[cDD(\?<!D(\?:DDDDDDDDDDDD(\?<=DDD(DDDDDD(\?:" + "DDDDDDD(\?<=D(\?()])rvp{243,}D$<[:space:]([:lowerpr)int:])])" + "Ea{}U[:upperword:][:xdigit(\?#:]or}Z+34gD{/P NJ", + 1, 0 }, + { "[^(,H>)*d2K0DNX5)T(].)[:digit:].", 0, 0 }, + { "([:punct:(\?#])})JJJJJJJJ[:xdigit:]" + "PPUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU.......................0hSk{" + ",89}[:xdigit:].[:xdigit:]Z", + 1, 0 }, + { "(LGTTTTTTTTTTTTTTTTTTTTTTTTTT[:alpha:]){-106,113}[:punct:]d|" + "[:digit:]kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk\?wP", + 1, 0 }, + { "([^[^<N_-k\?{(\?#18}]i]::::::::::::::::::::::::::)1+LLLLn{}/" + "){-198}", + 1, 0 }, + { "([[^(AAAAAAAAAA(\?(AAAAA)AAAAf).LzHHHHHHHHHHHHHHHHHHHHH(\?#" + "HHHHH|)[ZEEEEE(\?#EEEEEEEEE(\?<!EEEEEEEEsG)q[:punct:]{}][:" + "upperword:]D)[:space:][:digit:]+e[:ascii:]].i|JJJJJJJJ+n][:" + "xdigit:]Se)P[:lowerprint:]_______________________________.[:" + "punct:]pP{-172,86}iiiiiiiiiiiiiiiiiiiiiiiii){,-178}", + 1, 0 }, + { "([\?=[[^,BDRRPZ{129}*D-[:punct:]]])([:upperword:]ud)\?][:" + "punct:]A", + -1, 0 }, + { "(([(\?#((\?{\?=^])c-)C[:lowerprint:]xvkR}k\")" + "ccccccccccccccccccccNNNNNNN[:alp[ha:]{,93}vhlX:|A]2})nSw)]" + "N.", + 2, 0 }, + { "()g/qzyiV(x3d|A0wllllll){162}[:space:]", 2, 0 }, + { "qqqqqqqqqqqqqqqqqqqqvvvvvvvvvvvv8[:x(\?imsxmsx:digit:][:" + "alpha:]''''''''''''''''''''''''''')", + 0, 0 }, + { "({,226}nf^W=vs$xK^=A=M#b,)V", 1, 0 }, + { "(_T 2BC9N'cccccccccc-87EF#&^eQfDDDn._,m&c`tjAwR " + "#~A)[:(\?imsimx:alpha:])/yHYL6|{-40,47}", + 1, 0 }, + { "[[^]{-8(4,138})z[:xdigit:]{180,}]", 1, 0 }, + { "[([^T____________________(\?:__C(\?<=]-)])+[:ascii:])r[:" + "graph:].----------", + 0, 0 }, + { "[f{}LLLL(LLp((((\?<!((((((((((((((({,56}]BR`{,52}){-22,}\?[:" + "space:]h>Sow", + 0, 0 }, + { "{-179}^[:alpha:(\?!].a'5wacA3\\\\\\\\AAAAAAAA)~^]wC", 0, 0 }, + { ">[:digit:]{,-212}+(`)LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL[:ascii:" + "][:digit:][:space:]", + 1, 0 }, + { "[[^[[^RBW{,255(}(\?(\?>=(W)_]uu][:blankcntrl:])O)]]", 0, 0 }, + { "(C_______________________________)2", 1, 0 }, + { "([/ntf_a3].)", 1, 0 }, + { "[:space:]+[(:upperword:],c7[:asci(\?<=i:]ggggggggggg)[:" + "ascii:]/1$$$$$$$$$$$$$$$$$$$$$$$$$$)", + 0, 0 }, + { "Xq{109}~EEEEEEEE[:upper[^word:]lgB:X(h[:alpha:]B[:space:]].)" + "IkaH@3}}H'yK~\?[:upperw(\?#ord:(\?:]){=================[:" + "blankcntrl:])", + 1, 0 }, + { "(([[^]]$3Xr^$%%%%%%%%%%%%%%%%%%%%%================U[:ascii:]" + ")X).FFFFFFFFFFgO[:punct:]oooooooooooooooooooBC[:blankcntrl:]" + "mmmmmmmmmmmmmmmmmmmm[:lowerprint:]rBM~<HAc#Sb&&&&&&&&&&&&&&&" + "&&&&&&&&&&&&&&Cy", + 2, 0 }, + { "([([([^(\?:)D]-{M#H " + ">rERRRRRRR[^RRRRR(\?>RRRRR])[(\?=^)X]{207,}U])))Z[:" + "blankcntrl:]]yyyyyyyyyyyyyyyy\?", + 1, 0 }, + { "[Q(\?{*[^(\?(\?!!])[:graph:]]})[:alnum:]iE)dGGGGGGG[^" + "GGGGGGGGGG[:xdigit:]w]", + 0, 0 }, + { "[^Z(\?!6(\?(\?><=)[:graph:])]BBBBBBBBBBBBBBBB^)", 0, 0 }, + { "[[^([^[^][[[[[[[(\?({[[(\?(\?imsxmsx(\?imsi[ms:::[[[[[[[[[})" + ")]$)){12,})|:::::::::::::::::::[:lowerprint:]{}{-96,-147}){" + "13,}`[:digit:]]\"^Ca%%%%%%%%%%%%%%%%%%%%%%%%%%" + "UUUUUUUUUUUUUUUUUU]]9", + 0, 0 }, + { "[^(\?(\?(\?#!<=))JLBS\"zi)'''''''''''['''''''''''''" + "piiiiiiiiiiiii(\?<=iiii]])ZZZZZZZZZZZZZZZZZZ[:space:]", + 0, 0 }, + { "({})[:punct:]", 1, 0 }, + { "E9[:blankc(\?{ntrl:]})N", 0, 0 }, + { "[:alph(\?#a:]){198,}sq\?X0B7", 0, 0 }, + { "[^\\\\\\\\(\\\\\\[\\\\\\\\\\\\[(\?<(\?isximsx:={11(\?(9,}" + "\?0])]]))\?FN3M\?{-128,}Z444444)444fbLiVN8)", + 0, 0 }, + { "[[^[^([[[[[[[[[(\?>[[[[[[[[[[[[[[[[[[[[[{53(\?<=,-175(\?>}" + "ggggggggggggggggg%))[:alnum:])[:punct:]" + "kkkkkkkkkkkkkkkkkkkkkkkkk)+" + "Soooooooooooooooooooooooooooooooo](WR+--)x36+llllllllllll{," + "35}]Fqb^=F]KKKKKKaaaaa{,131}", + 1, 0 }, + { "(g\"Ssqw<&{Cl{82,}Mdf|9cIlmCW{}[:digit:]4C{}[:alnum:]PP)", 1, + 0 }, + { "OOOOOOOU[*evVIIIIIIIIIIIIIIIII(\?#(\?#IIII)]PP[:xdigit:]" + "2222222222222222[:xdigit:]Kx)p[:digit:]", + 0, 0 }, + { "([[{248,16(\?=5(\?#}][:alpha:])|[:p(\?!unct:(\?(]", 1, 0 }, + { "[pP((\?=S)(\?#)]$[:aln(\?(um:)]2\?)$GGGGGGGGGGGGGGGGG({-U:c)" + "{-61,}[:ascii:]{-202}G", + 1, 0 }, + { "()$D[:alnum:]", 1, 0 }, + { "[(\?#^]){}[:ascii:]", 0, 0 }, + { "[uuuuuuuuuuuuuuuuuuuuuuuuuuuuuu]FFFFFFFFFFFFFFFFFFFFFF&2e\?)" + "%oP'mc@z2b}n{<b4_Laz^0LLLLLLLLLLLLLLLLLLLLLLL,,,d", + 0, 0 }, + { "{}(^________________''|$)RRRRRRRRRRRRRRRRRRR", 1, 0 }, + { "(H)####################bbbbbbbbbbbbbbbbVSSSSSSSSSSS|" + "tdU\"goeAbPP{-248,81}", + 1, 0 }, + { "[^[(\?ims(\?>xisx:)UHpP*n{}]{}fx14<7OEpE>n2150)" + "8888888888888888]^GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGS", + 0, 0 }, + { "(d)+", 1, 0 }, + { "[^.(\?(>)(\?=e)])al[:space:]x", 0, 0 }, + { "[^256c(\?!]){-19,}", 0, 0 }, + { "Q)", 0, 0 }, + { "[^s\?\?(\?{\?\?\?(\?#\?(\?<!\?\?\?\?\?\?\?\?\?\?\?(" + "\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?{}]F\?j(jjjjjjjjjjjjjjjjjjn)" + "kTI1f[{1|(\?<=^[^+[:digit:]{}^s^))})))T]{-17}{CCCCCCCCCCa{-" + "21,}{,-146}^uZQB]YuLu-|tUGRMz^^", + 1, 0 }, + { "([^.{}.EE[EEEEEEEE(\?<=EEEEEEEEEEEEEEEU]]-@s))$", 1, 0 }, + { "[^([((\?#[#])|a)])[cccccccccccccccc][:digit:]LLLLLLL[:alnum:" + "]}[P%vzl{}^]&", + 0, 0 }, + { "({}[:space:]E)101+A{-35,11}", 1, 0 }, + { "(va:7)u[:alpha:]", 1, 0 }, + { "([^[[rrrrrrrrrr(\?:rrrrrrrrrr(\?<!rrrrrrrrry|D'*AH@a{}\?[:" + "space:][:alpha:]^]$ " + "{-225}[(\?(:as)(\?(>cii:])){-107,-139}6/" + "{^[:upperw(\?imsxmsx:ord:]{,-47} " + "]wuH#nAn)GGGGGGGGGGGGGGGGGr[)]T{91}lJ))[:lowerprint:][:" + "xdigit:][:lowerprint:])]*", + 1, 0 }, + { "()[:space:]~!$[:alnum:]JJJJ[:ascii:]", 1, 0 }, + { "[^(\?<=)-]()k", 1, 0 }, + { "(()W){,8}ea", 2, 0 }, + { "({,-56}5G&&&&rrrrrrrrrrrrrrrrrrrrrrrrrrk.8) hWJ,TM)0Yd-", 1, + 0 }, + { "(Z-fddddddddddddddddddddddd)-{9}", 1, 0 }, + { "[^<[(\?!:asc(\?:i(\?<!i:])F])[:alp(ha:]b))-}Wwx8B", 0, 0 }, + { "[^[^[^([(\?{}(\?=)(\?())-CCCCCCCCCCC(\?=CCCCCCCC(CCCCC(\?:" + "CCCCCCCC(\?{l[(\?!:space:]})[:upperwor(\?:d:]{-27}[:al[^pha:" + "][:xdigit:]^f", + 0, 0 }, + { "[[^]G@>2!+[:punct:(\?<!]{,189}6ZF[:blankcntrl:][:digit:]{," + "214}){-115,-14}l[:upperword:]{101,}Z[:ascii:]Ld&02|c]<0~<bc", + 0, 0 }, + { "(Q)[:digit:]x", 1, 0 }, + { "hT[[:alnum:]\?]O[OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOxFF%^(\?(_" + "LN " + "8uXQT\"*/" + "L)+l)>qQ[^]e[:ascii:]PP()[:digit:]NQ8%6d=&2I{-62,-142}w]].e{" + "}*", + 1, 0 }, + { "{,-219}xxxtEEEEEEEEEEEEEEEE[:pun(\?(ct:])qqq)" + "nnnnnnnnnnnnnnnnnnnnnnnnnnn", + 0, 0 }, + { "[:di(\?>git:])W4", 0, 0 }, + { "([^y])Fkvto$", 1, 0 }, + { "[^($$$$$$(\?!$$$$$(\?{$$$$$$(\?<=$$$$$$$$$$$+===)[:alnum:]" + "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM)Z]{}^[:blankcntrl:]--" + "xxxxxxxxxxxxxxx[^xxxxxxx)\?tVG\?{232,81}{121,}xn{,-226}})" + "tttttttttttttttttttttttmu(\?<!&&&&&&&&&&&&&&&&&&&&&&0b]z)$" + "87{,-192}{}{-242,}", + 0, 0 }, + { "l[:dig(\?(it:]|s*)aA[:digit(\?<=:].^.))x[:digit:]", 0, 0 }, + { "[:grap[^(\?#h:]').]Z", 0, 0 }, + { "[:gra[^ph:]t[:digit:]222222222222(22222222222222222H " + "qM]pWZr[:ascii:]-hRb_.)Q{-228,-204}{}", + 1, 0 }, + { "AAAAAAAAAAAAAAA(AA)YeX", 1, 0 }, + { "(!dqqqF*^){(,-79}s!!!!!!!!!!!!)", 2, 0 }, + { "[^(\?msxm(\?#sx:]|)ZHYup)j{95}0L:vXB#')d'DX\?m." + "T034\\\\\\\\\\\\\\\\\\\\\\y5rV{}S", + 0, 0 }, + { "(W*O+yl([\?!P(\?:)I]${}{-195,-14}[:upperword:]{}[:xdi[^git:]" + "[:space:]X[:grap[^h:]~]zzzzzzzzzzzzzzzzzzzzzzzL)+)Y " + "b.-=jf{-216,}${/!}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}|]", + 2, 0 }, + { "[^\\\\\\\\\\(\?<=\\\\\\\\\\\\\\\\m]{-48,234}[:alpha:]s)", 0, + 0 }, + { "[(\?{U}(\?<!)])LLLLLLLLLLLLLLsssssssssssssssssssssssssss[:" + "ascii:][:blankcntrl:]---------b", + 0, 0 }, + { "[^[^[(\?#)(\?imsxims[x:)<<<<[<<<<<<<<<<(\?<!<<<<<<<([^\?(<<<" + "<<<<<<<z(\?(zu(\?<=~83}aZpIE)[:alnum:](\?imsximsx:(\?!jrE6(" + "\?<!\?V(SzDU)000[000000000((\?=\?)=0])L|lOYuWXk", + 0, 0 }, + { "$o[:dig(it:]nnnnnnnnnnnnnnn{-94}|G)[:alpha(\?!:] " + "{,-108}D=\?>[:digit:]S[:space:]t", + 0, 0 }, + { "()n", 1, 0 }, + { "[:upp(erword:]$)<}.vZM<lEY5Y*", 0, 0 }, + { "[^([^\?>)rCD&{5(\?msxisx:7,}qqqqqqqqqqqqqqqqqq{31,}@w#W:(@(" + "\?:zp$YYYYA[:alpha:]{1}A)*dZJ\"5OG|\?(\?#a])]|){-150}[:" + "xdigit:]", + 0, 0 }, + { "[($)gwo{`\"]{-160,}" + "\\\\\\\\\\\\\\\\\\\\\\\\\\66666666666666888888888888", + -1, 1 }, + { "((}DA+Rc000000000000000000)%vvvvvvvvvvvvvvvvvvvvv%C&emZ*[:" + "alnum:]#m/" + "D[:graph:][:blank[^cntrl:]E{,168})" + "kkkkkkkkkk000000000000000]", + 2, 0 }, + { "[^[u*(\?#x01234)oxGGGGG(\?([GGGG)GGGGGGGGG]^U)!!CCCCBM`4QB^" + "XEN]{,-60}[:upperword:]G]", + 0, 0 }, + { "(%)~t{S,K^MI3PMo)=b", 1, 0 }, + { "[[[^]{}eU([:xdigit:]&&&&&&&&&&&&&&&&&)\"W|43[:alpha:][:" + "graph:]J8b[:blankcntrl:]gggggQ{,183}{,-254}\?[:ascii(:]{," + "134}", + 1, 0 }, + { "[[([^[^([^(\?=)1RRRRRRRRRRRRRRRRRRRRRR(\?:(\?(\?(\?!=#RRRRR(" + "\?=RRRR(\?<[^!Ru)])]o[:[graph:[^]{,7})[:digit(\?::]{-215,}e[" + ":space:]]", + 0, 0 }, + { "({{{{{{{{{{{{{{{{{{KKKKKKKKKKKKKKKKKKKKKKKKKKKKBBBBBBBBBBBB)" + "[:space:]0[:alnum:]HcctQA", + 1, 0 }, + { "[^(pP7(HsN[^g{186,-87}\?\?]EQ%u:-Y)+>>>>>>>>>>>>>>>>>>>>>pP]" + "[:alpha:]", + 0, 0 }, + { "[(.{141}h|)((\?:\?=@Q} " + "ghcC{+*(R)D+][:lo(\?#werprint:]" + "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzz))", + 0, 0 }, + { "[^({}S)PPFl(])-216", 0, 0 }, + { "[([[^(((([(\?#^[^[^\?4[(:[dig[^it(\?(:]{122,})y\?", 0, 0 }, + { "[[2${188}u{1(4(\?(1,1(\?{98}e{&tbaoI]q)[:punct:])d}))" + "Nqffffffffffffffffffffffffffff[:ascii:]+]", + 0, 0 }, + { "()K-", 1, 0 }, + { "[[{2(2((\?(\?!()2})])[:alpha:]fVVVVVVVVV{-47}):::::::::::)" + "\?vwyyyyyyyyyyyyyyyyyyyyyyyyy-]{}", + 0, 0 }, + { "ivcs)g", 0, 0 }, + { "(hhhh[^hhhh(\?{h\?]})%%%%%%%%%%%%%%%)\"+38mbY:s9{/d# " + "zaNnbQb)b:*zpKI{-26,-189}", + 1, 0 }, + { "S*(#)[:graph:]lllllllll&G)t", 1, 0 }, + { "([^[(([\?=\?<!)]]___{-63,})]nt", 1, 0 }, + { "[:b(lankcntrl:][:alpha:]*[:pu[^[nct:][:alpha:]A]$" + "aaaaaaaaaaaa*)A[:digit:]U][:alnum:]", + 0, 0 }, + { "[^f[^p000{68(\?isxmx:,}(\?!vvvvvv)$)]PP#*{(})[:punct:]&&&&&&" + "&&&&&&&[:punct:]\?][:blankcntrl:]", + 1, 0 }, + { "[^(((\?(\?(()))GGGGGGGGG{(\?!($)))((\?!)V^{228,145}))]{-229}" + "Qjjjjj[:punct:]R)", + 0, 0 }, + { "[(Q[^((\?{(\?:]~z)})gE(.<){}|)Kuuuuu$*" + "222222222222222222222D]", + -1, 0 }, + { "([^`(\?<=`````[^`````````M]\?)=L74A[:upperword:]]P", 1, 0 }, + { "(({}[:space:]qv-T){,-192}{-45}{65}9\?X).d", 2, 0 }, + { "_[(:upperword:]mU(P}qX>\?%)$Lwq[:alpha:]{-115,}=============" + "==================={127,}", + 1, 0 }, + { "e)", 0, 0 }, + { "[{,2[5}Klen+D0'YX(\?<=|_H]I,Y\"*/<3sM[:digit:]])#.", 0, 0 }, + { "[:(xdigit:]){[:digit(\?mxmsx::][:as(\?<=cii:]d!{135})#)pP[:" + "space:]Syyyyyyyyyyyyyyyyyyyy\"Gg8", + 0, 0 }, + { "[(\?()])", 0, 0 }, + { "[^([^[^[[^[:alpha:]SIus[^f<f]}}}}}}}}}}][:xdigit(\?=:]Z{-13}" + "*]_[]LLLL)]E[:alnum:]b$)]]]]]]]]]]]]]]]]]]]]]]]]][:" + "lowerprint:][:ascii:]{,40}{86,}" + "333333333999999999999999999999999999*" + "fffffffffffffffffffffffff99999999U9|[:digit:][:upperword:]" + "oowwwwwwww[wwwwwwwwww{195}[:xdigit:]]H{-73,153}R+zAz{}r/////" + "////////" + "{232,}kAoffffffffff[:blankcntrl:]xxxxxxxxxxxxxxx]KKKKKl0,[:" + "alpha:]|{,-165}Qc{96}CCCCCCCCCCCCCCCCCCCC/", + 0, 0 }, + { "{}:V(7O-)[:ascii:][:graph:]PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP#", + 1, 0 }, + { "[^(\?<[^=CC(CC$)]* c)BBBBBBBBBBBBBBBBBBBBBBB]z{-18,}", + 0, 0 }, + { "[[qqqqqqqqqqq(\?(qq235|ttttttttttttttttttttttttttttt[[ttt<<<" + "<(\?{<<<<<<<<<<<<)<<<<<<<<p)/" + "S9(\?{OOOOOOO(\?<!OOOk)})]nIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIb]" + "Z})", + 0, 0 }, + { "[^[^(\?>][^((\?<!C(\?!+(\?=)]^8)6nx).)){,-13}[:blankcntrl:]" + "\"(L{}){,29}nnnnn{-83}]l[:upperword:])", + 1, 0 }, + { "[(ZZ\"#(\?#Nb(\?<!:U)oRRRR])Zei${Ec/)s", 0, 0 }, + { "[^[^[(\?(t(\?:3```````)`````)|#CB)//////////////////////////" + "///" + "*!liB#|CCCCCCCCCCCCCC(\?=CCCCCCa7N]weTTTTTTTTTTTTTTTT1{}o\?{" + "}BBBBBBBBBBBBBBBBBBBBBBBB.])u{-218,126}.,[:space:]]", + 0, 0 }, + { "[[([:alnum:])yyy(\?!yyyyyyyyyy(\?!yyyyyyyyyyyyyyyyyyy[:" + "graph:]I])Uw*X.^[:ascii:]{,-63}[:digit:]{-88})&&&&&&&&&&&&&&" + "]*", + 0, 0 }, + { "[[[^K(\?=KKKKKKKKKKKK(\?:KKKKKKKKK[KKKKKK]]U[:digit:])]dd)({" + ",16})xy+Pu)JJJJJJJJJJJJJJJ[:space:][:ascii:][:upperword:]ql_" + "jywmt4B+]{-30,}^555555555Xza[:punct:]", + 1, 0 }, + { "[[^^XXX(\?:XXX((XXXXXXXXXXXXXXXXXXXX)v)$N9$" + "r\"\"\"\"\"\"\"\"\"\"\"\"\"].{,239}$[:punct:]\"9999][:alpha:" + "]{}c){,55}s[:upperword:][:xdigit:]310", + 0, 0 }, + { "[@([^I8oNl)]-{-203,-224}{-78,}KKKKKKKKc{-66}[:xdi(\?=git:]==" + "========){}f{-124,}[:upperword:][:lowerprint:]]{}--------l+", + 0, 0 }, + { "[^]ozp+0(\?#\"[(\?()X]))[:blankcntrl:][^e{99,222}" + "JJJJJJJJJJJJJJJ3F]\?[:blankcntrl:]l$ot", + 0, 0 }, + { "[[^[[((\?isximx:)2222222222(\?=22222[:graph:])+U)((\?{\?<=(" + "\?()iYv8qc@#y)G])+}))FvnP\"7OZ-b273[:ascii:]Ak6*`S[:digit:][" + ":graph:]]{2}^G{79,}DDDDDbbbbbbbbbbbbbbbbbbbbbbbb(bbbbbbb)|" + "tP48y{wNJ_S hJbY]]dc", + 1, 0 }, + { "[:alph(\?{a:]p1[:lowerprint:]}){163,}", 0, 0 }, + { "W()", 1, 0 }, + { "()``````````````````````````[:ascii:][:alnum:]{,26}[:graph:" + "]", + 1, 0 }, + { "[:al(\?<!num:]|byyy,*)U5%u${190}-{-221,-33}" + "k7777777777777777777777777777777+eXXXXXXXXXXXXXXXXX[X(\?(XX)" + "XX)S'vEAa]*e", + -1, 0 }, + { "[^(([R_AC[lE'{2(\?{28(]8LTt[]b[:punct:]]O)|2[:graph:][:" + "space:]}) " + "x3C[:alpha:])uI+dddddddddddddddddddddddd{-165,}" + "FFFFFFFFFFFFFFFFFFFFFFF)cccc*[:upperword:]]G{,-38}{24,}" + "555555555555555555555555555VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVZ[" + ":blankcntrl:][:ascii:]", + 0, 0 }, + { "[^QQQQQQQ(\?#QQ(QQQQQQ[:punct:][:space:]){(\?(\?:!}[:graph:]" + "t}}[^}}(}}}}}444444[^444444444444444444444]\?]G)E)L{,-103}{" + "84,}r$ii]-[:alp(\?<=ha:]S5G~9>n*)P<" + "3tttttttttttttttttttttttttt)n{}[:graph:]" + "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeee{,83}[:digit:])" + "0BBBBBBBBBBBBBBBBBBBBBBBBBBBBBB[:alpha:]{-155,}{151,}", + 0, 0 }, + { "Ue{,254}+f[:lowerp(\?<=rint:]U.fff)", 0, 0 }, + { "QQQQQQQQQQQQQQQQQQQQQQQQAY<J)'MPi_u%#2doopqU7/" + "{103}[:graph:]e!7{GOr", + 0, 0 }, + { "[^({,[^233}[^d)BBBBBBBBBBBBBBB=======(\?>===========[^=S|[^[" + ":alpha:]G/]qqqqqqqqq{}[:xdigit:])..k", + 0, 0 }, + { "[([^[[:space:]ffffff(\?=ff]M]))[:xdigit:]UbCI,CzalLU*y5I[:" + "digit:]r{-30,180}{-209,-45}Paf]", + 0, 0 }, + { "[^[h(\?{hhhhhhhhhhhhhhhhhhhhh})]{,143}[:lowerprint:][:ascii:" + "((\?(\?=])[:asc)ii:])zp]", + 0, 0 }, + { "[[(\?{]})]", 0, 0 }, + { "[[1\"3m^,(\?<!2((\?!\?#t```````````````````````````)\?)|c^)" + "A^~]{61}W\\\\\\vvvvrrrrrrrrrrr[:digit(\?#:])]F[:upperword:]" + "dX\\\\", + 0, 0 }, + { "([${144,}(\?<!)-RAk_F(\?imsxisx:=9]z/))", 1, 0 }, + { "[[^[[[^([[^[^[^([[^([[Uiiiii#####(\?(\?{(\?<!#########(\?=##" + "###).^)(.|>2m[M/" + "2222222222222222222222222222(\?:22222222222(\?#22(\?:(\?=" + "22222{,243}]x68+I/" + "K)11111111111]\\pP[:graph:]$[:space:]^{}A)[:xdigit:]-={>", + 0, 0 }, + { "[(\?>[(^()Vty2vvvvvvvvvvvvvvvvz^])ZZZZZZZZZZZZZZZZZZZ-------" + "---------5\\dVLSp8UE2m+z3X/Sd", + 0, 0 }, + { "[}}}}}}}}}}}}}}}}}}}(\?#}}(\?<=)|*C " + "]*29JW7O9mEB]pE_OoxN)[:alpha:]", + 0, 0 }, + { "([^((\?<=\?)D{,200}.[(\?#:ascii:])[:space:].)[:alpha:]D|[:" + "graph:]{,-41}*LLUUUUUUUUUUUUU{-189,-131}]qHR<k2@P{27}<^e,ub%" + "\?/4){-243}+[:digit:]%*x9lA^", + 1, 0 }, + { "([:alpha:]bT&+_)$Z{,212}x26`", 1, 0 }, + { "[^([^(A{[^}g(\?()A9p#54b]-------------------------------)." + "wzD#=f\\)A)8a]]DNNNNNNNNNNNNNNNNNNNNNNNNNN", + 0, 0 }, + { "(W000000000000000000000000000000)", 1, 0 }, + { "www(wwwwwwwwwwwww)", 1, 0 }, + { "()555555555555{18}i+[:alnum:]E {}U", 1, 0 }, + { "SqbHoooooooooooo[^oooooo([^ooooooo])\\N[:xdigit:]]oooo`", 0, + 0 }, + { "[999999999999999999uE{193,0}lx{7917}[:punct:]4&d]{221,}[:" + "digit:]{49,156}[:lowe(\?<=rprint:])[:space:]{-33}w+", + 0, 0 }, + { "[^(\?{})<{220,-193}[(\?=:xdigit:]UUUUUUUUUUUUUUUUUUU'{-18}]" + ")", + 0, 0 }, + { "b[(\?<=:upperw(\?{ord:][:digit:]})EEEEEEEEEEEEEEEEEEEEE/////" + "/////////////){177}C", + 0, 0 }, + { "(^).[:alnum:][^[(\?=[(\?{[})DA5{)[[I~y&O\?9>])]][:" + "blankcntrl:]M[:alpha:]x9[:upperword:]|[:xdigit:]b", + 1, 0 }, + { "()[:digit:][^[U}-]]{,206}V*WJ@R]\?", 1, 0 }, + { "[^](\?#{}(\?[<=)yv)]r", 0, 0 }, + { "({,-192}//////////////////////7!eW_0eoL){}", 1, 0 }, + { "^[:punct:(]+)IIIIIIIII[:punct:]P$pP", 0, 0 }, + { "[(\?=|U)^-]{-52,-72}[:digit:]*6666666666\?{{{", 0, 0 }, + { "([^f(\?:+{1((\?=34,}]))^)s0bux7\?5`Bwr[:upperword:])Dy+", 1, + 0 }, + { "AL{}:::::::::::::::::::::::::::::::{,(104}~@,Ysey@h).", 1, + 0 }, + { "[^((.)))(\?()))))))))))))))))))))(\?msxims:))))))))))[)][:" + "upperword:][:alpha:])", + 0, 0 }, + { "[^(()f])G^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^T{}N*nK[G]{,61}^^^^^" + "^^^]", + 0, 0 }, + { "[(N::(\?<=[:digit:][:graph:][:space:]xB5[(:xdigit:]|Yv{}" + "HHHHHHHHHHHHHHHHHHHHHHHHd).[:g(\?<=raph:])[:digit:]<<)[:" + "digit:])[:space:]Q[:punct:]x7C]", + 0, 0 }, + { "[^((\?(\?(())a)(\?!){})W)pP3333333333(" + "33333333333333333333hhh]{})", + 0, 0 }, + { "[^ [ " + "a*FFFFF[^FFFFFFFFFFF(\?<[^!FFFF(\?=FF])])L1]{,-52}{B-bxsPKg{" + ",8}[:digit:][:punct:][:upperword:]DD${,-131}", + 0, 0 }, + { "($$$$$$$$$$$$$$$$$$$$$$$$$$$$$^pP),,,,,,,,,,,,,(,,,,,,,,,,,," + ")QQQQQQQQQQQQQQQQQQQQQQQQ", + 2, 0 }, + { "[:lowerprint:]|l{(,-54}C{}*-)IIIIIIIIIIIIIIIII", 1, 0 }, + { "()+", 1, 0 }, + { "[(([(\?{[:punct:]]|))[[[[[[[[[[})]WWWWWWWW&$$$$$$$[:graph:]", + 0, 0 }, + { "[^(\?{}){(107[(^,}][:space:[]))^w,&aPPPPPP[^PPPPP{117,-213}" + "s\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?]]]222222[:d(\?(" + "igit:]NNNNNN)NNNNNNNNNNNNN8)I", + 0, 0 }, + { "[^(\?<!$)|TTTTTTTTTTTTTTTTTTTTTT(TTTT]a8)2<", 0, 0 }, + { "([^[]%[^[^]-][:alpha:]37*:[:space:]]lQvu)[:xdigit:][:" + "blankcntrl:]", + 1, 0 }, + { "[[Bl_>9C^:\?X_KK]2sw@hHZT!],uuuuuuut|lFW()''''''''''''''''''" + "'''[:graph:]<~v{-251}0[:digit:]C[{222,}]{,41}{}*g^UuS/" + "{-114}", + 1, 0 }, + { "(D{,-79}[:gra(ph:(\?(]C[:ascii:]))I[tC.%tkllll[^" + "llllllllllllllll]&&&&&&)&&&&&&&&&&&&&&&&&&&&&&)]10435", + 1, 0 }, + { "[:al(\?{[^num:]]})}x'[:(\?#xdigit:])" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxKKKKKKKKKKKKKKKKKKKKKKKKKKKKTT" + "Tr*%{~f", + 0, 0 }, + { "[ZQKEEEEEEEEEEEEEEEEE(\?<!]3|.~~~~~~~~~~~~~~303)" + "33333333333333333", + 0, 0 }, + { "(-62([:ascii:]5555){-230,}<<<<<<SM[:punct:]{72}|E{160,})" + "Pfqba!{,-188}DS{ +2tRu\"0JG$", + 2, 0 }, + { "([^(\?:(Ea00000000000000[:punct:][:graph:]{}]))[:xdigit:]{-" + "65}t){164,}", + 1, 0 }, + { "[\?$$$$$$$$$$$$$$$$$$$$$$$$$F......(\?(.).q#R:j6%TTLCdtuM|8*" + "54<GHoqEh9FBW0:W]L0)o][:upperword:]", + 0, 0 }, + { "[(\?>[:alnum:]W[:space:]]D)|L", 0, 0 }, + { "(M(MM)[:alnum:]|[:lowerprint:]4)", 2, 0 }, + { "[[^(\?:{}{2[2(\?>0,})]]]Etu)-)", 0, 0 }, + { "([^[^^z[:graph:]]#{-144,96}[:punct:]!4LY//////////////////" + "SSSSSSSSSSSSSSSSSSSSSSSSS[[^:xdigit:]\?`-!L#p0{52}]%{-121,}[" + ":graph:]]WqJ>$6UBg{,7}[:blankcntrl:])[:upperword:]y2wW!A[:" + "blankcntrl:]0CN\?", + 1, 0 }, + { "[[^(\?:|+bII(IIIIIII(\?(\?>!)275SIIIIIIIIII(IIIIIII(\?=" + "IIIIII[:graph:]|)`]S\?.}A)[:alnum:]Jgggggggggg{-150,}{-89,})" + "[:alpha:]Q)|07be5:j)]", + 0, 0 }, + { "([(\?i(ms(\?=x-x(\?>:))C)]){})>eIqm~lFb[:upperword:][:" + "blankcntrl:]w=[:digit:][:graph:]", + 1, 0 }, + { "([HHHHHHHHHHHHHHHHHHHHHHHHHH[^HHH(" + "\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?!!!!!!!!!!!!!!!!!!" + "!!{23}]~J=[:ascii:]tttttttttttttttt])-216", + 1, 0 }, + { "B{[^-32,246}{13(\?!0}q>GVQw*[:digit:][:punct:]." + "77777777777777777777`T(-t01odD]\?${}{-247}+gV{131})+[:" + "lowerprint:]m/z~d", + 0, 0 }, + { "[t[$FV+(\?=E=[^])]-$U{-22[5,}{253,}08g]$[{}][:xdigit:][:" + "punct:]{-18}{-173,}]{,-191}V_|90", + 0, 0 }, + { "()$", 1, 0 }, + { "[^[^((((((((((((((W[(\?::blankcntrl:]&-JH]J){93}LLLLLLL|r{," + "221}tY/172]-AS", + 0, 0 }, + { "[^()(\?{qqqq(\?msimsx:qqqqqqqqqq3999999999999GGGGG|S*W%{," + "128}][:xdigit:]AJt]}\"Zf!lRpr{>){,36}})", + 0, 0 }, + { "[([]^]^)", 0, 0 }, + { "([.(\?#){}[:alpha:]\?S{2}P%Gw]" + "nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnYiq5)>i*r<", + 1, 0 }, + { "[ggggggggggg$PPP:S " + "(:]N{239,}|A[:lowerprint:]vvvvvvvvvv[:lower(print:]{-184}({-" + "133,}+)[:punct:]P/Q.OOOOOOOOOOOOOOOOOOOOOOOOOOOOOO", + 1, 0 }, + { "(RRRRRRR[^RRRRR[RRRRRRRRR])]", 1, 0 }, + { "[(\?:^])D%", 0, 0 }, + { "()[]#C[+[j]{,29}-]", 1, 0 }, + { "(([(\?(((\?{\?!(\?=\?=#[Es*){02$r'}(\?:3pz)" + "uPPPPPPPPPPPPPPPP(\?(\?>:PPPP][:graph:][:ascii:]`.)[:punct:]" + "[:a(\?mxi:lnum:])r)$)[:xdigit:]$[:(\?=digit:])aa[^]a)\?])" + "sQQQQQQQQQQQQQQQQQQQQQQQQQQ^|$)-}))", + 2, 0 }, + { "z@@@@@@@y${}[:(\?:upperword:]l\?{,144}-)", 0, 0 }, + { "[:aln(\?:(\?>um:(\?imximsx:]){})FGGGGGGGGGG|-p){,105}", 0, + 0 }, + { "[[{17}llllllllllllllll(\?:lllllllll{,(\?#-94}OUUUUUUU(\?#" + "UUUUUUUUUUUUUAA]p[:digit:]{-1(57,}5yyyyyyyyyyyyyyyyyyyyy[:" + "alnum:]v{-185}^^^^^^^^^^^^^)d[[[p)]))", + 1, 0 }, + { "()|[:digit:].E2o", 1, 0 }, + { "()3[:lowerprint:]", 1, 0 }, + { "[(\?{(\?#(\?>SN}[^)z+r^t[:digit:]seP[:alnum:]$b1ZY[U(\?<!" + "U4IIIIIIIIIIIII(\?<=IIIIIIIIII]m)]))]4)", + 0, 0 }, + { "{,74} qkk[^p]kbi6>{}000000000000000000000000000000$|)", + 0, 0 }, + { "[:(\?=digit:])v{164}", 0, 0 }, + { "[:graph:]h[:upper(\?(wo(\?{rd:)])00000[^000000000000})." + "4OEVf{,-46}]A", + 0, 0 }, + { "[](((((((((((((((N{{{{{{{{{{{{{{{{,-1}e]a{-166,-44}", 0, 0 }, + { "([[^[^[(^[]]YYYYYYYYYYY]D.cQ{}[:alpha:]ttttttt000000[^0000(" + "\?<!0000000000000000N::::::::].][:alpha:]#5\?{}{-253,-193}]" + "\\[:ascii:]tS{,35}B)ffffffffffffffffffffffff))/", + 1, 0 }, + { "(G)[:alpha:(\?#])W{-197,-220}w8", 1, 0 }, + { "{-2[^00,(\?#-([84}ig+)]]l[:graph:][:graph:][:space:])" + "aaaaaaaaaaaaaaaaaaa{-208,}ea{,224}", + 0, 0 }, + { "[^[W(\?<=[B[:xdigit:]{255,}FAAAAAAAAAAAAAAAAAAAPP])[:xdigit:" + "]+][:lowerprint:]${-195}", + 0, 0 }, + { "[v{104,}BB].HHHHHHHHHHHH[:ascii:]" + "bbbbbbbbbbbbbbbbbbbbbbbbbbbb(btttttttttttttttttttttttttt){" + "180}", + 1, 0 }, + { "[^(i[^iiiiiiiiiiiiiiiiii(ii)n])#######################]", 0, + 0 }, + { "(([:space:])[:g(\?>raph:])[:punct:][:upperword:]LV\"t+t!)[:" + "ascii:][:lowerprint:]q", + 2, 0 }, + { "[[[^([7(\?[<!)\\PP~D7L (\?imsimsx:(\?= " + "$GS26L3-J(\?()!)]]{-178}%$[:p(\?!unct:]))yyyyyyyyyyyyyy@w,[" + "11!R86:)G*[(\?(:blankcntrl:]267$~L\?{-108}k[:alnum:]So\?Y/" + "eq]-|[:xdigit:]555555555555555555555555555)55555........W*O)" + ")][:alnum:]]I{,-126}[:lowerprint:]8\?[:xdigit:]u%wHc6\?:Pc.." + ".........................,,,,,,,,,,,,,,,,,,,,,,,,,,,]", + 0, 0 }, + { "((3pPp))QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ", 2, 0 }, + { "[[^]{-244[}(\?([^|W0E4]UUUUUUUUUUUUUU[:upper)word:][:space:]" + "{-57,})+L>R]]$PeFuufcBA`qr!!!!!!!!!!!!!!!!!!!!!!!!!", + 0, 0 }, + { "[[(\?#F^(\?<!)|)fff(\?!fffffffffffffffff(\?{ffffff(\?:" + "ffffff[:alnum:])]]c.\?-}))", + 0, 0 }, + { "[^[^((\?:)ww[wwww(\?>wwwww)3z/57z){34}]/(/////////////[^////" + "//////////////)]E%)L{-133}]*$]", + 1, 0 }, + { "(!)GS[:ascii:][:punct:]{235}T'&-_h\"", 1, 0 }, + { "(){}", 1, 0 }, + { "[[^((\?!(\?<=)*QF[:alpha:])([^[^\?<!x60t(\?<!" + "UUUUUUUUUUUUUUUUUUUU)K&d{118}z7nM.G)````````````````````````" + "```E:(\?(){31,}){}]k]){,109}[:space:]]ZZ[:xdigit:]]{-68,}`{}" + "{}e\?[:alnum:]", + 0, 0 }, + { "[^{223}.^,-qqqqqqqqq((\?!\?>qqqqqqqqqqqqqqqqqqqqqqqP6W0_'O)" + "Bur*'6&*t)]{65})+", + 0, 0 }, + { "([(\?=)]wr$7f5ru){100,}[:xdigit:]y{}[:digit:]{}2n@P|9#mru~" + "97{-189,73}$a", + 1, 0 }, + { "({-113,213}){-172,221}B[:ascii:]{,-48}", 1, 0 }, + { "[^[[Xf`````((\?{(\?<=\?imsmsx:`````````(\?!`````````[```(" + "\?mximsx:``(\?(&|o{xIaO][:)space:]3))\?])+)*<|@@@@@@@@@@@@@@" + "@@@@@@@@){-251,}{}]*[:graph:]1!azE\?|-120u*][:lowerprint:]}" + ")", + 0, 0 }, + { "[[[^##(\?################(\?>(\?(##t)][:punct:])b))<<<<<<<<<" + "<<<<<<<<<<<<<<<<<[:alnum:]y " + ">u=l:rp8i3Ci#]46%NIO-W[:space:]IIIIIIIIIIIIIIIIII]W[:space:]" + "f]l{-253}", + 0, 0 }, + { "[:graph:]L{-136,175}{[^}h(\?=t)Q]ooooooooo(" + "ooooooooooooooooo_)[:space:]q\?", + 1, 0 }, + { "()$.", 1, 0 }, + { "[(\?<!^$.\?{197}B]$)", 0, 0 }, + { "[:di(git:])[:low(erprint:])qqqqqqqqqqqqqqqq[:digit:]", 0, + 0 }, + { "((zzzzzzzzzzzzAUUUU)l$]VD z~)n", 2, 0 }, + { "([^[(\?<=^[]{}][.WWWW)044444444444(\?=44(\?{444(\?{(" + "444444444444e{(\?=}}))..t]+[:(\?<!xdigit:]P]-N}))))|)", + 1, 0 }, + { "\\ce[:(\?#asc(\?{ii:])})[:upperword:]`^", 0, 0 }, + { "[:graph:(\?<=])[:alpha:]", 0, 0 }, + { "([:upp(\?=erword:])pC)lp\?", 1, 0 }, + { "(oooooooooooooo\?fN)-[:alpha:]{-213}[:alnum:]qHEu", 1, 0 }, + { "[:punct:]TTTTTTTTTTTTTTTTTTT[:d(\?#igit:])[:alpha:]", 0, 0 }, + { "([^[^[^J4(+++++++++++++++++++++SgDE(\?>\"y8].]::::::::::::::" + ":)pP5-]p)O{,199}xxxxxxxxxxxxxxxxxxxxxx[:ascii:]%", + 1, 0 }, + { "([:alpha:]Fs)Z", 1, 0 }, + { "[()]{209}[:alpha:]hhhhhhhhh(hhhhhhhhhhhhhhhhhhhhh)pP<<<<<<<<" + "<<<<<<<<<<<<<<<<<<<<<", + 1, 0 }, + { "-{-8,}.[:(\?imsxx:ascii(\?<!:]{-231}aa*{}K^UQL\?)d\?[:" + "lowerprint:]W)q>D9'", + 0, 0 }, + { "[#(\?msximsx:#########################-IIIIIIIIIIIIII(IIII(" + "\?#IIIII((\?#[^III{})N.[(\?=:lowerprint:]))CwT,,,,,,,,,,,,,," + ",,,,,,Sq]$CCCCCCCCCCCCCCCCCCCCCCCuuuuuuuu])))", + 0, 0 }, + { "[:xdigit:][(\?#]){13}{,75}lllllllll", 0, 0 }, + { "[c]QQQQQQQQ1+{-252[(\?#}33333])[:upperword:]", 0, 0 }, + { "P@i #>>PF!@8G<[(\?:^P]-)D", 0, 0 }, + { "uZZZZZZZZZZZZZZ[^ZZZZZl*-211{199}(\?!p])" + "EEEEEEEEEEEEEEEEEEEEEEEEEEED[:lowerp(\?msximsx:rint:])", + 0, 0 }, + { "[(\?!^])021[:graph:]'", 0, 0 }, + { "\\(\?>[(\?<=:ascii:]{}[:alpha:]d8}G))", 0, 0 }, + { "[^[((\?!1)[^,a|]\?{,242}[:alnum:])X\"a", 0, 0 }, + { "pP[((\?simx::a(\?!lnum:]vvvvvvvvvvvvvvvvvvvvvvvvv)|O0)[:" + "digit:]ooooooooooooooooooooo)" + "\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"", + 0, 0 }, + { "_ L:8J-~ Y$[:uppe(rword:]{,-184}]{}6.A)", 0, 0 }, + { "{,105}.(9]]{-12})N@0nOOE", 1, 0 }, + { "HHHHHHHHHH[:xdigit:]uuuuuuuuuuuu{}E^X\\\\\\12601", -1, 1 }, + { "( o)=\"OU7h{V>", 1, 0 }, + { "[[:xdigit:])))))$[:xdigit:]+{152}{,-50}(c),,,,,,!!!!!!!!!!!!" + "!!(\?>!!!!!!!!!!!!!.[:digit:]i>\"O'i9])-175d_", + 0, 0 }, + { "[([^[^[^([[Eeee[^eeeeeee(\?(\?<!(eeeeeeeeeeeeeeeeeef|]][:" + "alph()\?>(\?!(\?>a:]a{,166})/////////////////////" + "[:gr[^aph:])Gpu", + 0, 0 }, + { "(7)NNNNNNNNNNN132", 1, 0 }, + { "[([\?#^[]{QKm$v])][:alp[^ha:]]", 0, 0 }, + { "(:{86})7{K|[:alpha:]{O", 1, 0 }, + { "([Y(\?{[[^:alnum:][:alnum:][:digit:][:a(\?(lpha(\?(:].})", 1, + 0 }, + { "[[({29,-30}([[^:digit:])Y]]J=~{,220}[:blankcntrl:])" + "0ooooooooooooooooooooooooooooooo[:punct:]&]", + 0, 0 }, + { "[^1Dx32[:alnum:]]{[(\?::punct:]MMMMMMMMMM)12759", 0, 0 }, + { "([[[]]*|(_])[:u(\?{pperword:]})", 2, 0 }, + { "[:upper(\?(wo)rd:]){-16,250}", 0, 0 }, + { "([^{194}i(\?({161)}PP\\S{}{,-14}]))z{208,225}BpPEt", 1, 0 }, + { "[(\?m-ms:)}&!@29k0sUqzt9}<-x|A$!+G>>>>>>>>>>>>>>>>>>>>>>>>>>" + ">>>>>>CCCCCCCCCC-][[:space:]][:space:]El", + 0, 0 }, + { "()[:digit:(\?isx(\?>ix:]K^WQQQQQQQQQQQQQQs)[:lowerprint:])", + 1, 0 }, + { "[a|(\?imix:S(\?(SSSS)SS(\?>S)]W)8t[:ascii:]f$)[:alnum:]" + "111111111111111111111[^[:space:]x{12729}+'''''''''''''''']", + 0, 0 }, + { "[^(\?!(\?(\?#=)a)[:punct:]=2)(){}$$$$$$$$$(\?ims(\?#-isx:$$$" + "$$$$$$$$$$$$$(\?#$$s)x{294b}##############################" + "slllll)]){,209}333333333333333333G:v2/K", + 0, 0 }, + { "[^]ub(\?<=)vQ6(\?#Z\"3.)[:space:]u[[:digit:]]" + "7777777777777777U'{}sssssssssss", + 0, 0 }, + { "(([(])`[:ascii:]b)", 2, 0 }, + { "[[[^[^([^[^(\?=(\?imxisx:[[^w])", 0, 0 }, + { "pppp(pppppppppp-{-175}Nb>k&)sssss{-190,-54}", 1, 0 }, + { "()OJ@`'%[:(as(\?!cii(\?#:]))+pffffffffffffffffffffffffffff{," + "162}[:ascii:]5)s-[:graph:]", + 1, 0 }, + { "[(M{}Ux5{jaW/" + "{}[^u[:alpha:]s^{84,}PPb@Wt$(\?>nha<Yf41a)]{}[:lowerprint:])" + "*[:lowerprint:]][:upperword:]^1gS.^=pp{}" + "FFFFFFFFFFFFFFFFFFFFFFFFFFF33333333333{}", + 0, 0 }, + { ")\?L9~h4BQnNp F\\Q{}", 0, 0 }, + { "($)[:upperwor(\?:d:])N[:alnum:]" + "bcccccccc5555555555555555555555555.N[:blankcntrl:]", + 1, 0 }, + { "2222222222222222222ppppppppppppppppp[:lowerprint:]))[^B\\e{{" + "{{{f]6#+{,-104}{{{{{{{{{{{{{", + 0, 0 }, + { "<[(\?>:al[^pha:]])\"O\"vN", 0, 0 }, + { "[(\?>d8E@b.{(\?<=,-250}(\?=mx48[:punct:]^&)]nAeYY)W)-13272", + 0, 0 }, + { "22222222222222222222222222///////////////////" + "[:digi(\?#t:]eM)[:lowerprint:][:alpha:][:alpha:]EEEEEEEEEEE", + 0, 0 }, + { "[(\?={38,223})^\\\\\\\\\\\\\\\\L(\?:{,-50}3|)}r]aW\\x70U{-" + "110,}8LUf)w]4+oav", + 0, 0 }, + { "G[:upperword:]v[:lowerprint:]-tu)j8CK", 0, 0 }, + { "[([([^().(\?(\?><=c)'(\?<(='(\?<!''''''''(\?(\?<!!''''''''''" + "'(\?=''''''/" + "(|dHj(P>L\?q!G))|)(\?=n(\?(^tk)T-z$q!D|2<rc[^{,53})]jZy))))" + "6)[:bla)nkcntrl:])010])7pE`l[:space:]([:lowerprint:]" + "eXXXXXXXXXXXXXXXXXXXTTTrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr]+[:" + "alph(\?!a:]7)444444444444444444444444l{34,}]J{}" + "yyyyyyyyyyyyyyyyyyyyyyyyyyy)\?'z9~9s.mA", + 1, 0 }, + { "().", 1, 0 }, + { "{-205(,}[:al(ph(\?>[^a:]W,[4DLR[^^8THMtVv~KKw(\?>)pPF)].{-" + "245,}]))fffffffffd[:alpha:]zzzzzzzzzzzzzzzzzzzzzzzzzzzzz", + 1, 0 }, + { "[^[[^]{-[1(\?imximx:83,}{,182}][:graph:]]^])-bTO X0P", 0, + 0 }, + { "[11111111111(\?#11111111]U[:asc([\?!ii:]{,37}+{-89}){-170," + "218}{-21,})f[:xdigit:]]P.[:xdig(\?:it:]145)YYYYYY$S@:@@@@@@@" + "@@{-150,-109}", + 0, 0 }, + { "{-40}<o][^D[(:graph:]]d).Q", 0, 0 }, + { "()APPLn[:xdigit:]", 1, 0 }, + { "[([^\?+++++++++++ [ (\?> (\?( (\?{ " + "(\?!]E{-29})pP)})ZpP", + 0, 0 }, + { "(t|{}c[^z^\?(@YLD]bSSSSSSSSSSSSSSS)+{{{{{{{{{{{{{{{[:xdigit:" + "]n>1)WkF}7", + 1, 0 }, + { "W22[0Q[^d-d{}PPPPPPPPPPPPPPP<^FZ(\?<=\"[U]Yo}9H'cYy]S[:" + "alnum:]^8wTDH)^u", + 0, 0 }, + { "([^[(\?:(\?>((\?#$)(\?{^(\?>))///////////(\?>/" + "ggggggggggggggggg{1(\?!90,-13}\\D)Dyyyyyyyyyyyy(\?!y(\?<!" + "yyyyyyy)})]]$)[:xdigit:]|{}-)#a))nPpP[:lowerprint:]AA)V+q^[:" + "blankcntrl:]", + 1, 0 }, + { "([^(\?!]))D{,97}", 1, 0 }, + { "(c){,141}", 1, 0 }, + { "nn[:s(\?<=pace:])[:upperword:]ooooooooooooooooooo*^[:space:]" + "`{-188,129}mmmmmmmmmmmmm^.", + 0, 0 }, + { "[[G{(\?imsximsx:2(49}{,-46}r(\?(\?=#Gw]u))[:bl(\?>ankcntrl:]" + "))(^m+)zSiZ " + "F4[!]VV$E{-9,-100}''''('''''''''\?DEOOOOOOOOOOOO############" + "###[:space:])HHHH)[:digit:]'////////////", + 2, 0 }, + { "[^*}(\?>)(\?:7Q=#+]KKKKKKKKKKKKKKKKKKKKKKKKKKKG)]]]]]]]]]]]]" + "]]]]]]]]]]]]]][:alpha:]-{}", + 0, 0 }, + { "[n(\?<(\?#!nnnnnn55555{205,}!)[:alnum:]^]!!!!!!!!!!!!!!!!!!!" + "!!!![:punct:])[:x(\?(digit:]vr)|'n6W5 D&jk[:punct:]5)", + 0, 0 }, + { "[^P(P{(\?i(msxisx:235,}))***])[:alpha:]^", 0, 0 }, + { "[([t(\?<!(\?<!4])[:u(\?=pperword:]))-])}}}}}}}}}}}}}}}}}c{-" + "39,}[:digit:]$-", + 0, 0 }, + { "([^)]{241}[:xdigit:][:upp(\?=erwo(\?(rd:]-xF5b{})q[:ascii:])" + "T4U{185}9999999999)()X&Ny[:alpha:]@@@@@@@@@@@@@@@@@@@@@@@@@@" + "@@@@@@{69,}[:alnum:]x{d7f8}p-[:digit:]", + 2, 0 }, + { "(f)(${,111}{25,}!\\d{,94}[:blankcntrl:]@[:space:][:ascii:])-" + "237{,232}DQVVVVVVVVVVVVVV)-", + 2, 0 }, + { "PP[:g(\?!raph:]){}", 0, 0 }, + { "([[^-][^4[:digit:]NNNNNNNNNNN]TVU:])[:ascii:]", 1, 0 }, + { "(([^(\?[[^<=)][:graph:]+iiiiiiiiiiiiiiiiiiiiiiiiii0INFX[:" + "xdigi(\?(t:][:blankcntrl:]][:graph:]qM6A[:alpha:][:graph:])" + "1*]eFvvvvvvvvvv)v-)U))t{89}", + 2, 0 }, + { "[^ZZZZZZZZZZZZZZZiiiiiiiiiiiii(iiiiiiiiiiiiiii{}))))))))))))" + "))))))]))))))))))))))))))))))))[:digit:]-", + 0, 0 }, + { "ddddddddd+zzzzzzzzzzzz[:graph(:])ssssssM{-223}[:graph:]", 0, + 0 }, + { "[:alph(\?>a:])x11{-144,45}.", 0, 0 }, + { "[]{#y.^(\?{{}&&&&(\?:[^&&&&&&&&)[:punct:]n{190}OylBQ{(\?!-" + "73})2u',x(\?#Ds(\?#{})j(\?{-})})u0(((((((\?{(((([:alnum:])" + "MC})b=71TncyE>[:xdigit:]*\\f]{}]\"p#!8twZT\")[:punct:][:" + "space:]", + 0, 0 }, + { "[^(Z6]8)|'@p8{}[:upperword:]MMMMMMMMMMMMMMMMMMMMMMMMMMMM{}" + "7c", + 0, 0 }, + { "$0)@#vp,VcJ.Bdh", 0, 0 }, + { "[[^(-])nnnn+s`[:alpha:][:blankcnt[^rl:][:upperword:]{-15,}][" + ":g(raph:]c]){,-177}6[:upperword:]##################{,-14}", + 0, 0 }, + { "[[(5C{86(,}PPrrrrrrrrrrrrrrrrrrrrr{150,182})N{}LSC|)-[:" + "alnum:]{}KKKKKKKKKKKKKKKK<4=~7K3PPPPPPPPPPPPPPPPPPPPPPP[:" + "lowerprint:]]]", + -1, 0 }, + { "([^(x{145b[5}^hfc.0)+]z@_&lA{-34,}])X\?", 1, 0 }, + { "([(\?<=)(\?!])l)L", 1, 0 }, + { "({-104,}DrPPDF4444444444444[:space:])[:space:]", 1, 0 }, + { "())))", 1, 0 }, + { "[[^((\?>\?(\?[{})q5v}r7t(P)xtffffffffffff))]{,-66}kdExX&-" + "SCeCzzzzzzzzzEc)E,\"^I]x{e629}|{}]", + 0, 0 }, + { "[h[:punct:]p\\[\\\\(\?:\\\\[^\\\\)Eo#:C$u[^T/" + "ysA[*%nM:f]{,221}[:lowerprin[^t:]{]bx{f285}E]E[:alnum:]+]" + "1oe3B][:alp(ha:]]fh7}M$l)D{17}", + 0, 0 }, + { "IIIIIIII[^IIIIIIX]-_S[:digit(\?#:])" + "33333333333333333333333333[:punct:]iiiiiiiiiiiiiiiiii", + 0, 0 }, + { "[^[[:punct:](\?((\?:^ " + "#Q_po(\?=[:alpha:]{}z()(\?!======'wq$Q2)LLLLLLLLLLLLLLLe(" + "C9gggggggggggggggggg[(\?<=:alnum:]()\?<!{-85,}W[[[[[[[[[[[[[" + "[[[(\?{[[[[[[^)(]\?])|uuu[uuuuuuuuuuuuuuuuuu{,-20}p${}]MHI&" + "7s:\?$[:digit:]-:)_V`*{-52,}{250}$:ME9izF/" + "uP[:blankcntrl:]})''''''''''''''''''''''''''''')" + "CCCCCCCCCCCCCCCCCCCCCCCCdd[:ascii:][:lowerprint:]." + "Mcccccccccc2B{-230,}$[:digit:]", + 1, 0 }, + { "()|mOAuK~P144[:space:]^9dddddddddddddddddddddddddddddd[:" + "blankcntrl:]", + 1, 0 }, + { "[^[^[^.L[^-vEUl(\?>(\?=a!Ib1P]])])~~~~~~~]xE9", 0, 0 }, + { "X()", 1, 0 }, + { "[^()(\?#G(\?<!)(\?=^r])*,XXXXXXXXXXXXXXXXX@)444444444", 0, + 0 }, + { "([[((\?<=({,-70})-[:xd(\?=igit:]{,138})", -1, 0 }, + { "[(^]{62,67})", 0, 0 }, + { "([((])[:space:]))", 1, 0 }, + { "(a{(109,})[:alpha:]{,-121}{})]RRRRRRRRRRRRRRRRRRRRRRRR{}{" + "125,}ttttttttt{46,}`[:space:]", + 2, 0 }, + { "[^[^([q[8]~.IPmiBSspP)]QpX[pT==8@lulANS]]{,-98}]", 0, 0 }, + { "[^77777777777777777777777(\?>777777])", 0, 0 }, + { "(),e<^X~{[:alpha:]{}G{70}", 1, 0 }, + { "({-211,}'){}", 1, 0 }, + { "[^(\?imsxsx:{}[*])cccccccccccccccccccccccccccccccc<z0W8]$", + 0, 0 }, + { "(){2,89}$z", 1, 0 }, + { "((050[^\"\"\"\"\"\"\"\"z]8|j{}{,-112}$).pP)qq1~hW}L", 2, 0 }, + { "[[^[(+xx(\?<!xxxxxxxx(\?!xxxxxxxxxx(\?#(\?>[x))(\?:]r.]]]))[" + ":graph(\?<=:])))", + 0, 0 }, + { "[^([(\?#)(\?(\?(<=)l|\?(\?!])kkkkkkkkkkkkkkkkkkkkkkkkkk", 0, + 0 }, + { "[:xdigit:]K(KKKKKKK)^3c.OOO{-240,-10}2{-97,-139}*{-34,}[:" + "xdigit:]", + 1, 0 }, + { "[([^66666666F(\?>FFFFFFFFFFwpP)LLLLLDeDA&Am$l[:xdigit:]!T5#]" + "n[:alpha:]U*)))))))))))))PP]", + 0, 0 }, + { "[[[:punct:]u^[:xdigit:]L(\?:[:xdigit:][[:graph:]PP{21}A[:" + "alpha:]8%I(M%b<eE~#C@r=uG~~~~~~~~~~~~~~~~~~~~~~~~~~~~+w]pP)" + "T]]$$$$$$$$$$$$$$${-121,}|l", + 0, 0 }, + { "([(107{,-4(\?=}~[^D)])f]{,46}+ri<)", 1, 0 }, + { "[(\?<=]{,208}+~)", 0, 0 }, + { "[^444(\?<=4444444[:alnum:]&[,i]0)[:alpha:][:upperword:]", 0, + 0 }, + { "[^([^(\?()*+)SS(\?>SSSSSSSSSSSSSSSSSSSSSS]]]]]]]]]]]]]]]]]]]" + "]]]]]]]]]]]{,-1}])[:blankcntrl:]============================" + "===[:punct:][:blankcntrl:]Z[:space:][:ascii:]$|$[:" + "blankcntrl:] JR.{,133}[:alpha:]$\?)<]", + -1, 0 }, + { "(OL[:u[pperword(:][:s[^pace:].[:spac(e:],,,,]*])$)\?)", 1, + 0 }, + { "(VI[:digit:][:alpha:]6)EG", 1, 0 }, + { "({}){-2,-40}rrrrrrrrrrrrrrrrrrrrrrr[:punct:]", 1, 0 }, + { "()q", 1, 0 }, + { "[^([^[([^C|])]{,-56}[:xdigit:]{-144,}V])fYv{-[40,-58}$@@@@@@" + "@@@@@@@]|Y(-]-.]h-[:dig(it:])>>>dddddddddddddddddddddddddd{" + "101,}", + 1, 0 }, + { "([P,{1(\?(\?(<=28,-218[^)}LoZX)])!!!!!!!!!!!!!!*[:blank(\?!" + "cntrl:]ed)\\\\\\\\\\\\\\\\\\\\[\\L\?][:graph:]:*Y{-108,120}" + "xCC)]", + 1, 0 }, + { "(A[:space:]PP{185}a^!!!!!!lllllll)*db\?$Pfr", 1, 0 }, + { "{-21,-118}kG[(\?{:xdigit:]})[:punct:]{69}" + "Qyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy5{}TTTTTTTTTTTTTTTTTTTTT", + 0, 0 }, + { "[[^[P(\?<=P$X>0^d.[:punct:](\?#ccccccccccccccccccccccccc{}" + "3N000(\?>00000000000000000000000000000]f[:punct:]5)).R======" + "=========={,222}^wwwwwwww$)]-{} " + "]{,-22}CjP{242,}", + 0, 0 }, + { "[(\?#^]{})", 0, 0 }, + { "[^([[([([[([^[^(\?:(\?(\?(!)]\"))h>\"RRRRRRRRRRRRRRRR[^" + "RRRRR{68,-65}7Q(\?{]", + 0, 0 }, + { "(P{}){175,}PP{}rttttttttttt", 1, 0 }, + { "[:bla(\?{nkcntrl(\?#:]})))))))))))))))))))))))!!!!sR{})", 0, + 0 }, + { " [:digit:]dAAAAAAAAAAAAA^[:ascii(:]55)^", 0, 0 }, + { "($*)dZY", -1, 0 }, + { "[:graph:][:lowerprint:]S[:gr(\?=aph:]{-128,}" + "666666666666666666666{}[:upperword:]|" + "nnnnnnnnnnnnnnnnnnnnnnnnnnB)c[:xdigit:]{-225,}{-4,}{-192,}" + "QQQQQQQQQQQQQQQ@@@@@@@@@@@@@@@@@@@@@@.", + 0, 0 }, + { "([:digit:]s{44,}{}{-31,}c{,-130}pP){-241,}UeN", 1, 0 }, + { "([^)((\?>\?#{}hK\"V2\?d][KKK(\?imsxim:KKKKKKKKKKKKKKKKKKKK[^" + "KKKKKKKKKWWWW[WWWWWWWWWWWWWWWWW)B])_l_3", + 1, 0 }, + { "[(^[(\?!*){[^,91}].j]*]L)*c|[:alpha:]&", 0, 0 }, + { "[^[[[^[777GGG(\?:W_U(\?imsxms:[:punct:]A]-)[:digit:][:" + "blankcntrl(\?(:]][:alnum:)])]WRRRRRRRRRRRRRRRRRRRRRRRRRRR]{" + "31,}[:xdigit:]][:xdigit:]))))))))))))))))))))))$[:xdigit:]", + 0, 0 }, + { "[:ascii:]m*[:punct:]#[(\?<!:punct:][:alpha:]-," + "7vyXeeeeeeeeeeeeeeeeeeeeeeeee^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" + "^%%%%%%%%%%%%%%%%%%%%%%%%%%%%[:digit:]''''''''''''''''')", + 0, 0 }, + { "([^*[(:punct:]9999999999999999999{147,}]j{,193}{171}Z-)){" + "208}0[:graph:]yDt", + 1, 0 }, + { "(dw[[:alpha:]U]ttt[tttttttttttttttttttt]Q^171e)[:xdigit:]/", + 1, 0 }, + { "[[^((\?#)Tqqqqqqqqqqqqqqqqqqqqqqqqq105++++++++++++++++++++++" + "++++b7V+7dit]])|D", + 0, 0 }, + { "{}P7.Ajh[:xdigit:]^[:blankc((\?(\?<=nt[rl:]FFF)-]){}o|a[:" + "grap(\?!h:]))PsssssssssssssssssssssssssssssssN^{-60,}Kb", + 0, 0 }, + { "[:alpha(\?(:]$!_+777777777777777777777777O)666)lll[^llllll[^" + "l{{{{{{{{{{{{{{{{{{{{{{|]{-217,}MoEl`7)^)LlU[:alph[a:]({-" + "241,27})]]{-212}{,249}n)X", + 1, 0 }, + { "[U|ajP[:alnum:]n[(:digit:]]W)[:graph:]b[:xdigit:].P", 0, 0 }, + { "(([:low(\?-imsx:erprint:]|{}[:ascii:][:gr(\?:aph:])>>>>>>>>>" + ">>>>{,-129}))\?{-226,}^P)R", + 2, 0 }, + { "[^[[nnnnnnnnnn(\?=nnnn(\?!nnnnnnnnnnnn(\?#nnnnnn{,-38}N){" + "202,}]$[:alnum:])]t][:alnum:[]^=w){237}][:alpha:]-[:alpha:]+" + "e", + 0, 0 }, + { "()[(\?(:digit):]+qc)O88888888{,151}aJ", 1, 0 }, + { "([^([(\?!sv(\?=)d]{-200,})N))]Z{-73,15}", 1, 0 }, + { "([\?\?\?\?|||||||||||(\?{||(\?=||||||||-}[))Ehhhhhhhhhhhhh{," + "202}&TcfL((\?:>)((\?!\?>$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$8[:" + "alpha:]\\d])]C[:graph:]h*,\"\?u{|mU,a)[:blankcntrl:][:" + "lowerp(\?>rint:])PPnP+9.[:xdigit:]*PPjjjjjjjjjj~y<#*scf_\"^" + "e[:xdig(\?(i)t(:])~$y)^){-131,77}^L%", + 1, 0 }, + { "[^[(((\?>)$}h9$B5+yhU/" + "Nqh$YYYYYYYYYYYYYYYYYYYYYShK)3WHw1vMMMMMMMMMMMMM(\?=" + "MMMMMMMMMMMM[:alnum:]/" + ")dddddddddddd(dddddd\"e5zLW)+![:space:]+BHGHfAS]" + "\?IIIIIIIIIIIIIIII*&&&&&&&&&&&&&&&&&&)NNvwDteepjdm<<<<<<<<<<" + "<<<<<<<<<<<<<<<<<<<<${61,219}D][:digit:]0", + -1, 0 }, + { "[:punct:][{177,(\?=234}]ix9*)", 0, 0 }, + { "([^K{,3(\?<=4}]I)\?U)", 1, 0 }, + { "[([^[[[^([([^[^(\?=])X", 0, 0 }, + { "[:blankcntrl:(])qd_R\?{\?r[=\"[^[^6]vX8)a+{C%H84CK6Uy#E]sE{" + "208}", + 0, 0 }, + { "PPPPPPPPPPPPPPPPPPPPPPPPPPnnnnnnnnnn()[:upperword:]us", 1, + 0 }, + { "x{,46}[:graph:]LU{}CU)", 0, 0 }, + { "()-t|[^W{}][:lo[^werprint:]{}]\?b5", 1, 0 }, + { "()x5A", 1, 0 }, + { "[([^]-217)]s{-47,135}0000000000000000000000000000000{,-108}", + 0, 0 }, + { "[^((\?{[^L\?u]})f", 0, 0 }, + { "()[[^^(\?{y(\?=VF_(\?<=]D}))]-= {46,})^5bIEQ{,-96}Z", 1, 0 }, + { "([^{}f[:punct:]\"X%%%%%%%%%%%%%%%%%%%%]5{-194}A[:punct:]" + "mnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn+AAAAAAAAAA-)", + 1, 0 }, + { "(CCCCCCCCCCCCCCCCCCCC{-230}352{-182,-68}O4{})", 1, 0 }, + { "([^[^\?[:space:]$TTTTTTTTTTTTLLLLL[^LLLLLLLLLL[^({}{4,-179}]" + "]J] C]){}C{}{-224,})QQQQQQQQQQQQQQQQQ^", + 1, 0 }, + { "([[:alnum:]].){-155,-82}dzI{55,}^", 1, 0 }, + { "([[:alnum:](\?#{88,-178})[:graph:]NC\"pI[:punct:]rmWd5y^p+" + "gUP]YYYYYYYYYYYYYYYYYYYY~{,-62}{,200}{-109}{}+" + "333333333333333333333333333333{}p)^.hhhhhhhhhhhhhhh", + 1, 0 }, + { "[000000(\?mmsx:00000000000000000000000)M]]]]2*`[^]QQQQQQQQ(" + "\?<=QQQQQQQQQQQQQQQQQQQQQQQ])\"<h\?", + 0, 0 }, + { "[^((<g(\?>5j[bbbbbbb(\?{bb)o{}3(\?imxisx:E]g})YYYYY[:" + "blankcntr(\?#l:].(()w264[:ascii:]^)[:ascii:]G)&(n " + "{^PGn[:xdigit:])nv_e|]{-103,30}", + 3, 0 }, + { "[^(([(\?!{}@[^HCO[[^^D[|]{,-49}][:xdigit:]]c`4[:ascii(\?<!:]" + ")$66666666666)*)]PP$Z[:alpha:]{,-235}UK],(aT/" + "+6rbMqs60EloA)[:g(\?isx:raph:]!)]z$o{-24,}x1E[:blankcntrl:]" + "ZDFvk", + 1, 0 }, + { "[:blank(\?=cntrl:]US@.!\"[:digit:]*E)$16182", 0, 0 }, + { "[-{}x{3772[}][:(\?<=xdigit:][:u(\?#pperword:].W)aD)<pfN<b=C|" + "-{-38}EZdOP|!>ggggggggggggggg\\\\\\\\\\\\\\\\\\\\\\\\\\Ef[:" + "space:]\?][:ascii:]{21,}", + 0, 0 }, + { "([:xdigit:]W[:u(pperword(\?::]jS " + "[:upperword:]*)[:alpha:]nnnnnnnnnnn))-148}SSu", + 1, 0 }, + { "([^(\?!\?)[(:upperword:])Bx^x$~lCr6*)6", 1, 0 }, + { "[{,-78}Y[:xdigit:][^s(\?>]P[:space:])]YYYYYYYYY[:punct:][:" + "alnum:][:blankcntrl:]", + 0, 0 }, + { "([MMMMMM(\?(MMM)M(\?<=MMMMMMMMMMMMMMM[^M)]en][:punct:]-[:" + "alpha:]))Nr[:space:]", + 1, 0 }, + { "~=1([^(\?=(\?:l){}])j{-44}{-18}[^u[:graph:]]{-187,}[:xdigit:" + "]w[:alpha:])", + 1, 0 }, + { "[ccccc(\?>c(\?{cccc[ccccwetoCei+)w&-+{,-142}[:alpha:]" + "PP66io4(|zkA=],,,,,,,,,,,,,,,,,,,,,Lx5Cx{d2bb}]{188}U~~~~~~~" + "~~~~~~~~~~~~~~~~})", + 0, 0 }, + { "Q|0\"[:d(\?:igit:]^{,-174})", 0, 0 }, + { "[^[(\?>rh])]", 0, 0 }, + { "[ees{{{{{{{{{{{{{{{{{bbbbbbb4`ml******(\?=****+])", 0, 0 }, + { "((hdG[((\?<=:dig(it:])[^[:alpha:]$(\?sxi:)x{11390}[(\?{:" + "upperword:]~)i 8[:blankcn[trl:(])]+{,-183}Zqp", + 2, 0 }, + { "Dd{D8`+DW={-[53,1(\?<=71}])", 0, 0 }, + { "[:(\?(alpha:][:punct:])", 0, 0 }, + { ".LLLLLLLLLLLLLLLLLLLLLLLLLLLL{}pP[:punct:]x0CZ{30,}!!!(!!!!!" + "!!!!!!!!!!!!!!!!!!!!==@77.%[:graph:]D)", + 1, 0 }, + { "[^[^[[r(\?#]){-237,}RRRRRRRRRRRRRRRRRRRRRRRR[^Rll(\?!(\?{" + "lllll]", + 0, 0 }, + { "()*ooooooooooooooooooooyyyyyyyyyyyyyyy", 1, 0 }, + { "{,4(}D)JJJJJJJJJJJJJJJJJJJJJJJJJ", 1, 0 }, + { "((b.D{}[:al[pha:]{64}]{})==========================[:alnum:]" + "h>77b)!Ab", + 2, 0 }, + { "([^[^[^oooooooooooooooooooooo][:space:][:punct:]PeniKe*~$" + "g\?${>[:lowerprint:]w))))))))))))))){}yyyyyyyyyyyyyyyyyy]pP." + "|QhZ]{,190})sssssssssssssr+=[:blankcntrl:]" + "WWWWWWWWWWWWWWWWWWWWW", + 1, 0 }, + { "([*(\?{})hhhhhhhhhhhhhhhh]G{,-170}QdErrrrrrrc-" + "jjjjjjjjjjjjjjjjjjjjn+{-130,-10})PpDS@Bee", + 1, 0 }, + { "([:b(\?=lankcntrl:]))T[:alnum:]{-224}ywt", 1, 0 }, + { "([633(\?<=333(\?<=3333333333(333333)^\?]aGA)[:digi(\?>(\?{t:" + "])$[[:space:][:xdigit:])|8T\?',_{171}{}{113}b\?5kAv0/" + "7{})`huh>xM]C8pYRz]s$Eu08)", + 1, 0 }, + { "-(pP)[:alnum:]$^", 1, 0 }, + { "[^x(\?{{17681}]P*)U(_t/8E_\"iN})3333333", 1, 0 }, + { "(([^([[r(\?=[[^^*kx$][:alpha:]:::[:::::[^[^::::::::((\?{\?{:" + ":]).^p[:space:]}){52}{}]W{}fn", + 2, 0 }, + { "[:(\?>punct:]Ef[:xdigit:]x{c07b}{-50}Z{129,}YL1T`\\A)x[:" + "punc(\?=t:]e[:xdigit:]2c6E46Y)+n ", + 0, 0 }, + { "[^(\?!{,-79}[:punct:]'|}>,)][:blankcntrl:]{-118,-231}{-119,-" + "50}:XXXXXXXXXXXXXXXXX-~{}$txlB)3KFL", + 0, 0 }, + { "[^(([^fccccccccccccccccccc(\?<!ccccgQeKMfKzz]X$$$$$$$$$$$$$$" + "$$$$$$$$$$$$$$$$$[:l(\?<=(\?<=owerprint:]))s{-97}{}))EUi${,-" + "132}'{79}---------{,-93}77777777777777777[:lowerprint:].:H)[" + ":punct:]nnnnnncP\?s1:dGed{186}N@pppppppppppppppppppppP{-212," + "-110}[:space:][:lowerprint:]$S}7{-112,164}-*.{-184,}" + "OOOOOOOOOO]f\?", + 0, 0 }, + { "(([\?#(\?>)])qcU$Q7|82\?{})", 2, 0 }, + { "[^yyyyyyyyyyyyyyyyyyyy(\?#yyyyyyyyyyya][:ascii:]\?)", 0, 0 }, + { "(([((\?{)EEEE(\?<!EEEEE(\?:EEEEEE~)}){244,}" + "QQQQQQQQQQQQQQQQQQQ(\?>QQQQQQ(\?!QQQQQ][:digit:]\?))" + "99999999999999)[:digit:][:upperword:]b))PP{}{}", + 2, 0 }, + { "(K(c=B))", 2, 0 }, + { "(G`*s\?b[:g(raph:]))", 1, 0 }, + { "[^[([[[*QQQQQQQQQQQQQQQQ(\?=(\?=QQQQQQ(\?<!" + "QQQQQQQQZddddddddd((\?{\?>ddddddddddc{22,}iiiiiiiii(" + "iiiiiiiiiiiiiii(\?#iiiiiii[^i))\?\?\?\?\?\?]WWW)[:" + "lowerprint:])]{-60,202}+[:upperword:]f[:xdigit:][:alnum:]{,-" + "214})1~~~~~~~MMMMMMMMMMMMMMMMMM.", + 0, 0 }, + { "({-102,})A.", 1, 0 }, + { "[((((\?<!(\?[^>(\?#\?()))p\"JD.{}(\?>)))((\?{l(\?<=).'053][:" + "xdigit:]N+)})]WWWW%[:asc(\?{ii:]}))B[:alnum:]X){}s[:digit:]", + 0, 0 }, + { "x7&{139}WWWWWWWWWWWWWW[:blankcntr[^(\?<!l:]-71]\"{-167}cqkI)" + "[:dig[^it:]{}{}[:digit:]*[:punct:]-[l11111111111111111(\?(" + "111111111{175,-216}~[:alnum:]`+X1F)vCpWSp(\?>~[^n@f`````````" + "````)````````P])Y,N{}{}]{}pXF@)", + 0, 0 }, + { "G[([(\?(^)$])P]^[:alnum:]){,-48}[:blankcntrl:]{}", 0, 0 }, + { "[[^[^f(\?=f(\?<=fffffff[^fffffffff[^fffffffff(\?<=fff]){-" + "194,150}fx{e5a4}V", + 0, 0 }, + { "9[:xdigit(\?{:]})", 0, 0 }, + { "[^([[(\?>()$xxxxxxxxxxxxxx[xxxxxxxxxxxxxxxx((\?=aA)s13]])pp[" + "(\?>pppppppppppppppp|{}){20,}]b)]{-179,183}{-204,}[:ascii:])" + "]-11111111{}{,132}qooooooooooooooooooo{}${}|9t", + 0, 0 }, + { "([^[{}]\"[^6]*-{,-106}{}u]BR~8WG,U-)[:blankcntrl:]", 1, 0 }, + { "[''''''''(''''''''''z])c", 0, 0 }, + { "[^[(\?>])[:alnum:]r[:alnum:]+{,215}D]", 0, 0 }, + { "([({,127}7Qr(\?:z)pPNev%}(\?msximsx:4(\?<!){}&.D5555(\?<=" + "55555555555555555555i$[:xdigit:]){,-157}[:graph:]U[:punct:]" + "nn(\?=nnnnnnnnnnnn(\?>nn(\?:nnnnnnnn_U{}]E)):^" + "oooooooooooooooooooooooooooo)", + 1, 0 }, + { "[^(\?#)(\?<!k2z]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]BW[:alnum:][:" + "graph:]{157}Y]s$C)[:graph:]{,-189}", + 0, 0 }, + { "$+CCCCCCCCC[^CCCCCC(\?<=Ca=]r{-81}[:alpha:][:alpha:])E=", -1, + 0 }, + { "[(((\?=\?{([^(\?<=)])>!(([:alnum:]{252}{}})ffffffffffffl){}" + "A2r\?~ImE\"[:punct:]){}[:digit:]", + 2, 0 }, + { "([:blank[cntrl:]].t^P)", 1, 0 }, + { "[^[(\?:X])|rrrrrrrrrrrrrrrrrrrrrrrrrr*P]Q", 0, 0 }, + { "[[[^(\?{((\?<!))s})(\?<!A){14}(\?:L*+TTTTTTT]U{[^-12([\?!,}" + "\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?)Y`Y)L]|]]|" + "]", + 0, 0 }, + { "hkXzf',]yP$+[:u(pperword:])", -1, 0 }, + { "(#[:blankcnt(\?iximsx:rl:])$QQQQQQ{}[:digit:])\?A", 1, 0 }, + { "(B{-34,})*{,106}", 1, 0 }, + { "[(\?{:graph:]})", 0, 0 }, + { "((){}{,63}[:punct:]^t[:space:])^17737", 2, 0 }, + { "([^[SSSSSSSSS[SSSSSSSSSSSSSSS[([[[{38,}]Jn][:alpha:]])])$'", + 1, 0 }, + { "[^({}{95})B{1(\?>15}]x{f779}ZZ,Wo)O[:alpha:][:lowerprint:]{" + "81,228}Q[:upperword:]", + 0, 0 }, + { "[[^[^()n[[[[[[[[[[[[^[[[[[[[[[[(\?: G)(\?{K![^m) " + "j(\?:C|((\?:n*Xlaa908:n$m,))[:xdigit:]x(\?{{1a5cd}" + "pppppppppppppp(\?(pppp)p(pQ)))" + "ddddddddddddddddddddddddddddddd]q[:alnum:(\?{]Ga})\?})@[:" + "lowerprint:]{,169}[:blankcntrl:][:graph:]]n{-76,}|U\"{,-54}" + "t]I{}{-64,-232}]\?].\?{-111,227}) " + "@hFp\?j=H$Wbu<{,209}De{,145}{206}-})[:blankcntrl:]", + 0, 0 }, + { "[^[^(LLLLLLLLLLLLLL[^L[L[:alpha:]3{,189}(\?#(\?>n){}^" + "EXXXXXXXXXXXXXXXXXXXXXXXXX]c*)^r=$WWWWWWWWWWWWW", + 0, 0 }, + { ")w###################", 0, 0 }, + { "{,121}[:d(\?(i)git:])E\?[:punct:]LLLLLLLLL[:ascii:]+", 0, + 0 }, + { "([]]]]]]]]]]]]][:space:]Jrt3o.]b)pwwwwwwwwwwwQfm~", 1, 0 }, + { "[+-{,-120}*(\?!()t*(\?(\?{>G)F)yd]V{}f<\?}){245}" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx[:upperword:]", + 0, 0 }, + { "(DDDDDDDDDDDDDDDDDDDDDDDDDDDDDc[:space:][:pu[^nct:]{-11,12}[" + ":ascii:][:alpha:]{,155}P])", + 1, 0 }, + { "()ggggggg{-136,-21}", 1, 0 }, + { "([^((\?<=U\?)(\?=^^^^^^^^^^^[^^^^^^^^^^^^^///(\?#//[////////" + "////////////" + "(\?()#######b+]$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$^[:digit:])" + "\\U]Q8@}4d)\\U", + 1, 0 }, + { "A[:graph(\?::])-mo=U[:upperword:]" + "ttttttttttttttttttttttttttt", + 0, 0 }, + { "[^(((\?=\?im-m(sx:)c~~[^~~~~~~~~~~~~~(\?>~~~~~~~~~~~~~" + "SSSSSSSSSSSSSSSSSSSS]{51,}[:digit:]{,-179}N))kk[" + "kkkkkkkkkkkkkkg$)[(\?::punct:]zWl)]|)*", + 0, 0 }, + { "[((\?=()+A)][:graph:]x0B)[:graph:]", 0, 0 }, + { "(nR%B[:blankcntrl:]C=|en-[:digit:]n[:graph:]HHHH[HH]D\?%[:" + "digit:]MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM.z(oF9zW8A7cfff(f))-[" + ":blankcntrl:][:blankcntrl:]A[:digit:])D{,-243}", + 3, 0 }, + { "([[()]]{,-251(})\?L)uw@", 2, 0 }, + { "\"|{(,-144})A.ooooooooo(ooooooFFFFFFFFFFFFF\?)n{,-18}", 2, + 0 }, + { "([^([(([[^([000000[0(0(\?!0(\?=0000000])45|E]", 1, 0 }, + { "[B[[[[[[[[[[[|{}*oKqv%(\?<=wsQ{1pMeK1^6%nLNqi<@ge][:punct:]=" + " M@* " + "D|NwL\\-" + "117\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"~" + "Qnd]h.O\"01x:[:alpha:]^){}D}\"", + 0, 0 }, + { "([[RRRRRRRRRRRRRRRRRRRRRRRRRRRRxpSrx{7d79}*oJ2`Ft{n1,3g:1H@" + "bT$D " + "&[n/" + "Cg)=ld@Ir{Fk>*4*`(\?>````````````````````(\?:`````.........." + "...........]]{,246})7 \"F4[^F|/g)]+e`rw@{,-69}H)", + 1, 0 }, + { "([(\?<=)X[:digit:]PP.[(\?#:((\?#\?#graph:])[:digit:][Q+)(N][" + ":alpha:]]f)[:graph:])+Elllllllllllllllll[:digit:]=)pP{uU-" + "20bzY|ZKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKt<c", + 1, 0 }, + { "[^(([^$(\?:(\?#w)[(\?::punct:]]d{-149,}[:ascii:])[:" + "blankcntrl:]@@@@@[@@@@@@@@@@@@@@[:graph:][:xdigit:]O[:alpha:" + "]2$-[:graph:])[:lowerprint:]-\?#S[:blankcntrl:][:alnum:]){-" + "77,}]d[:digit:]N5v+Sqqqqqqq^% " + "-I4]*.)^[:alnum:]" + "JDfjMRU7ttttttttttttjjjjjjjjjjjjjjjjjjjjjjCCCCCCCCCCCCCCCCCC" + "CD{,21}{0,67}[:graph:]{,208}B", + -1, 0 }, + { "(%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%[:ascii:])i{}[:lowerprint:]" + "epxxxxxxxxxxxxxx[:lowerprint:]r-", + 1, 0 }, + { "([(^w(\?!)()])-s", 1, 0 }, + { "[aIIIIIIIIIIIII(\?imsxims(\?=x:IIIIIIIm^NXXXXX(\?!(" + "\?isximsx:XXXXXXXXXXXXXS0]F)z))+" + "rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr{,-237})" + "ZZZZZZZZZZZZZZZZZZZZZZ", + 0, 0 }, + { "(Z)[:alpha:]", 1, 0 }, + { "U#Z(=)", 1, 0 }, + { "([:lowerprint:][:punct:])1cVb*[:xdigit:]&&&&&&&&&&&&&&&&&&&&" + "&&&&O", + 1, 0 }, + { "()~K`3/[^*h[]G6[:upperw(\?()ord:]w)[:punct:]]{}", 1, 0 }, + { "[[[]V[:digit(\?>:])|l*KKKKKKKKKKKKKKKKK,,,,,,,[,,,s.{148,}" + "P33333][:lo(\?<!werprin(\?!t:]ZZZZZZZZZZZZZZZZZZZZZZZ]{,-" + "229}{-160,}){,-211}XPPP].{}z[:alnum:][:alpha:(\?=]t{166,}" + "uuuuu6]i*p(m))[:space:]E|S", + 1, 0 }, + { "[^(h(\?(\?({#2})(\?(\?#>Q){,57}%[:digit:]" + "\?\?\?\?\?\?\?\?\?\?.[)]]d{)-49,}f)^O{,68})\?C", + 0, 0 }, + { "(}u])18621", 1, 0 }, + { "[:as(\?=cii:][^(\?=)(S-{.F-[:punct:]3-105^[:lowerprint:]" + "111111111111111111111111---)][:alnum:][:ascii:]JJJJJwHSk", + -1, 0 }, + { "[^3>>>>>>-sZ^^^^(\?>]Y[:di(\?(\?imxim:#git:]{-158,-102}[:" + "punct:]{}{87,})))[:upperword:]", + 0, 0 }, + { "[(\?<!^r]$W){}*[:alpha:].[:digit:]", 0, 0 }, + { "[:ascii(\?::[^])X]-", 0, 0 }, + { "[([^]Z)[:upperword:]N{}*[:graph:]*^", 0, 0 }, + { "([[(\?#^[(:graph:]]){205,}[:gr(aph:]T%]^" + "MMMMMMMMMMMMMMMMMMMM){) <v\\[:digit:])", + 1, 0 }, + { "[^Y.h~b(\?<=~P{(\?=169,65}\?[^\?\?\?\?\?\?\?\?\?[" + "\?\?\?\?\?\?\?\?\?K\"s`[yT7oP[:alpha:]{})]zrrrrrrrrrrrrrr)]" + "KKKKKKKKKKKKKKK[:digit:]S][:lowerprint:][:digit:]", + 0, 0 }, + { "(s)", 1, 0 }, + { "[u(\?!uuuuuuuuuuuuuuuuuuuu[:digit:]{,48}[:graph:]WL[:alnum:]" + "]v=_)VN>{AjBBBBBBBBBBBBBBBBBBBBBBB[:upperword:]`'W)", + 0, 0 }, + { "[^([[()DN1[^][|]\?]{-104,}])[:space:]][:lowerprint:]r[:" + "alpha:].DU", + 0, 0 }, + { "[^((33333333333333333333333(\?<=3333333D))" + "kkkkkkkkkkkkkkkkkkkkkkk[k[:alpha:]])]X+", + 0, 0 }, + { "[({,-17})[@e{220,(\?#41}])]]{-213,-225}", 0, 0 }, + { "[[^(\?#[(\?:^[[(\?(^]))]])]vvvvvvvvvvvvvvvvvvvvv{,96}|m]{-" + "79,248}[:alpha:])", + 0, 0 }, + { "([[(\?imsisx:^}$,-[:al(\?>num:]Xqqqqqqqqqqqq{-185,154}]b#+T)" + "{-241,})A{-27}[(\?<!:lowerprint:]X)[:punct:]ME-]+" + "BBBBBBBBBBBBBBBBa|{-40}M8mhgD 0HU]{16})", + -1, 0 }, + { "[^(\?>([\?()(\?#))]--R1rk^UnP.[(\?!:digit:]])^)[:upperword:]" + "{}0000000000000000000000000000000~U{-139,-19}z<L-228", + 0, 0 }, + { "()-:=3uE$[:alnum:]bP%{-210,}", 1, 0 }, + { "(U)7777]]]]]]]]]]]]]]]]]]]]]]]]]]]]]c::AA[:alpha:]{,3}f1{" + "NzH@3lTf{}{", + 1, 0 }, + { "[C{(\?>})RR(\?=R<]p'N~&.-})6]", 0, 0 }, + { "[^\?[^(\?(lFt]).[^7Q-])kkkkkkkkkkkk]XTFy\"1Deiv!,'xVK", 0, + 0 }, + { "[^$[^[:xdigit:](\?{*{245,99}h8v(\?!)]]u)Z[:punct:]})[:alnum:" + "]+|[:blankcntrl:]u{}[:lowerprint:]+bBJ4+k-v{-116}", + 0, 0 }, + { "S)f{,180}[:graph:]&{12,244}", 0, 0 }, + { "(([[(.()[^^{80(\?>(\?<=,235})ddddddddd[^ddddddddd(\?<=d.__B{" + "36}````````````````(\?:```(\?>```````,,,,,,,(\?:,,)P$U,[:" + "xdigit:])zzzzzzzzzzzzz]UUUU[uB]n<&[(:ascii:].][:alnum:])\?S]" + "{})d{138,}s9========[:lowerprint:]]OOOOOOOOOOOOOOO|" + "yyyyyyyyyyyyyyy$LZ[:lowerprint:]EEEEEEE[:ascii:][:punct:]" + "VpP^{-48}D){,46}x))2P))a[:lowerprint:]r", + 2, 0 }, + { "[^(((\?<!):())PPPPPPPPPPPPPPP(\?=[PPPPPPP(\?{PPPPPPPP$)})" + "77777777777777777]{,-57}::::::::::::(::::::::::::::::)]g{89}" + "__________________[:xdigit:]l[:punct:])N", + 1, 0 }, + { ":02-k\?p3I7aEhJ\\265-[:space:]pP[:space:]x0F[:alnum:]aM4[:" + "lowerprint:]sA@@@@@@@@@@@@@@@@@@@@@@@@@@@@", + -1, 1 }, + { "a[:upper(\?{word:]})X{-173,}-2F[:lowerprint:]", 0, 0 }, + { "u,w<g*Q002S{,130}{239}[:lower(print:]cr{-165,}#$k<L/" + "&)[:blankcntrl:]aaaaaaaaaaaaaaaaaaaaaa[:ascii:]", + 0, 0 }, + { "(xFA^{-161,93})U[:xdigit:]", 1, 0 }, + { "[^(\?=]{})mE`", 0, 0 }, + { "[[((\?(\?#:alnum:]])x6CS[:digit:]{-197,}.)N", 0, 0 }, + { "[^(\?![])C*[:upp(erword:])-176]", 0, 0 }, + { "[[^[[^[55555555555555555555555555(\?>555(\?<!555)S][]]A[:l(" + "\?>owerp(rint:]])]*", + 0, 0 }, + { "Au)khgzAfXIZoZ=g[:digit:]){,186}Upvf=x<]Tbd5Rq\?.", 0, 0 }, + { "b{-176,}B^[:bla(\?(<!nkcntrl:]{-6,133}#B " + ":)<<<<<<<<<<<<<<<<<<<)[:alnum:]$}}}}}}}}}}}}}}}}}}}}}}}[:" + "xdigit:]tw", + 0, 0 }, + { "(4IIIII(IIIIIIIIIIIIIIIII{})W{-152,-238}){,-56}^{-142,}", 2, + 0 }, + { "[^([[(\?(\?(!)>>>>>>>>>>>>>(>>>>>>>>D)Ix{(1(\?imxmsx:762)c})" + ")A)[[[[[[[[[[[[[[5Rp]DDDDDDDDDDDDDDDDDDDD]Us+\\w[:digit:]{-" + "47}[:xdigit:][:blankcntrl:])ddddddddddddddd[^ddddddddddddd[:" + "digit:]|]]*{-165,-230}{-212}{53,}]\?", + 0, 0 }, + { "[^[^]]|[:(\?:alnum:])}}}}}}}}}}}}}}}}}}}}", 0, 0 }, + { "VVVVVVVVVVVVVVVVVVVVVVVVVVVV[:d(i(\?#git:])){{{{{{[:digit:]" + "ZfQ55555555{}Z", + 0, 0 }, + { "[L][:blankcnt(\?((\?=rl:(\?=]){-35,[^}){)eJb>>>>>>>>>>>>>>>>" + ">>>>>>$ [:xdigit:]l0Tv2Tw2@C[:space:]Zc/{*)>]N3j~.dMBBBB", + 0, 0 }, + { "[[^(\?>(([]))])[:graph:]]{65,}as#Q:lQ", 0, 0 }, + { "[^[fPPUUUUUUUUUUU(\?#UUU[^UUUUUU(\?<=" + "UUUUUUUUUGGGGGGGGGGGGGGGGGGG((\?{\?=GGGGGG.MK))+]+)&UxFW)" + "rwv\?@D.", + 0, 0 }, + { "{-(60,})m", 1, 0 }, + { "b[(])^w", 0, 0 }, + { "[][^qVs(\?:(p])X)\?'", 0, 0 }, + { "()8", 1, 0 }, + { "(t[:punc[^t:(\?{][:blankcntrl:])})[^8\?]z*]", 1, 0 }, + { "[:lowerprint:])[:graph:]lppppppppppppppppppppppppppppf", 0, + 0 }, + { "[:alph(a:])[:ascii:]g +z-Bc-U{,%Gk", 0, 0 }, + { "u[:graph:(\?=]*)W:::", 0, 0 }, + { "([:alnum(:])l)", 1, 0 }, + { "[[[}}}}}}}}(\?<!}}}}}}}+(\?{),,,,,,,,,,,,,,(\?!,,,,,,,,]" + "99999999999&R[:ascii:]ZZZZ-{-10,}{96}Ed*][:graph:])]}){}{}G{" + "-9,}", + 0, 0 }, + { "([^[{}]]Z[[^:graph:]{-47}55555555555555555555555555555[:" + "ascii:]s]6,$:3qAew1Y)+)[:punct:]", + 1, 0 }, + { "[[[[[([[[[[[[[[[[[[[[[[[[[[[[[8!1i]')", 0, 0 }, + { "([((\?(\?#>)(\?{,)At]%M9FSq5)EB", 1, 0 }, + { "(}````````````````(``{210,})[:(\?#space:]P[:digit:])PP.{-" + "227,}$pK~mm ImR|{,51}[:alnum:]<)[:alpha:]", + 2, 0 }, + { "[^(\?<=])[:digit:]", 0, 0 }, + { "[^'''''''{(\?:178,}e{,16}$QQQQQQQQQQQQQQQQQQQQQQQ$])", 0, + 0 }, + { "[^(\?>@K*)(\?#d18]{78,}B)[:digit:]{-193,}=wg{,59}", 0, 0 }, + { "[^.{156,}!(\?<=!!!!!!!!!!!!!!(\?{!(\?(!!!!!!!!!!!!!)})" + "TTTTTTTTTTTTTTTTTTTTTTTTTTTTT[^}}}}}}}}}}}})}}}}}}}}}}}}}]])" + "{}^L#%-{}FC", + 0, 0 }, + { "(eeeee{-169,-100}-fa[:upperword:]N)$Nellllllllllllll", 1, + 0 }, + { "[[(\?!())\?[(\?!:alnum:]e{,28}M])[:punct:]" + "CCCCCCCCCCCCCCCCCCCC]{-150,}{-167}", + 0, 0 }, + { "[[@[@(\?#@[@]P]Z{')]{-186,117}]+)7f-", 0, 0 }, + { "\\Q+kD}]AEM)u ", 0, 0 }, + { "([(\?{(\?=:::::::::::::&){,210}]^})P{-31,}8[:space:]C[:" + "alnum:][:a(scii:]z|[:upperword:])[:alnum:][:graph:])zr~Zk", + 1, 0 }, + { ".[:space:]e[:g(\?{(\?{raph:]})})@@@@@@@@@@@@@wb|~k", 0, 0 }, + { "()ooooooooo\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"[:graph:]", 1, + 0 }, + { "[^64h(\?(@Eyw][:xdi[git:]pP%%%%%u(uuu[:up[perword:]`8Utdh{)}" + "]]))lW[:punct:]W.hhhhhhhhhhhhhhhhhhhhhhhh'm<<}O8`ZXtG.$", + 1, 0 }, + { "BPP[:digit:]bbbbbbbbbbb(bb)S+[:alnum:]", 1, 0 }, + { "um.[:ascii((\?#\?!:])*)+KKKKKKKKKKKKKKKKKKKKKKKKKS.=<Bf", 0, + 0 }, + { "", -1, 0 }, + { "(()$[:lowerprint:][:s[pace:]2]bbbbbbbbbyoooooooooooooooooo*{" + "39,}$')qV`AcH>,eDl", + -1, 0 }, + { "(()[^])e{-241,}", -1, 0 }, + { "()[:alpha:]rliiiiiiii[:alnum:]Mb*QW9N.>\?{115,}&u*j", -1, + 0 }, + { "()[]p", -1, 0 }, + { "(I[^]pfL)$[:punct:]", -1, 0 }, + { "([])>>>>>>>>>>[:alnum:]", -1, 0 }, + { "([])O\\\\\\\\\\\\\\fffffffffffffffffffffff=s6jCZy/" + "b+ir2'*{151,}", + -1, 0 }, + { "([])nnnnnnnnnnnnnnnnnnnnnnnnnn[:xdigit:]^N$f", -1, 0 }, + { "([]M)[:lowerprint:]a(pg$Z[:punct:])77777777777.", -1, 0 }, + { "([]XXXXXXXXXXXXXXXXXXXXXX-===========)", -1, 0 }, + { "([]lkX{-224}[:blankcntrl:]$gPKIZlSC#F@XX " + "I'^}{234}yZm)uuuuuuuuuuuuuuuuuuuuuurS", + -1, 0 }, + { "([^0kYkg9])IIIIIIIIIIIIIIIIIIIIII/" + "{(192,-118}l+FoSD6\?A)c[:xdigit:]`````````````````e-{-4,-" + "170}x{4620}Z[:upperword:]", + -1, 0 }, + { "([^[^[^()(\?>){}B]XYF+#[:alpha:]{-85((,-55[^}t]n).{,-33}]](" + "bQJ!|O+{175,})RFh)Z+^.{137,}:VpP[:alpha:]-MceqVVkkkk(" + "kkkkkkkkkkkkkkkkkk)" + "\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?{-" + "115,-67})``````````````````````````````", + -1, 0 }, + { "([^]EzU[:alnum:]+^^^^^^^^^^^^^^^^^^^)[:xdigit:]HHHHHHHH$" + "66666666666666666666666666666666UUUUUUUUUUUUUUUUUUUUL{}iiii{" + "-76}X", + -1, 0 }, + { "([^]~~~~~~~~~~{240,})]NOp", -1, 0 }, + { "(sb)[:digit:]VVVVVVVVx{9569}52,|]", -1, 0 }, + { "(x{19762}){}", -1, 0 }, + { "-[:xdigit:][]", -1, 0 }, + { "121|", -1, 0 }, + { "141[:xdigit:][:lowerprint:]{24}{59,191}[:digit:]/", -1, 0 }, + { "G[^],,,,,,,,,,,,,+\"DiX", -1, 0 }, + { "Gm(ho9:\"8{-188,-200}Z[:blankcntrl:]{,171}" + "\?\?\?\?\?\?\?\?\?\?\?[:blankcntrl:]LLLLLLLLLLLLLLLLLLLLLLL{" + "}^[:graph:][:blankc(\?#ntrl:])w", + -1, 0 }, + { "N\"\"\"\"\"\"\"-------------------------|[:alnum:]" + "AAAAAAAAAAAAAAAAAAAAf\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?", + -1, 0 }, + { "U{-30,}^\?\?\?", -1, 0 }, + { "W^*04rAY(Ee*>[^o3[]]_)", -1, 0 }, + { "X[^]}*C[:alnum:]", -1, 0 }, + { "[${,-3}]+^\?[|x8A|][:space:]'''''['''''" + "JJJJJJJJJJJJJJJJJJJJJJJJJJJJJyl}.Y7G]", + -1, 0 }, + { "[()&[&&&]\?\?[" + "\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?" + "pg%k8ug`Wqk4|NR{h[CK5Ez=]jHpQw&`{:]{,91}D", + -1, 0 }, + { "[(\?#(\?:)[)([\?>)(\?>(\?:[:alnum:])]G]{85}[^)w]N]gYrUs|", + -1, 0 }, + { "[(\?<=)[:digit:]\?]{152,}VR|", -1, 0 }, + { "[****(\?>**********(\?<!*******Q)Vr){[^25,}*:" + "FFFFFFFFFFFFFFFFFFFFFFFF(\?{FFFF(({}D]|", + -1, 0 }, + { "[:ascii:]+{124,}:*]\?$-{92}D[:lowerprint:]``````````````````" + "```", + -1, 0 }, + { "[:ascii:]\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?A<", -1, 0 }, + { "[:blankcntrl:]p\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?$" + "\?TTTTTTTTTTTTTTTTT[:ascii:][:upperword:]", + -1, 0 }, + { "[:punct:]{254}DDDDDDDDDDDDDDD@[:alpha:]Z\?\?-----R", -1, 0 }, + { "[:upperword:]J\?\?nqCAdfyW5", -1, 0 }, + { "[:upperword:]{-39}|", -1, 0 }, + { "[:xdigit:]^\?", -1, 0 }, + { "[Z*e ]NdmP\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?\?", -1, 0 }, + { "[[:punct:]q]ex{15625}-", -1, 0 }, + { "[[[^([^L((\?{b(\?=C\?]-134{,-207}[:ascii:]Hz}XIz}|", -1, 0 }, + { "[[^V(\?:(\?<!(\?>))TTTTTTTTTTTTTTTTTTTTTTT)[:punct:][:digit:" + "]]GGGGGGGGGGGGGGGGGGGGG,]|.{-224}{96}{239,}1", + -1, 0 }, + { "[[^^PP]{,-222}{182}{141}]zFD}-.", -1, 0 }, + { "[] Hn&[:xdigit:][:upperword:]f", -1, 0 }, + { "[]$.B", -1, 0 }, + { "[]&&&&&&&&&&&&&&&&&&&&&&&", -1, 0 }, + { "[]()[:xdigit:]er063{132,140}$", -1, 0 }, + { "[]+1434", -1, 0 }, + { "[]-", -1, 0 }, + { "[]-#yyK", -1, 0 }, + { "[]-(S$5)AxbdTKO[:alnum:]", -1, 0 }, + { "[]2883", -1, 0 }, + { "[]2dhd-[:alpha:]" + "sssssssssssssssss55555555555555555555555555555555Z[:punct:]", + -1, 0 }, + { "[]4", -1, 0 }, + { "[]44444444444444444G", -1, 0 }, + { "[]\?", -1, 0 }, + { "[]A", -1, 0 }, + { "[]Gap8bc", -1, 0 }, + { "[]OOOO", -1, 0 }, + { "[]PP", -1, 0 }, + { "[]QQ", -1, 0 }, + { "[]WaFaGO,o", -1, 0 }, + { "[]Z", -1, 0 }, + { "[][:alpha:]|[:digit:]Ls$I-Ff~+xA3e", -1, 0 }, + { "[][:ascii:]-218", -1, 0 }, + { "[][:ascii:]N}}}}}}}}}}}}}}}-{137,}8682", -1, 0 }, + { "[][:lowerprint:]Ur", -1, 0 }, + { "[][:space:]15097", -1, 0 }, + { "[][:xdigit:]", -1, 0 }, + { "[]dpSSSSSSSS", -1, 0 }, + { "[]e13768", -1, 0 }, + { "[]gT", -1, 0 }, + { "[]h", -1, 0 }, + { "[]n", -1, 0 }, + { "[]vvvvvvvvvvvvvvvvvvvvvvvvvv*[:xdigit:]", -1, 0 }, + { "[]{,-212}1111111111111111111C3821", -1, 0 }, + { "[]{-128,}hc", -1, 0 }, + { "[]{-181,}&[:xdigit:].\?}}}}}}}}}}}}}}}}}}}}}}", -1, 0 }, + { "[]{}F&}i`7|ZAH", -1, 0 }, + { "[^(\?())u{196,}pP][r^ndddddddddddddddddddddd]{31,246}\?J", + -1, 0 }, + { "[^.ii.1-S]lwwwwwwwwwwwwwwwwww[^wwwwwwwwwwwwww[:alnum:]DOpP+<" + "N][^]44{179}{-194,56}", + -1, 0 }, + { "[^2[:alnum:]]\?t\?\?", -1, 0 }, + { "[^[((\?{[^^<<<<(\?(\?<!{)})(\?<!]{,184}{-213}|", -1, 0 }, + { "[^[^[]\?{89,}PPsvf{[:space:]]]vd{161,}", -1, 0 }, + { "[^[^].]+{0}s", -1, 0 }, + { "[^]${}", -1, 0 }, + { "[^]([:punct:]),%[:xdigit:]w^0\?{-233}", -1, 0 }, + { "[^]-", -1, 0 }, + { "[^].^", -1, 0 }, + { "[^]6743", -1, 0 }, + { "[^]JD", -1, 0 }, + { "[^]N=[:upperword:]zzzzzzzzzzzzzzzzz.", -1, 0 }, + { "[^]OLz_6", -1, 0 }, + { "[^]PP[:digit:]0eBEx=", -1, 0 }, + { "[^]SHzuKp", -1, 0 }, + { "[^][:upperword:]{111}-TpmXw", -1, 0 }, + { "[^]^''''''''z{-73,}", -1, 0 }, + { "[^]^{,141}e", -1, 0 }, + { "[^]aaaaaaaaaaaaaaaaaaa{-98,43}", -1, 0 }, + { "[^]f", -1, 0 }, + { "[^]l", -1, 0 }, + { "[^]n\"Wt", -1, 0 }, + { "[^]pPZ\?q+m0LJ+", -1, 0 }, + { "[^]p[:upperword:]L:", -1, 0 }, + { "[^]q\?{,-18}-", -1, 0 }, + { "[^]s[:space:(\?<=]$", -1, 0 }, + { "[^]{,58}t", -1, 0 }, + { "[^]{255,}JJJJJJJJJJJJJJJJJJJJJJJJJJ", -1, 0 }, + { "[^]{45}", -1, 0 }, + { "[^]{W", -1, 0 }, + { "[^]{}{-22}", -1, 0 }, + { "[^]{}{}{}[:xdigit:]+", -1, 0 }, + { "[^]|9{,-108}{}.LVIJJJJJJJJJJJJJJJPP", -1, 0 }, + { "[^{,-254}]|", -1, 0 }, + { "[o(\?{(\?<=}[))f++++++++++++++++" + "777777777777777777777777yzPPs]" + "\?\?dRRRRRRRRRRRRRRRRRRRRRRRRRRRR&]>%fffffffffff", + -1, 0 }, + { "aW|", -1, 0 }, + { "cT{}[]C^r2``tm", -1, 0 }, + { "kkkkkkkkkkkkkkkkkkkkkkk[:blankcntrl:]|{}3{26,}{151,}[:punct:" + "]JJJlH$gP%(2WUE%%%%%%%%%%%%%%%%%%%%a){ibf{}\?", + -1, 0 }, + { "lZ\?\?\?\?\?\?\?\?\?\?\?-P2eZt[:punct:]", -1, 0 }, + { "vF3qn[^]N.", -1, 0 }, + { "wwwwwwwwwwwwww{-176,}275[^]>." + "UUUUUUUUUUUUUUUUUUUUeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee2$Yd", + -1, 0 }, + { "{-197,223}bf]]]]]]]]]]\?&}/s\?\?~c", -1, 0 }, + { "{-37,}EpP|", -1, 0 }, + { "{}@]a[][:xdigit:]z{a", -1, 0 }, + { "}02|", -1, 0 }, + { "}}}}}}}}}(}}){}[llll]^N|", -1, 0 }, + }; + unsigned int i; + int r; + + UNUSED(state); + +#ifdef HAVE_REGEX_H + /* + * Check if we get the expected response. + */ + for (i = 0; i < sizeof(tests) / sizeof(*tests); i++) { + regex_t preg; + + memset(&preg, 0, sizeof(preg)); + r = regcomp(&preg, tests[i].expression, REG_EXTENDED); + if (((r != 0 && tests[i].expect != -1) || + (r == 0 && tests[i].expect == -1)) && + !tests[i].exception) + { + if (verbose) { + print_error("regcomp(%s) -> %s expected %s\n", + tests[i].expression, + r != 0 ? "bad" : "good", + tests[i].expect == -1 ? "bad" + : "good"); + } + } else if (r == 0 && + preg.re_nsub != (unsigned int)tests[i].expect && + !tests[i].exception) + { + if (verbose) { + print_error("%s preg.re_nsub %lu expected %d\n", + tests[i].expression, + (unsigned long)preg.re_nsub, + tests[i].expect); + } + tests[i].expect = preg.re_nsub; + } + if (r == 0) { + regfree(&preg); + } + } +#endif /* ifdef HAVE_REGEX_H */ + + /* + * Check if we get the expected response. + */ + for (i = 0; i < sizeof(tests) / sizeof(*tests); i++) { + r = isc_regex_validate(tests[i].expression); + if (r != tests[i].expect) { + print_error("# %s -> %d expected %d\n", + tests[i].expression, r, tests[i].expect); + } + assert_int_equal(r, tests[i].expect); + } +} + +int +main(int argc, char **argv) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(regex_validate), + }; + int c; + + while ((c = isc_commandline_parse(argc, argv, "v")) != -1) { + switch (c) { + case 'v': + verbose = true; + break; + default: + break; + } + } + + 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 */ diff --git a/lib/isc/tests/result_test.c b/lib/isc/tests/result_test.c new file mode 100644 index 0000000..a0f6702 --- /dev/null +++ b/lib/isc/tests/result_test.c @@ -0,0 +1,132 @@ +/* + * 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 <setjmp.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +#define UNIT_TESTING +#include <cmocka.h> + +#include <isc/result.h> +#include <isc/util.h> + +#include <pk11/result.h> + +/* convert result to identifier string */ +static void +isc_result_toid_test(void **state) { + const char *id; + + UNUSED(state); + + id = isc_result_toid(ISC_R_SUCCESS); + assert_string_equal("ISC_R_SUCCESS", id); + + id = isc_result_toid(ISC_R_FAILURE); + assert_string_equal("ISC_R_FAILURE", id); +} + +/* convert result to description string */ +static void +isc_result_totext_test(void **state) { + const char *str; + + UNUSED(state); + + str = isc_result_totext(ISC_R_SUCCESS); + assert_string_equal("success", str); + + str = isc_result_totext(ISC_R_FAILURE); + assert_string_equal("failure", str); +} + +/* check tables are populated */ +static void +tables(void **state) { + const char *str; + isc_result_t result; + + UNUSED(state); + + pk11_result_register(); + + for (result = 0; result < ISC_R_NRESULTS; result++) { + str = isc_result_toid(result); + assert_non_null(str); + assert_string_not_equal(str, "(result code text not " + "available)"); + + str = isc_result_totext(result); + assert_non_null(str); + assert_string_not_equal(str, "(result code text not " + "available)"); + } + + str = isc_result_toid(result); + assert_non_null(str); + assert_string_equal(str, "(result code text not available)"); + + str = isc_result_totext(result); + assert_non_null(str); + assert_string_equal(str, "(result code text not available)"); + + for (result = ISC_RESULTCLASS_PK11; + result < (ISC_RESULTCLASS_PK11 + PK11_R_NRESULTS); result++) + { + str = isc_result_toid(result); + assert_non_null(str); + assert_string_not_equal(str, "(result code text not " + "available)"); + + str = isc_result_totext(result); + assert_non_null(str); + assert_string_not_equal(str, "(result code text not " + "available)"); + } + + str = isc_result_toid(result); + assert_non_null(str); + assert_string_equal(str, "(result code text not available)"); + + str = isc_result_totext(result); + assert_non_null(str); + assert_string_equal(str, "(result code text not available)"); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(isc_result_toid_test), + cmocka_unit_test(isc_result_totext_test), + cmocka_unit_test(tables), + }; + + 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 */ diff --git a/lib/isc/tests/safe_test.c b/lib/isc/tests/safe_test.c new file mode 100644 index 0000000..89bd3ba --- /dev/null +++ b/lib/isc/tests/safe_test.c @@ -0,0 +1,107 @@ +/* + * 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. + */ + +/* ! \file */ + +#if HAVE_CMOCKA + +#include <sched.h> /* IWYU pragma: keep */ +#include <setjmp.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +#define UNIT_TESTING +#include <cmocka.h> + +#include <isc/safe.h> +#include <isc/util.h> + +/* test isc_safe_memequal() */ +static void +isc_safe_memequal_test(void **state) { + UNUSED(state); + + assert_true(isc_safe_memequal("test", "test", 4)); + assert_true(!isc_safe_memequal("test", "tesc", 4)); + assert_true( + isc_safe_memequal("\x00\x00\x00\x00", "\x00\x00\x00\x00", 4)); + assert_true( + !isc_safe_memequal("\x00\x00\x00\x00", "\x00\x00\x00\x01", 4)); + assert_true( + !isc_safe_memequal("\x00\x00\x00\x02", "\x00\x00\x00\x00", 4)); +} + +/* test isc_safe_memwipe() */ +static void +isc_safe_memwipe_test(void **state) { + UNUSED(state); + + /* These should pass. */ + isc_safe_memwipe(NULL, 0); + isc_safe_memwipe((void *)-1, 0); + + /* + * isc_safe_memwipe(ptr, size) should function same as + * memset(ptr, 0, size); + */ + { + char buf1[4] = { 1, 2, 3, 4 }; + char buf2[4] = { 1, 2, 3, 4 }; + + isc_safe_memwipe(buf1, sizeof(buf1)); + memset(buf2, 0, sizeof(buf2)); + + assert_int_equal(memcmp(buf1, buf2, sizeof(buf1)), 0); + } + + /* + * Boundary test. + */ + { + char buf1[4] = { 1, 2, 3, 4 }; + char buf2[4] = { 1, 2, 3, 4 }; + + /* + * We wipe 3 elements on purpose, keeping the 4th in + * place. + */ + isc_safe_memwipe(buf1, 3); + memset(buf2, 0, 3); + + assert_int_equal(memcmp(buf1, buf2, sizeof(buf1)), 0); + } +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(isc_safe_memequal_test), + cmocka_unit_test(isc_safe_memwipe_test), + }; + + 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 */ diff --git a/lib/isc/tests/siphash_test.c b/lib/isc/tests/siphash_test.c new file mode 100644 index 0000000..72c7126 --- /dev/null +++ b/lib/isc/tests/siphash_test.c @@ -0,0 +1,183 @@ +/* + * 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 <sched.h> +#include <setjmp.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdlib.h> + +#define UNIT_TESTING +#include <cmocka.h> + +#include <isc/siphash.h> + +#include "../siphash.c" + +const uint8_t vectors_sip64[64][8] = { + { 0x31, 0x0e, 0x0e, 0xdd, 0x47, 0xdb, 0x6f, 0x72 }, + { 0xfd, 0x67, 0xdc, 0x93, 0xc5, 0x39, 0xf8, 0x74 }, + { 0x5a, 0x4f, 0xa9, 0xd9, 0x09, 0x80, 0x6c, 0x0d }, + { 0x2d, 0x7e, 0xfb, 0xd7, 0x96, 0x66, 0x67, 0x85 }, + { 0xb7, 0x87, 0x71, 0x27, 0xe0, 0x94, 0x27, 0xcf }, + { 0x8d, 0xa6, 0x99, 0xcd, 0x64, 0x55, 0x76, 0x18 }, + { 0xce, 0xe3, 0xfe, 0x58, 0x6e, 0x46, 0xc9, 0xcb }, + { 0x37, 0xd1, 0x01, 0x8b, 0xf5, 0x00, 0x02, 0xab }, + { 0x62, 0x24, 0x93, 0x9a, 0x79, 0xf5, 0xf5, 0x93 }, + { 0xb0, 0xe4, 0xa9, 0x0b, 0xdf, 0x82, 0x00, 0x9e }, + { 0xf3, 0xb9, 0xdd, 0x94, 0xc5, 0xbb, 0x5d, 0x7a }, + { 0xa7, 0xad, 0x6b, 0x22, 0x46, 0x2f, 0xb3, 0xf4 }, + { 0xfb, 0xe5, 0x0e, 0x86, 0xbc, 0x8f, 0x1e, 0x75 }, + { 0x90, 0x3d, 0x84, 0xc0, 0x27, 0x56, 0xea, 0x14 }, + { 0xee, 0xf2, 0x7a, 0x8e, 0x90, 0xca, 0x23, 0xf7 }, + { 0xe5, 0x45, 0xbe, 0x49, 0x61, 0xca, 0x29, 0xa1 }, + { 0xdb, 0x9b, 0xc2, 0x57, 0x7f, 0xcc, 0x2a, 0x3f }, + { 0x94, 0x47, 0xbe, 0x2c, 0xf5, 0xe9, 0x9a, 0x69 }, + { 0x9c, 0xd3, 0x8d, 0x96, 0xf0, 0xb3, 0xc1, 0x4b }, + { 0xbd, 0x61, 0x79, 0xa7, 0x1d, 0xc9, 0x6d, 0xbb }, + { 0x98, 0xee, 0xa2, 0x1a, 0xf2, 0x5c, 0xd6, 0xbe }, + { 0xc7, 0x67, 0x3b, 0x2e, 0xb0, 0xcb, 0xf2, 0xd0 }, + { 0x88, 0x3e, 0xa3, 0xe3, 0x95, 0x67, 0x53, 0x93 }, + { 0xc8, 0xce, 0x5c, 0xcd, 0x8c, 0x03, 0x0c, 0xa8 }, + { 0x94, 0xaf, 0x49, 0xf6, 0xc6, 0x50, 0xad, 0xb8 }, + { 0xea, 0xb8, 0x85, 0x8a, 0xde, 0x92, 0xe1, 0xbc }, + { 0xf3, 0x15, 0xbb, 0x5b, 0xb8, 0x35, 0xd8, 0x17 }, + { 0xad, 0xcf, 0x6b, 0x07, 0x63, 0x61, 0x2e, 0x2f }, + { 0xa5, 0xc9, 0x1d, 0xa7, 0xac, 0xaa, 0x4d, 0xde }, + { 0x71, 0x65, 0x95, 0x87, 0x66, 0x50, 0xa2, 0xa6 }, + { 0x28, 0xef, 0x49, 0x5c, 0x53, 0xa3, 0x87, 0xad }, + { 0x42, 0xc3, 0x41, 0xd8, 0xfa, 0x92, 0xd8, 0x32 }, + { 0xce, 0x7c, 0xf2, 0x72, 0x2f, 0x51, 0x27, 0x71 }, + { 0xe3, 0x78, 0x59, 0xf9, 0x46, 0x23, 0xf3, 0xa7 }, + { 0x38, 0x12, 0x05, 0xbb, 0x1a, 0xb0, 0xe0, 0x12 }, + { 0xae, 0x97, 0xa1, 0x0f, 0xd4, 0x34, 0xe0, 0x15 }, + { 0xb4, 0xa3, 0x15, 0x08, 0xbe, 0xff, 0x4d, 0x31 }, + { 0x81, 0x39, 0x62, 0x29, 0xf0, 0x90, 0x79, 0x02 }, + { 0x4d, 0x0c, 0xf4, 0x9e, 0xe5, 0xd4, 0xdc, 0xca }, + { 0x5c, 0x73, 0x33, 0x6a, 0x76, 0xd8, 0xbf, 0x9a }, + { 0xd0, 0xa7, 0x04, 0x53, 0x6b, 0xa9, 0x3e, 0x0e }, + { 0x92, 0x59, 0x58, 0xfc, 0xd6, 0x42, 0x0c, 0xad }, + { 0xa9, 0x15, 0xc2, 0x9b, 0xc8, 0x06, 0x73, 0x18 }, + { 0x95, 0x2b, 0x79, 0xf3, 0xbc, 0x0a, 0xa6, 0xd4 }, + { 0xf2, 0x1d, 0xf2, 0xe4, 0x1d, 0x45, 0x35, 0xf9 }, + { 0x87, 0x57, 0x75, 0x19, 0x04, 0x8f, 0x53, 0xa9 }, + { 0x10, 0xa5, 0x6c, 0xf5, 0xdf, 0xcd, 0x9a, 0xdb }, + { 0xeb, 0x75, 0x09, 0x5c, 0xcd, 0x98, 0x6c, 0xd0 }, + { 0x51, 0xa9, 0xcb, 0x9e, 0xcb, 0xa3, 0x12, 0xe6 }, + { 0x96, 0xaf, 0xad, 0xfc, 0x2c, 0xe6, 0x66, 0xc7 }, + { 0x72, 0xfe, 0x52, 0x97, 0x5a, 0x43, 0x64, 0xee }, + { 0x5a, 0x16, 0x45, 0xb2, 0x76, 0xd5, 0x92, 0xa1 }, + { 0xb2, 0x74, 0xcb, 0x8e, 0xbf, 0x87, 0x87, 0x0a }, + { 0x6f, 0x9b, 0xb4, 0x20, 0x3d, 0xe7, 0xb3, 0x81 }, + { 0xea, 0xec, 0xb2, 0xa3, 0x0b, 0x22, 0xa8, 0x7f }, + { 0x99, 0x24, 0xa4, 0x3c, 0xc1, 0x31, 0x57, 0x24 }, + { 0xbd, 0x83, 0x8d, 0x3a, 0xaf, 0xbf, 0x8d, 0xb7 }, + { 0x0b, 0x1a, 0x2a, 0x32, 0x65, 0xd5, 0x1a, 0xea }, + { 0x13, 0x50, 0x79, 0xa3, 0x23, 0x1c, 0xe6, 0x60 }, + { 0x93, 0x2b, 0x28, 0x46, 0xe4, 0xd7, 0x06, 0x66 }, + { 0xe1, 0x91, 0x5f, 0x5c, 0xb1, 0xec, 0xa4, 0x6c }, + { 0xf3, 0x25, 0x96, 0x5c, 0xa1, 0x6d, 0x62, 0x9f }, + { 0x57, 0x5f, 0xf2, 0x8e, 0x60, 0x38, 0x1b, 0xe5 }, + { 0x72, 0x45, 0x06, 0xeb, 0x4c, 0x32, 0x8a, 0x95 } +}; + +const uint8_t vectors_hsip32[64][4] = { + { 0xa9, 0x35, 0x9f, 0x5b }, { 0x27, 0x47, 0x5a, 0xb8 }, + { 0xfa, 0x62, 0xa6, 0x03 }, { 0x8a, 0xfe, 0xe7, 0x04 }, + { 0x2a, 0x6e, 0x46, 0x89 }, { 0xc5, 0xfa, 0xb6, 0x69 }, + { 0x58, 0x63, 0xfc, 0x23 }, { 0x8b, 0xcf, 0x63, 0xc5 }, + { 0xd0, 0xb8, 0x84, 0x8f }, { 0xf8, 0x06, 0xe7, 0x79 }, + { 0x94, 0xb0, 0x79, 0x34 }, { 0x08, 0x08, 0x30, 0x50 }, + { 0x57, 0xf0, 0x87, 0x2f }, { 0x77, 0xe6, 0x63, 0xff }, + { 0xd6, 0xff, 0xf8, 0x7c }, { 0x74, 0xfe, 0x2b, 0x97 }, + { 0xd9, 0xb5, 0xac, 0x84 }, { 0xc4, 0x74, 0x64, 0x5b }, + { 0x46, 0x5b, 0x8d, 0x9b }, { 0x7b, 0xef, 0xe3, 0x87 }, + { 0xe3, 0x4d, 0x10, 0x45 }, { 0x61, 0x3f, 0x62, 0xb3 }, + { 0x70, 0xf3, 0x67, 0xfe }, { 0xe6, 0xad, 0xb8, 0xbd }, + { 0x27, 0x40, 0x0c, 0x63 }, { 0x26, 0x78, 0x78, 0x75 }, + { 0x4f, 0x56, 0x7b, 0x5f }, { 0x3a, 0xb0, 0xe6, 0x69 }, + { 0xb0, 0x64, 0x40, 0x00 }, { 0xff, 0x67, 0x0f, 0xb4 }, + { 0x50, 0x9e, 0x33, 0x8b }, { 0x5d, 0x58, 0x9f, 0x1a }, + { 0xfe, 0xe7, 0x21, 0x12 }, { 0x33, 0x75, 0x32, 0x59 }, + { 0x6a, 0x43, 0x4f, 0x8c }, { 0xfe, 0x28, 0xb7, 0x29 }, + { 0xe7, 0x5c, 0xc6, 0xec }, { 0x69, 0x7e, 0x8d, 0x54 }, + { 0x63, 0x68, 0x8b, 0x0f }, { 0x65, 0x0b, 0x62, 0xb4 }, + { 0xb6, 0xbc, 0x18, 0x40 }, { 0x5d, 0x07, 0x45, 0x05 }, + { 0x24, 0x42, 0xfd, 0x2e }, { 0x7b, 0xb7, 0x86, 0x3a }, + { 0x77, 0x05, 0xd5, 0x48 }, { 0xd7, 0x52, 0x08, 0xb1 }, + { 0xb6, 0xd4, 0x99, 0xc8 }, { 0x08, 0x92, 0x20, 0x2e }, + { 0x69, 0xe1, 0x2c, 0xe3 }, { 0x8d, 0xb5, 0x80, 0xe5 }, + { 0x36, 0x97, 0x64, 0xc6 }, { 0x01, 0x6e, 0x02, 0x04 }, + { 0x3b, 0x85, 0xf3, 0xd4 }, { 0xfe, 0xdb, 0x66, 0xbe }, + { 0x1e, 0x69, 0x2a, 0x3a }, { 0xc6, 0x89, 0x84, 0xc0 }, + { 0xa5, 0xc5, 0xb9, 0x40 }, { 0x9b, 0xe9, 0xe8, 0x8c }, + { 0x7d, 0xbc, 0x81, 0x40 }, { 0x7c, 0x07, 0x8e, 0xc5 }, + { 0xd4, 0xe7, 0x6c, 0x73 }, { 0x42, 0x8f, 0xcb, 0xb9 }, + { 0xbd, 0x83, 0x99, 0x7a }, { 0x59, 0xea, 0x4a, 0x74 } +}; + +static void +isc_siphash24_test(void **state) { + UNUSED(state); + + uint8_t in[64], out[8], key[16]; + for (size_t i = 0; i < ARRAY_SIZE(key); i++) { + key[i] = i; + } + + for (size_t i = 0; i < ARRAY_SIZE(in); i++) { + in[i] = i; + isc_siphash24(key, in, i, out); + assert_memory_equal(out, vectors_sip64[i], 8); + } +} + +static void +isc_halfsiphash24_test(void **state) { + UNUSED(state); + + uint8_t in[64], out[4], key[16]; + for (size_t i = 0; i < ARRAY_SIZE(key); i++) { + key[i] = i; + } + + for (size_t i = 0; i < ARRAY_SIZE(in); i++) { + in[i] = i; + isc_halfsiphash24(key, in, i, out); + assert_memory_equal(out, vectors_hsip32[i], 4); + } +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(isc_siphash24_test), + cmocka_unit_test(isc_halfsiphash24_test), + }; + + 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 */ diff --git a/lib/isc/tests/sockaddr_test.c b/lib/isc/tests/sockaddr_test.c new file mode 100644 index 0000000..6719a6e --- /dev/null +++ b/lib/isc/tests/sockaddr_test.c @@ -0,0 +1,188 @@ +/* + * 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 <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/netaddr.h> +#include <isc/print.h> +#include <isc/sockaddr.h> +#include <isc/util.h> + +#include "isctest.h" + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = isc_test_begin(NULL, true, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + isc_test_end(); + + return (0); +} + +/* test sockaddr hash */ +static void +sockaddr_hash(void **state) { + isc_sockaddr_t addr; + struct in_addr in; + struct in6_addr in6; + unsigned int h1, h2, h3, h4; + int ret; + + UNUSED(state); + + in.s_addr = inet_addr("127.0.0.1"); + isc_sockaddr_fromin(&addr, &in, 1); + h1 = isc_sockaddr_hash(&addr, true); + h2 = isc_sockaddr_hash(&addr, false); + assert_int_not_equal(h1, h2); + + ret = inet_pton(AF_INET6, "::ffff:127.0.0.1", &in6); + assert_int_equal(ret, 1); + isc_sockaddr_fromin6(&addr, &in6, 1); + h3 = isc_sockaddr_hash(&addr, true); + h4 = isc_sockaddr_hash(&addr, false); + assert_int_equal(h1, h3); + assert_int_equal(h2, h4); +} + +/* test isc_sockaddr_isnetzero() */ +static void +sockaddr_isnetzero(void **state) { + isc_sockaddr_t addr; + struct in_addr in; + struct in6_addr in6; + bool r; + int ret; + size_t i; + struct { + const char *string; + bool expect; + } data4[] = { + { "0.0.0.0", true }, { "0.0.0.1", true }, + { "0.0.1.0", true }, { "0.1.0.0", true }, + { "1.0.0.0", false }, { "0.0.0.127", true }, + { "0.0.0.255", true }, { "127.0.0.1", false }, + { "255.255.255.255", false }, + }; + /* + * Mapped addresses are currently not netzero. + */ + struct { + const char *string; + bool expect; + } data6[] = { + { "::ffff:0.0.0.0", false }, + { "::ffff:0.0.0.1", false }, + { "::ffff:0.0.0.127", false }, + { "::ffff:0.0.0.255", false }, + { "::ffff:127.0.0.1", false }, + { "::ffff:255.255.255.255", false }, + }; + + UNUSED(state); + + for (i = 0; i < sizeof(data4) / sizeof(data4[0]); i++) { + in.s_addr = inet_addr(data4[i].string); + isc_sockaddr_fromin(&addr, &in, 1); + r = isc_sockaddr_isnetzero(&addr); + assert_int_equal(r, data4[i].expect); + } + + for (i = 0; i < sizeof(data6) / sizeof(data6[0]); i++) { + ret = inet_pton(AF_INET6, data6[i].string, &in6); + assert_int_equal(ret, 1); + isc_sockaddr_fromin6(&addr, &in6, 1); + r = isc_sockaddr_isnetzero(&addr); + assert_int_equal(r, data6[i].expect); + } +} + +/* + * test that isc_sockaddr_eqaddrprefix() returns true when prefixes of a + * and b are equal, and false when they are not equal + */ +static void +sockaddr_eqaddrprefix(void **state) { + struct in_addr ina_a; + struct in_addr ina_b; + struct in_addr ina_c; + isc_sockaddr_t isa_a; + isc_sockaddr_t isa_b; + isc_sockaddr_t isa_c; + + UNUSED(state); + + assert_true(inet_pton(AF_INET, "194.100.32.87", &ina_a) >= 0); + assert_true(inet_pton(AF_INET, "194.100.32.80", &ina_b) >= 0); + assert_true(inet_pton(AF_INET, "194.101.32.87", &ina_c) >= 0); + + isc_sockaddr_fromin(&isa_a, &ina_a, 0); + isc_sockaddr_fromin(&isa_b, &ina_b, 42); + isc_sockaddr_fromin(&isa_c, &ina_c, 0); + + assert_true(isc_sockaddr_eqaddrprefix(&isa_a, &isa_b, 0)); + assert_true(isc_sockaddr_eqaddrprefix(&isa_a, &isa_b, 29)); + assert_true(isc_sockaddr_eqaddrprefix(&isa_a, &isa_c, 8)); + + assert_false(isc_sockaddr_eqaddrprefix(&isa_a, &isa_b, 30)); + assert_false(isc_sockaddr_eqaddrprefix(&isa_a, &isa_b, 32)); + assert_false(isc_sockaddr_eqaddrprefix(&isa_a, &isa_c, 16)); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(sockaddr_hash, _setup, + _teardown), + cmocka_unit_test(sockaddr_isnetzero), + cmocka_unit_test(sockaddr_eqaddrprefix), + }; + + 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 */ diff --git a/lib/isc/tests/socket_test.c b/lib/isc/tests/socket_test.c new file mode 100644 index 0000000..ab19b06 --- /dev/null +++ b/lib/isc/tests/socket_test.c @@ -0,0 +1,833 @@ +/* + * 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. + */ + +/*! \file */ + +#if HAVE_CMOCKA +#include <sched.h> /* IWYU pragma: keep */ +#include <setjmp.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> + +#define UNIT_TESTING +#include <cmocka.h> + +#include <isc/atomic.h> +#include <isc/platform.h> +#include <isc/print.h> +#include <isc/socket.h> +#include <isc/task.h> + +#include "../unix/socket_p.h" +#include "isctest.h" + +static bool recv_dscp; +static unsigned int recv_dscp_value; +static bool recv_trunc; +isc_socket_t *s1 = NULL, *s2 = NULL, *s3 = NULL; +isc_task_t *test_task = NULL; + +/* + * Helper functions + */ + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = isc_test_begin(NULL, true, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + if (s1 != NULL) { + isc_socket_detach(&s1); + } + if (s2 != NULL) { + isc_socket_detach(&s2); + } + if (s3 != NULL) { + isc_socket_detach(&s3); + } + if (test_task != NULL) { + isc_task_detach(&test_task); + } + + isc_test_end(); + + return (0); +} + +typedef struct { + atomic_bool done; + isc_result_t result; + isc_socket_t *socket; +} completion_t; + +static void +completion_init(completion_t *completion) { + atomic_init(&completion->done, false); + completion->socket = NULL; +} + +static void +accept_done(isc_task_t *task, isc_event_t *event) { + isc_socket_newconnev_t *nevent = (isc_socket_newconnev_t *)event; + completion_t *completion = event->ev_arg; + + UNUSED(task); + + completion->result = nevent->result; + atomic_store(&completion->done, true); + if (completion->result == ISC_R_SUCCESS) { + completion->socket = nevent->newsocket; + } + + isc_event_free(&event); +} + +static void +event_done(isc_task_t *task, isc_event_t *event) { + isc_socketevent_t *sev = NULL; + isc_socket_connev_t *connev = NULL; + completion_t *completion = event->ev_arg; + UNUSED(task); + + switch (event->ev_type) { + case ISC_SOCKEVENT_RECVDONE: + case ISC_SOCKEVENT_SENDDONE: + sev = (isc_socketevent_t *)event; + completion->result = sev->result; + if ((sev->attributes & ISC_SOCKEVENTATTR_DSCP) != 0) { + recv_dscp = true; + recv_dscp_value = sev->dscp; + } else { + recv_dscp = false; + } + recv_trunc = ((sev->attributes & ISC_SOCKEVENTATTR_TRUNC) != 0); + break; + case ISC_SOCKEVENT_CONNECT: + connev = (isc_socket_connev_t *)event; + completion->result = connev->result; + break; + default: + assert_false(true); + } + atomic_store(&completion->done, true); + isc_event_free(&event); +} + +static isc_result_t +waitfor(completion_t *completion) { + int i = 0; + while (!atomic_load(&completion->done) && i++ < 5000) { + isc_test_nap(1000); + } + if (atomic_load(&completion->done)) { + return (ISC_R_SUCCESS); + } + return (ISC_R_FAILURE); +} + +static void +waitbody(void) { + isc_test_nap(1000); +} + +static isc_result_t +waitfor2(completion_t *c1, completion_t *c2) { + int i = 0; + + while (!(atomic_load(&c1->done) && atomic_load(&c2->done)) && + i++ < 5000) + { + waitbody(); + } + if (atomic_load(&c1->done) && atomic_load(&c2->done)) { + return (ISC_R_SUCCESS); + } + return (ISC_R_FAILURE); +} + +/* + * Individual unit tests + */ + +/* Test UDP sendto/recv (IPv4) */ +static void +udp_sendto_test(void **state) { + isc_result_t result; + isc_sockaddr_t addr1, addr2; + struct in_addr in; + char sendbuf[BUFSIZ], recvbuf[BUFSIZ]; + completion_t completion; + isc_region_t r; + + UNUSED(state); + + in.s_addr = inet_addr("127.0.0.1"); + isc_sockaddr_fromin(&addr1, &in, 0); + isc_sockaddr_fromin(&addr2, &in, 0); + + result = isc_socket_create(socketmgr, PF_INET, isc_sockettype_udp, &s1); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_socket_bind(s1, &addr1, 0); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_socket_getsockname(s1, &addr1); + assert_int_equal(result, ISC_R_SUCCESS); + assert_true(isc_sockaddr_getport(&addr1) != 0); + + result = isc_socket_create(socketmgr, PF_INET, isc_sockettype_udp, &s2); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_socket_bind(s2, &addr2, 0); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_socket_getsockname(s2, &addr2); + assert_int_equal(result, ISC_R_SUCCESS); + assert_true(isc_sockaddr_getport(&addr2) != 0); + + result = isc_task_create(taskmgr, 0, &test_task); + assert_int_equal(result, ISC_R_SUCCESS); + + snprintf(sendbuf, sizeof(sendbuf), "Hello"); + r.base = (void *)sendbuf; + r.length = strlen(sendbuf) + 1; + + completion_init(&completion); + result = isc_socket_sendto(s1, &r, test_task, event_done, &completion, + &addr2, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + waitfor(&completion); + assert_true(atomic_load(&completion.done)); + assert_int_equal(completion.result, ISC_R_SUCCESS); + + r.base = (void *)recvbuf; + r.length = BUFSIZ; + completion_init(&completion); + result = isc_socket_recv(s2, &r, 1, test_task, event_done, &completion); + assert_int_equal(result, ISC_R_SUCCESS); + waitfor(&completion); + assert_true(atomic_load(&completion.done)); + assert_int_equal(completion.result, ISC_R_SUCCESS); + assert_string_equal(recvbuf, "Hello"); +} + +/* Test UDP sendto/recv with duplicated socket */ +static void +udp_dup_test(void **state) { + isc_result_t result; + isc_sockaddr_t addr1, addr2; + struct in_addr in; + char sendbuf[BUFSIZ], recvbuf[BUFSIZ]; + completion_t completion; + isc_region_t r; + + UNUSED(state); + + in.s_addr = inet_addr("127.0.0.1"); + isc_sockaddr_fromin(&addr1, &in, 0); + isc_sockaddr_fromin(&addr2, &in, 0); + + result = isc_socket_create(socketmgr, PF_INET, isc_sockettype_udp, &s1); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_socket_bind(s1, &addr1, 0); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_socket_getsockname(s1, &addr1); + assert_int_equal(result, ISC_R_SUCCESS); + assert_true(isc_sockaddr_getport(&addr1) != 0); + + result = isc_socket_create(socketmgr, PF_INET, isc_sockettype_udp, &s2); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_socket_bind(s2, &addr2, 0); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_socket_getsockname(s2, &addr2); + assert_int_equal(result, ISC_R_SUCCESS); + assert_true(isc_sockaddr_getport(&addr2) != 0); + + result = isc_socket_dup(s2, &s3); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_task_create(taskmgr, 0, &test_task); + assert_int_equal(result, ISC_R_SUCCESS); + + snprintf(sendbuf, sizeof(sendbuf), "Hello"); + r.base = (void *)sendbuf; + r.length = strlen(sendbuf) + 1; + + completion_init(&completion); + result = isc_socket_sendto(s1, &r, test_task, event_done, &completion, + &addr2, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + waitfor(&completion); + assert_true(atomic_load(&completion.done)); + assert_int_equal(completion.result, ISC_R_SUCCESS); + + snprintf(sendbuf, sizeof(sendbuf), "World"); + r.base = (void *)sendbuf; + r.length = strlen(sendbuf) + 1; + + completion_init(&completion); + result = isc_socket_sendto(s1, &r, test_task, event_done, &completion, + &addr2, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + waitfor(&completion); + assert_true(atomic_load(&completion.done)); + assert_int_equal(completion.result, ISC_R_SUCCESS); + + r.base = (void *)recvbuf; + r.length = BUFSIZ; + completion_init(&completion); + result = isc_socket_recv(s2, &r, 1, test_task, event_done, &completion); + assert_int_equal(result, ISC_R_SUCCESS); + waitfor(&completion); + assert_true(atomic_load(&completion.done)); + assert_int_equal(completion.result, ISC_R_SUCCESS); + assert_string_equal(recvbuf, "Hello"); + + r.base = (void *)recvbuf; + r.length = BUFSIZ; + completion_init(&completion); + result = isc_socket_recv(s3, &r, 1, test_task, event_done, &completion); + assert_int_equal(result, ISC_R_SUCCESS); + waitfor(&completion); + assert_true(atomic_load(&completion.done)); + assert_int_equal(completion.result, ISC_R_SUCCESS); + assert_string_equal(recvbuf, "World"); +} + +/* Test UDP sendto/recv (IPv4) */ +static void +udp_dscp_v4_test(void **state) { + isc_result_t result; + isc_sockaddr_t addr1, addr2; + struct in_addr in; + char sendbuf[BUFSIZ], recvbuf[BUFSIZ]; + completion_t completion; + isc_region_t r; + isc_socketevent_t *socketevent; + + UNUSED(state); + + in.s_addr = inet_addr("127.0.0.1"); + isc_sockaddr_fromin(&addr1, &in, 0); + isc_sockaddr_fromin(&addr2, &in, 0); + + result = isc_socket_create(socketmgr, PF_INET, isc_sockettype_udp, &s1); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_socket_bind(s1, &addr1, ISC_SOCKET_REUSEADDRESS); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_socket_getsockname(s1, &addr1); + assert_int_equal(result, ISC_R_SUCCESS); + assert_true(isc_sockaddr_getport(&addr1) != 0); + + result = isc_socket_create(socketmgr, PF_INET, isc_sockettype_udp, &s2); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_socket_bind(s2, &addr2, ISC_SOCKET_REUSEADDRESS); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_socket_getsockname(s2, &addr2); + assert_int_equal(result, ISC_R_SUCCESS); + assert_true(isc_sockaddr_getport(&addr2) != 0); + + result = isc_task_create(taskmgr, 0, &test_task); + assert_int_equal(result, ISC_R_SUCCESS); + + snprintf(sendbuf, sizeof(sendbuf), "Hello"); + r.base = (void *)sendbuf; + r.length = strlen(sendbuf) + 1; + + completion_init(&completion); + + socketevent = isc_socket_socketevent( + test_mctx, s1, ISC_SOCKEVENT_SENDDONE, event_done, &completion); + assert_non_null(socketevent); + + if ((isc_net_probedscp() & ISC_NET_DSCPPKTV4) != 0) { + socketevent->dscp = 056; /* EF */ + socketevent->attributes |= ISC_SOCKEVENTATTR_DSCP; + } else if ((isc_net_probedscp() & ISC_NET_DSCPSETV4) != 0) { + isc_socket_dscp(s1, 056); /* EF */ + socketevent->dscp = 0; + socketevent->attributes &= ~ISC_SOCKEVENTATTR_DSCP; + } + + recv_dscp = false; + recv_dscp_value = 0; + + result = isc_socket_sendto2(s1, &r, test_task, &addr2, NULL, + socketevent, 0); + assert_int_equal(result, ISC_R_SUCCESS); + waitfor(&completion); + assert_true(atomic_load(&completion.done)); + assert_int_equal(completion.result, ISC_R_SUCCESS); + + r.base = (void *)recvbuf; + r.length = BUFSIZ; + completion_init(&completion); + result = isc_socket_recv(s2, &r, 1, test_task, event_done, &completion); + assert_int_equal(result, ISC_R_SUCCESS); + waitfor(&completion); + assert_true(atomic_load(&completion.done)); + assert_int_equal(completion.result, ISC_R_SUCCESS); + assert_string_equal(recvbuf, "Hello"); + + if ((isc_net_probedscp() & ISC_NET_DSCPRECVV4) != 0) { + assert_true(recv_dscp); + assert_int_equal(recv_dscp_value, 056); + } else { + assert_false(recv_dscp); + } +} + +/* Test UDP sendto/recv (IPv6) */ +static void +udp_dscp_v6_test(void **state) { + isc_result_t result; + isc_sockaddr_t addr1, addr2; + struct in6_addr in6; + char sendbuf[BUFSIZ], recvbuf[BUFSIZ]; + completion_t completion; + isc_region_t r; + isc_socketevent_t *socketevent; + int n; + + UNUSED(state); + + n = inet_pton(AF_INET6, "::1", &in6.s6_addr); + assert_true(n == 1); + isc_sockaddr_fromin6(&addr1, &in6, 0); + isc_sockaddr_fromin6(&addr2, &in6, 0); + + result = isc_socket_create(socketmgr, PF_INET6, isc_sockettype_udp, + &s1); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_socket_bind(s1, &addr1, 0); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_socket_getsockname(s1, &addr1); + assert_int_equal(result, ISC_R_SUCCESS); + assert_true(isc_sockaddr_getport(&addr1) != 0); + + result = isc_socket_create(socketmgr, PF_INET6, isc_sockettype_udp, + &s2); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_socket_bind(s2, &addr2, 0); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_socket_getsockname(s2, &addr2); + assert_int_equal(result, ISC_R_SUCCESS); + assert_true(isc_sockaddr_getport(&addr2) != 0); + + result = isc_task_create(taskmgr, 0, &test_task); + assert_int_equal(result, ISC_R_SUCCESS); + + snprintf(sendbuf, sizeof(sendbuf), "Hello"); + r.base = (void *)sendbuf; + r.length = strlen(sendbuf) + 1; + + completion_init(&completion); + + socketevent = isc_socket_socketevent( + test_mctx, s1, ISC_SOCKEVENT_SENDDONE, event_done, &completion); + assert_non_null(socketevent); + + if ((isc_net_probedscp() & ISC_NET_DSCPPKTV6) != 0) { + socketevent->dscp = 056; /* EF */ + socketevent->attributes = ISC_SOCKEVENTATTR_DSCP; + } else if ((isc_net_probedscp() & ISC_NET_DSCPSETV6) != 0) { + isc_socket_dscp(s1, 056); /* EF */ + } + + recv_dscp = false; + recv_dscp_value = 0; + + result = isc_socket_sendto2(s1, &r, test_task, &addr2, NULL, + socketevent, 0); + assert_int_equal(result, ISC_R_SUCCESS); + waitfor(&completion); + assert_true(atomic_load(&completion.done)); + assert_int_equal(completion.result, ISC_R_SUCCESS); + + r.base = (void *)recvbuf; + r.length = BUFSIZ; + completion_init(&completion); + result = isc_socket_recv(s2, &r, 1, test_task, event_done, &completion); + assert_int_equal(result, ISC_R_SUCCESS); + waitfor(&completion); + assert_true(atomic_load(&completion.done)); + assert_int_equal(completion.result, ISC_R_SUCCESS); + assert_string_equal(recvbuf, "Hello"); + if ((isc_net_probedscp() & ISC_NET_DSCPRECVV6) != 0) { + assert_true(recv_dscp); + assert_int_equal(recv_dscp_value, 056); + } else { + assert_false(recv_dscp); + } +} + +/* Test TCP sendto/recv (IPv4) */ +static void +tcp_dscp_v4_test(void **state) { + isc_result_t result; + isc_sockaddr_t addr1; + struct in_addr in; + char sendbuf[BUFSIZ], recvbuf[BUFSIZ]; + completion_t completion, completion2; + isc_region_t r; + + UNUSED(state); + + in.s_addr = inet_addr("127.0.0.1"); + isc_sockaddr_fromin(&addr1, &in, 0); + + result = isc_socket_create(socketmgr, PF_INET, isc_sockettype_tcp, &s1); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_socket_bind(s1, &addr1, 0); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_socket_getsockname(s1, &addr1); + assert_int_equal(result, ISC_R_SUCCESS); + assert_true(isc_sockaddr_getport(&addr1) != 0); + + result = isc_socket_listen(s1, 3); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_socket_create(socketmgr, PF_INET, isc_sockettype_tcp, &s2); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_task_create(taskmgr, 0, &test_task); + assert_int_equal(result, ISC_R_SUCCESS); + + completion_init(&completion2); + result = isc_socket_accept(s1, test_task, accept_done, &completion2); + assert_int_equal(result, ISC_R_SUCCESS); + + completion_init(&completion); + result = isc_socket_connect(s2, &addr1, test_task, event_done, + &completion); + assert_int_equal(result, ISC_R_SUCCESS); + waitfor2(&completion, &completion2); + assert_true(atomic_load(&completion.done)); + assert_int_equal(completion.result, ISC_R_SUCCESS); + assert_true(atomic_load(&completion2.done)); + assert_int_equal(completion2.result, ISC_R_SUCCESS); + s3 = completion2.socket; + + isc_socket_dscp(s2, 056); /* EF */ + + snprintf(sendbuf, sizeof(sendbuf), "Hello"); + r.base = (void *)sendbuf; + r.length = strlen(sendbuf) + 1; + + recv_dscp = false; + recv_dscp_value = 0; + + completion_init(&completion); + result = isc_socket_sendto(s2, &r, test_task, event_done, &completion, + NULL, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + waitfor(&completion); + assert_true(atomic_load(&completion.done)); + assert_int_equal(completion.result, ISC_R_SUCCESS); + + r.base = (void *)recvbuf; + r.length = BUFSIZ; + completion_init(&completion); + result = isc_socket_recv(s3, &r, 1, test_task, event_done, &completion); + assert_int_equal(result, ISC_R_SUCCESS); + waitfor(&completion); + assert_true(atomic_load(&completion.done)); + assert_int_equal(completion.result, ISC_R_SUCCESS); + assert_string_equal(recvbuf, "Hello"); + + if ((isc_net_probedscp() & ISC_NET_DSCPRECVV4) != 0) { + if (recv_dscp) { + assert_int_equal(recv_dscp_value, 056); + } + } else { + assert_false(recv_dscp); + } +} + +/* Test TCP sendto/recv (IPv6) */ +static void +tcp_dscp_v6_test(void **state) { + isc_result_t result; + isc_sockaddr_t addr1; + struct in6_addr in6; + char sendbuf[BUFSIZ], recvbuf[BUFSIZ]; + completion_t completion, completion2; + isc_region_t r; + int n; + + UNUSED(state); + + n = inet_pton(AF_INET6, "::1", &in6.s6_addr); + assert_true(n == 1); + isc_sockaddr_fromin6(&addr1, &in6, 0); + + result = isc_socket_create(socketmgr, PF_INET6, isc_sockettype_tcp, + &s1); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_socket_bind(s1, &addr1, 0); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_socket_getsockname(s1, &addr1); + assert_int_equal(result, ISC_R_SUCCESS); + assert_true(isc_sockaddr_getport(&addr1) != 0); + + result = isc_socket_listen(s1, 3); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_socket_create(socketmgr, PF_INET6, isc_sockettype_tcp, + &s2); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_task_create(taskmgr, 0, &test_task); + assert_int_equal(result, ISC_R_SUCCESS); + + completion_init(&completion2); + result = isc_socket_accept(s1, test_task, accept_done, &completion2); + assert_int_equal(result, ISC_R_SUCCESS); + + completion_init(&completion); + result = isc_socket_connect(s2, &addr1, test_task, event_done, + &completion); + assert_int_equal(result, ISC_R_SUCCESS); + waitfor2(&completion, &completion2); + assert_true(atomic_load(&completion.done)); + assert_int_equal(completion.result, ISC_R_SUCCESS); + assert_true(atomic_load(&completion2.done)); + assert_int_equal(completion2.result, ISC_R_SUCCESS); + s3 = completion2.socket; + + isc_socket_dscp(s2, 056); /* EF */ + + snprintf(sendbuf, sizeof(sendbuf), "Hello"); + r.base = (void *)sendbuf; + r.length = strlen(sendbuf) + 1; + + recv_dscp = false; + recv_dscp_value = 0; + + completion_init(&completion); + result = isc_socket_sendto(s2, &r, test_task, event_done, &completion, + NULL, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + waitfor(&completion); + assert_true(atomic_load(&completion.done)); + assert_int_equal(completion.result, ISC_R_SUCCESS); + + r.base = (void *)recvbuf; + r.length = BUFSIZ; + completion_init(&completion); + result = isc_socket_recv(s3, &r, 1, test_task, event_done, &completion); + assert_int_equal(result, ISC_R_SUCCESS); + waitfor(&completion); + assert_true(atomic_load(&completion.done)); + assert_int_equal(completion.result, ISC_R_SUCCESS); + assert_string_equal(recvbuf, "Hello"); + + if ((isc_net_probedscp() & ISC_NET_DSCPRECVV6) != 0) { + /* + * IPV6_RECVTCLASS is undefined for TCP however + * if we do get it it should be the value we set. + */ + if (recv_dscp) { + assert_int_equal(recv_dscp_value, 056); + } + } else { + assert_false(recv_dscp); + } +} + +/* probe dscp capabilities */ +static void +net_probedscp_test(void **state) { + unsigned int n; + + UNUSED(state); + + n = isc_net_probedscp(); + assert_true((n & ~ISC_NET_DSCPALL) == 0); + + /* ISC_NET_DSCPSETV4 MUST be set if any is set. */ + if (n & (ISC_NET_DSCPPKTV4 | ISC_NET_DSCPRECVV4)) { + assert_true((n & ISC_NET_DSCPSETV4) != 0); + } + + /* ISC_NET_DSCPSETV6 MUST be set if any is set. */ + if (n & (ISC_NET_DSCPPKTV6 | ISC_NET_DSCPRECVV6)) { + assert_true((n & ISC_NET_DSCPSETV6) != 0); + } + +#if 0 + fprintf(stdout,"IPv4:%s%s%s\n", + (n & ISC_NET_DSCPSETV4) ? " set" : "none", + (n & ISC_NET_DSCPPKTV4) ? " packet" : "", + (n & ISC_NET_DSCPRECVV4) ? " receive" : ""); + + fprintf(stdout,"IPv6:%s%s%s\n", + (n & ISC_NET_DSCPSETV6) ? " set" : "none", + (n & ISC_NET_DSCPPKTV6) ? " packet" : "", + (n & ISC_NET_DSCPRECVV6) ? " receive" : ""); +#endif /* if 0 */ +} + +/* Test UDP truncation detection */ +static void +udp_trunc_test(void **state) { + isc_result_t result; + isc_sockaddr_t addr1, addr2; + struct in_addr in; + char sendbuf[BUFSIZ * 2], recvbuf[BUFSIZ]; + completion_t completion; + isc_region_t r; + isc_socketevent_t *socketevent; + + UNUSED(state); + + in.s_addr = inet_addr("127.0.0.1"); + isc_sockaddr_fromin(&addr1, &in, 0); + isc_sockaddr_fromin(&addr2, &in, 0); + + result = isc_socket_create(socketmgr, PF_INET, isc_sockettype_udp, &s1); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_socket_bind(s1, &addr1, ISC_SOCKET_REUSEADDRESS); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_socket_getsockname(s1, &addr1); + assert_int_equal(result, ISC_R_SUCCESS); + assert_true(isc_sockaddr_getport(&addr1) != 0); + result = isc_socket_create(socketmgr, PF_INET, isc_sockettype_udp, &s2); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_socket_bind(s2, &addr2, ISC_SOCKET_REUSEADDRESS); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_socket_getsockname(s2, &addr2); + assert_int_equal(result, ISC_R_SUCCESS); + assert_true(isc_sockaddr_getport(&addr2) != 0); + + result = isc_task_create(taskmgr, 0, &test_task); + assert_int_equal(result, ISC_R_SUCCESS); + + /* + * Send a message that will not be truncated. + */ + memset(sendbuf, 0xff, sizeof(sendbuf)); + snprintf(sendbuf, sizeof(sendbuf), "Hello"); + r.base = (void *)sendbuf; + r.length = strlen(sendbuf) + 1; + + completion_init(&completion); + + socketevent = isc_socket_socketevent( + test_mctx, s1, ISC_SOCKEVENT_SENDDONE, event_done, &completion); + assert_non_null(socketevent); + + result = isc_socket_sendto2(s1, &r, test_task, &addr2, NULL, + socketevent, 0); + assert_int_equal(result, ISC_R_SUCCESS); + waitfor(&completion); + assert_true(atomic_load(&completion.done)); + assert_int_equal(completion.result, ISC_R_SUCCESS); + + r.base = (void *)recvbuf; + r.length = BUFSIZ; + completion_init(&completion); + recv_trunc = false; + result = isc_socket_recv(s2, &r, 1, test_task, event_done, &completion); + assert_int_equal(result, ISC_R_SUCCESS); + waitfor(&completion); + assert_true(atomic_load(&completion.done)); + assert_int_equal(completion.result, ISC_R_SUCCESS); + assert_string_equal(recvbuf, "Hello"); + assert_false(recv_trunc); + + /* + * Send a message that will be truncated. + */ + memset(sendbuf, 0xff, sizeof(sendbuf)); + snprintf(sendbuf, sizeof(sendbuf), "Hello"); + r.base = (void *)sendbuf; + r.length = sizeof(sendbuf); + + completion_init(&completion); + + socketevent = isc_socket_socketevent( + test_mctx, s1, ISC_SOCKEVENT_SENDDONE, event_done, &completion); + assert_non_null(socketevent); + + result = isc_socket_sendto2(s1, &r, test_task, &addr2, NULL, + socketevent, 0); + assert_int_equal(result, ISC_R_SUCCESS); + waitfor(&completion); + assert_true(atomic_load(&completion.done)); + assert_int_equal(completion.result, ISC_R_SUCCESS); + + r.base = (void *)recvbuf; + r.length = BUFSIZ; + completion_init(&completion); + recv_trunc = false; + result = isc_socket_recv(s2, &r, 1, test_task, event_done, &completion); + assert_int_equal(result, ISC_R_SUCCESS); + waitfor(&completion); + assert_true(atomic_load(&completion.done)); + assert_int_equal(completion.result, ISC_R_SUCCESS); + assert_string_equal(recvbuf, "Hello"); + assert_true(recv_trunc); +} + +/* + * Main + */ +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(udp_sendto_test, _setup, + _teardown), + cmocka_unit_test_setup_teardown(udp_dup_test, _setup, + _teardown), + cmocka_unit_test_setup_teardown(tcp_dscp_v4_test, _setup, + _teardown), + cmocka_unit_test_setup_teardown(tcp_dscp_v6_test, _setup, + _teardown), + cmocka_unit_test_setup_teardown(udp_dscp_v4_test, _setup, + _teardown), + cmocka_unit_test_setup_teardown(udp_dscp_v6_test, _setup, + _teardown), + cmocka_unit_test_setup_teardown(net_probedscp_test, _setup, + _teardown), + cmocka_unit_test_setup_teardown(udp_trunc_test, _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 */ diff --git a/lib/isc/tests/stats_test.c b/lib/isc/tests/stats_test.c new file mode 100644 index 0000000..89b2462 --- /dev/null +++ b/lib/isc/tests/stats_test.c @@ -0,0 +1,141 @@ +/* + * 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 <sched.h> /* IWYU pragma: keep */ +#include <setjmp.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +#define UNIT_TESTING +#include <cmocka.h> + +#include <isc/mem.h> +#include <isc/result.h> +#include <isc/stats.h> +#include <isc/util.h> + +#include "isctest.h" + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = isc_test_begin(NULL, true, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + isc_test_end(); + + return (0); +} + +/* test stats */ +static void +isc_stats_basic_test(void **state) { + isc_stats_t *stats = NULL; + isc_result_t result; + + UNUSED(state); + + result = isc_stats_create(test_mctx, &stats, 4); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(isc_stats_ncounters(stats), 4); + + /* Default all 0. */ + for (int i = 0; i < isc_stats_ncounters(stats); i++) { + assert_int_equal(isc_stats_get_counter(stats, i), 0); + } + + /* Test increment. */ + for (int i = 0; i < isc_stats_ncounters(stats); i++) { + isc_stats_increment(stats, i); + assert_int_equal(isc_stats_get_counter(stats, i), 1); + isc_stats_increment(stats, i); + assert_int_equal(isc_stats_get_counter(stats, i), 2); + } + + /* Test decrement. */ + for (int i = 0; i < isc_stats_ncounters(stats); i++) { + isc_stats_decrement(stats, i); + assert_int_equal(isc_stats_get_counter(stats, i), 1); + isc_stats_decrement(stats, i); + assert_int_equal(isc_stats_get_counter(stats, i), 0); + } + + /* Test set. */ + for (int i = 0; i < isc_stats_ncounters(stats); i++) { + isc_stats_set(stats, i, i); + assert_int_equal(isc_stats_get_counter(stats, i), i); + } + + /* Test update if greater. */ + for (int i = 0; i < isc_stats_ncounters(stats); i++) { + isc_stats_update_if_greater(stats, i, i); + assert_int_equal(isc_stats_get_counter(stats, i), i); + isc_stats_update_if_greater(stats, i, i + 1); + assert_int_equal(isc_stats_get_counter(stats, i), i + 1); + } + + /* Test resize. */ + isc_stats_resize(&stats, 3); + assert_int_equal(isc_stats_ncounters(stats), 4); + isc_stats_resize(&stats, 4); + assert_int_equal(isc_stats_ncounters(stats), 4); + isc_stats_resize(&stats, 5); + assert_int_equal(isc_stats_ncounters(stats), 5); + + /* Existing counters are retained */ + for (int i = 0; i < isc_stats_ncounters(stats); i++) { + uint32_t expect = i + 1; + if (i == 4) { + expect = 0; + } + assert_int_equal(isc_stats_get_counter(stats, i), expect); + } + + isc_stats_detach(&stats); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(isc_stats_basic_test, _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 */ diff --git a/lib/isc/tests/symtab_test.c b/lib/isc/tests/symtab_test.c new file mode 100644 index 0000000..4b82308 --- /dev/null +++ b/lib/isc/tests/symtab_test.c @@ -0,0 +1,171 @@ +/* + * 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 <sched.h> /* IWYU pragma: keep */ +#include <setjmp.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define UNIT_TESTING +#include <cmocka.h> + +#include <isc/print.h> +#include <isc/symtab.h> +#include <isc/util.h> + +#include "isctest.h" + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = isc_test_begin(NULL, true, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + isc_test_end(); + + return (0); +} + +static void +undefine(char *key, unsigned int type, isc_symvalue_t value, void *arg) { + UNUSED(arg); + + assert_int_equal(type, 1); + isc_mem_free(test_mctx, key); + isc_mem_free(test_mctx, value.as_pointer); +} + +/* test symbol table growth */ +static void +symtab_grow(void **state) { + isc_result_t result; + isc_symtab_t *st = NULL; + isc_symvalue_t value; + isc_symexists_t policy = isc_symexists_reject; + int i; + + UNUSED(state); + + result = isc_symtab_create(test_mctx, 3, undefine, NULL, false, &st); + assert_int_equal(result, ISC_R_SUCCESS); + assert_non_null(st); + + /* Nothing should be in the table yet */ + + /* + * Put 1024 entries in the table (this should necessate + * regrowing the hash table several times + */ + for (i = 0; i < 1024; i++) { + char str[16], *key; + + snprintf(str, sizeof(str), "%04x", i); + key = isc_mem_strdup(test_mctx, str); + assert_non_null(key); + value.as_pointer = isc_mem_strdup(test_mctx, str); + assert_non_null(value.as_pointer); + result = isc_symtab_define(st, key, 1, value, policy); + assert_int_equal(result, ISC_R_SUCCESS); + if (result != ISC_R_SUCCESS) { + undefine(key, 1, value, NULL); + } + } + + /* + * Try to put them in again; this should fail + */ + for (i = 0; i < 1024; i++) { + char str[16], *key; + + snprintf(str, sizeof(str), "%04x", i); + key = isc_mem_strdup(test_mctx, str); + assert_non_null(key); + value.as_pointer = isc_mem_strdup(test_mctx, str); + assert_non_null(value.as_pointer); + result = isc_symtab_define(st, key, 1, value, policy); + assert_int_equal(result, ISC_R_EXISTS); + undefine(key, 1, value, NULL); + } + + /* + * Retrieve them; this should succeed + */ + for (i = 0; i < 1024; i++) { + char str[16]; + + snprintf(str, sizeof(str), "%04x", i); + result = isc_symtab_lookup(st, str, 0, &value); + assert_int_equal(result, ISC_R_SUCCESS); + assert_string_equal(str, (char *)value.as_pointer); + } + + /* + * Undefine them + */ + for (i = 0; i < 1024; i++) { + char str[16]; + + snprintf(str, sizeof(str), "%04x", i); + result = isc_symtab_undefine(st, str, 1); + assert_int_equal(result, ISC_R_SUCCESS); + } + + /* + * Retrieve them again; this should fail + */ + for (i = 0; i < 1024; i++) { + char str[16]; + + snprintf(str, sizeof(str), "%04x", i); + result = isc_symtab_lookup(st, str, 0, &value); + assert_int_equal(result, ISC_R_NOTFOUND); + } + + isc_symtab_destroy(&st); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(symtab_grow, _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 */ diff --git a/lib/isc/tests/task_test.c b/lib/isc/tests/task_test.c new file mode 100644 index 0000000..7d7132d --- /dev/null +++ b/lib/isc/tests/task_test.c @@ -0,0 +1,1595 @@ +/* + * 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/atomic.h> +#include <isc/cmocka.h> +#include <isc/commandline.h> +#include <isc/condition.h> +#include <isc/managers.h> +#include <isc/mem.h> +#include <isc/platform.h> +#include <isc/print.h> +#include <isc/task.h> +#include <isc/time.h> +#include <isc/timer.h> +#include <isc/util.h> + +#include "isctest.h" + +/* Set to true (or use -v option) for verbose output */ +static bool verbose = false; + +static isc_mutex_t lock; +static isc_condition_t cv; + +atomic_int_fast32_t counter; +static int active[10]; +static atomic_bool done, done2; + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + isc_mutex_init(&lock); + + isc_condition_init(&cv); + + result = isc_test_begin(NULL, true, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_setup2(void **state) { + isc_result_t result; + + UNUSED(state); + + isc_mutex_init(&lock); + + isc_condition_init(&cv); + + /* Two worker threads */ + result = isc_test_begin(NULL, true, 2); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_setup4(void **state) { + isc_result_t result; + + UNUSED(state); + + isc_mutex_init(&lock); + + isc_condition_init(&cv); + + /* Four worker threads */ + result = isc_test_begin(NULL, true, 4); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + isc_test_end(); + isc_condition_destroy(&cv); + + return (0); +} + +static void +set(isc_task_t *task, isc_event_t *event) { + atomic_int_fast32_t *value = (atomic_int_fast32_t *)event->ev_arg; + + UNUSED(task); + + isc_event_free(&event); + atomic_store(value, atomic_fetch_add(&counter, 1)); +} + +#include <isc/thread.h> + +static void +set_and_drop(isc_task_t *task, isc_event_t *event) { + atomic_int_fast32_t *value = (atomic_int_fast32_t *)event->ev_arg; + + UNUSED(task); + + isc_event_free(&event); + LOCK(&lock); + atomic_store(value, atomic_fetch_add(&counter, 1)); + UNLOCK(&lock); +} + +/* Create a task */ +static void +create_task(void **state) { + isc_result_t result; + isc_task_t *task = NULL; + + UNUSED(state); + + result = isc_task_create(taskmgr, 0, &task); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_task_destroy(&task); + assert_null(task); +} + +/* Process events */ +static void +all_events(void **state) { + isc_result_t result; + isc_task_t *task = NULL; + isc_event_t *event = NULL; + atomic_int_fast32_t a, b; + int i = 0; + + UNUSED(state); + + atomic_init(&counter, 1); + atomic_init(&a, 0); + atomic_init(&b, 0); + + result = isc_task_create(taskmgr, 0, &task); + assert_int_equal(result, ISC_R_SUCCESS); + + /* First event */ + event = isc_event_allocate(test_mctx, task, ISC_TASKEVENT_TEST, set, &a, + sizeof(isc_event_t)); + assert_non_null(event); + + assert_int_equal(atomic_load(&a), 0); + isc_task_send(task, &event); + + event = isc_event_allocate(test_mctx, task, ISC_TASKEVENT_TEST, set, &b, + sizeof(isc_event_t)); + assert_non_null(event); + + assert_int_equal(atomic_load(&b), 0); + isc_task_send(task, &event); + + while ((atomic_load(&a) == 0 || atomic_load(&b) == 0) && i++ < 5000) { + isc_test_nap(1000); + } + + assert_int_not_equal(atomic_load(&a), 0); + assert_int_not_equal(atomic_load(&b), 0); + + isc_task_destroy(&task); + assert_null(task); +} + +/* Privileged events */ +static void +privileged_events(void **state) { + isc_result_t result; + isc_task_t *task1 = NULL, *task2 = NULL; + isc_event_t *event = NULL; + atomic_int_fast32_t a, b, c, d, e; + int i = 0; + + UNUSED(state); + + atomic_init(&counter, 1); + atomic_init(&a, -1); + atomic_init(&b, -1); + atomic_init(&c, -1); + atomic_init(&d, -1); + atomic_init(&e, -1); + + /* + * Pause the net/task manager so we can fill up the work + * queue without things happening while we do it. + */ + isc_nm_pause(netmgr); + isc_taskmgr_setmode(taskmgr, isc_taskmgrmode_privileged); + + result = isc_task_create(taskmgr, 0, &task1); + assert_int_equal(result, ISC_R_SUCCESS); + isc_task_setname(task1, "privileged", NULL); + assert_false(isc_task_getprivilege(task1)); + isc_task_setprivilege(task1, true); + assert_true(isc_task_getprivilege(task1)); + + result = isc_task_create(taskmgr, 0, &task2); + assert_int_equal(result, ISC_R_SUCCESS); + isc_task_setname(task2, "normal", NULL); + assert_false(isc_task_getprivilege(task2)); + + /* First event: privileged */ + event = isc_event_allocate(test_mctx, task1, ISC_TASKEVENT_TEST, set, + &a, sizeof(isc_event_t)); + assert_non_null(event); + + assert_int_equal(atomic_load(&a), -1); + isc_task_send(task1, &event); + + /* Second event: not privileged */ + event = isc_event_allocate(test_mctx, task2, ISC_TASKEVENT_TEST, set, + &b, sizeof(isc_event_t)); + assert_non_null(event); + + assert_int_equal(atomic_load(&b), -1); + isc_task_send(task2, &event); + + /* Third event: privileged */ + event = isc_event_allocate(test_mctx, task1, ISC_TASKEVENT_TEST, set, + &c, sizeof(isc_event_t)); + assert_non_null(event); + + assert_int_equal(atomic_load(&c), -1); + isc_task_send(task1, &event); + + /* Fourth event: privileged */ + event = isc_event_allocate(test_mctx, task1, ISC_TASKEVENT_TEST, set, + &d, sizeof(isc_event_t)); + assert_non_null(event); + + assert_int_equal(atomic_load(&d), -1); + isc_task_send(task1, &event); + + /* Fifth event: not privileged */ + event = isc_event_allocate(test_mctx, task2, ISC_TASKEVENT_TEST, set, + &e, sizeof(isc_event_t)); + assert_non_null(event); + + assert_int_equal(atomic_load(&e), -1); + isc_task_send(task2, &event); + + isc_nm_resume(netmgr); + + /* We're waiting for *all* variables to be set */ + while ((atomic_load(&a) < 0 || atomic_load(&b) < 0 || + atomic_load(&c) < 0 || atomic_load(&d) < 0 || + atomic_load(&e) < 0) && + i++ < 5000) + { + isc_test_nap(1000); + } + + /* + * We can't guarantee what order the events fire, but + * we do know the privileged tasks that set a, c, and d + * would have fired first. + */ + assert_true(atomic_load(&a) <= 3); + assert_true(atomic_load(&c) <= 3); + assert_true(atomic_load(&d) <= 3); + + /* ...and the non-privileged tasks that set b and e, last */ + assert_true(atomic_load(&b) > 3); + assert_true(atomic_load(&e) > 3); + + assert_int_equal(atomic_load(&counter), 6); + + isc_task_setprivilege(task1, false); + assert_false(isc_task_getprivilege(task1)); + + isc_task_destroy(&task1); + assert_null(task1); + isc_task_destroy(&task2); + assert_null(task2); +} + +/* + * Edge case: this tests that the task manager behaves as expected when + * we explicitly set it into normal mode *while* running privileged. + */ +static void +privilege_drop(void **state) { + isc_result_t result; + isc_task_t *task1 = NULL, *task2 = NULL; + isc_event_t *event = NULL; + atomic_int_fast32_t a, b, c, d, e; /* non valid states */ + int i = 0; + + UNUSED(state); + + atomic_init(&counter, 1); + atomic_init(&a, -1); + atomic_init(&b, -1); + atomic_init(&c, -1); + atomic_init(&d, -1); + atomic_init(&e, -1); + + /* + * Pause the net/task manager so we can fill up the work queue + * without things happening while we do it. + */ + isc_nm_pause(netmgr); + isc_taskmgr_setmode(taskmgr, isc_taskmgrmode_privileged); + + result = isc_task_create(taskmgr, 0, &task1); + assert_int_equal(result, ISC_R_SUCCESS); + isc_task_setname(task1, "privileged", NULL); + assert_false(isc_task_getprivilege(task1)); + isc_task_setprivilege(task1, true); + assert_true(isc_task_getprivilege(task1)); + + result = isc_task_create(taskmgr, 0, &task2); + assert_int_equal(result, ISC_R_SUCCESS); + isc_task_setname(task2, "normal", NULL); + assert_false(isc_task_getprivilege(task2)); + + /* First event: privileged */ + event = isc_event_allocate(test_mctx, task1, ISC_TASKEVENT_TEST, + set_and_drop, &a, sizeof(isc_event_t)); + assert_non_null(event); + + assert_int_equal(atomic_load(&a), -1); + isc_task_send(task1, &event); + + /* Second event: not privileged */ + event = isc_event_allocate(test_mctx, task2, ISC_TASKEVENT_TEST, + set_and_drop, &b, sizeof(isc_event_t)); + assert_non_null(event); + + assert_int_equal(atomic_load(&b), -1); + isc_task_send(task2, &event); + + /* Third event: privileged */ + event = isc_event_allocate(test_mctx, task1, ISC_TASKEVENT_TEST, + set_and_drop, &c, sizeof(isc_event_t)); + assert_non_null(event); + + assert_int_equal(atomic_load(&c), -1); + isc_task_send(task1, &event); + + /* Fourth event: privileged */ + event = isc_event_allocate(test_mctx, task1, ISC_TASKEVENT_TEST, + set_and_drop, &d, sizeof(isc_event_t)); + assert_non_null(event); + + assert_int_equal(atomic_load(&d), -1); + isc_task_send(task1, &event); + + /* Fifth event: not privileged */ + event = isc_event_allocate(test_mctx, task2, ISC_TASKEVENT_TEST, + set_and_drop, &e, sizeof(isc_event_t)); + assert_non_null(event); + + assert_int_equal(atomic_load(&e), -1); + isc_task_send(task2, &event); + + isc_nm_resume(netmgr); + + /* We're waiting for all variables to be set. */ + while ((atomic_load(&a) == -1 || atomic_load(&b) == -1 || + atomic_load(&c) == -1 || atomic_load(&d) == -1 || + atomic_load(&e) == -1) && + i++ < 5000) + { + isc_test_nap(1000); + } + + /* + * We need to check that all privilege mode events were fired + * in privileged mode, and non privileged in non-privileged. + */ + assert_true(atomic_load(&a) <= 3); + assert_true(atomic_load(&c) <= 3); + assert_true(atomic_load(&d) <= 3); + + /* ...and neither of the non-privileged tasks did... */ + assert_true(atomic_load(&b) > 3); + assert_true(atomic_load(&e) > 3); + + /* ...but all five of them did run. */ + assert_int_equal(atomic_load(&counter), 6); + + isc_task_destroy(&task1); + assert_null(task1); + isc_task_destroy(&task2); + assert_null(task2); +} + +static void +sleep_cb(isc_task_t *task, isc_event_t *event) { + UNUSED(task); + int p = *(int *)event->ev_arg; + if (p == 1) { + /* + * Signal the main thread that we're running, so that + * it can trigger the race. + */ + LOCK(&lock); + atomic_store(&done2, true); + SIGNAL(&cv); + UNLOCK(&lock); + /* + * Wait for the operations in the main thread to be finished. + */ + LOCK(&lock); + while (!atomic_load(&done)) { + WAIT(&cv, &lock); + } + UNLOCK(&lock); + } else { + /* + * Wait for the operations in the main thread to be finished. + */ + LOCK(&lock); + atomic_store(&done2, true); + SIGNAL(&cv); + UNLOCK(&lock); + } + isc_event_free(&event); +} + +static void +pause_unpause(void **state) { + isc_result_t result; + isc_task_t *task = NULL; + isc_event_t *event1, *event2 = NULL; + UNUSED(state); + atomic_store(&done, false); + atomic_store(&done2, false); + + result = isc_task_create(taskmgr, 0, &task); + assert_int_equal(result, ISC_R_SUCCESS); + + event1 = isc_event_allocate(test_mctx, task, ISC_TASKEVENT_TEST, + sleep_cb, &(int){ 1 }, sizeof(isc_event_t)); + assert_non_null(event1); + event2 = isc_event_allocate(test_mctx, task, ISC_TASKEVENT_TEST, + sleep_cb, &(int){ 2 }, sizeof(isc_event_t)); + assert_non_null(event2); + isc_task_send(task, &event1); + isc_task_send(task, &event2); + /* Wait for event1 to be running */ + LOCK(&lock); + while (!atomic_load(&done2)) { + WAIT(&cv, &lock); + } + UNLOCK(&lock); + /* Pause-unpause-detach is what causes the race */ + isc_task_pause(task); + isc_task_unpause(task); + isc_task_detach(&task); + /* Signal event1 to finish */ + LOCK(&lock); + atomic_store(&done2, false); + atomic_store(&done, true); + SIGNAL(&cv); + UNLOCK(&lock); + /* Wait for event2 to finish */ + LOCK(&lock); + while (!atomic_load(&done2)) { + WAIT(&cv, &lock); + } + UNLOCK(&lock); +} + +/* + * Basic task functions: + */ +static void +basic_cb(isc_task_t *task, isc_event_t *event) { + int i, j; + + UNUSED(task); + + j = 0; + for (i = 0; i < 1000000; i++) { + j += 100; + } + + UNUSED(j); + + if (verbose) { + print_message("# task %s\n", (char *)event->ev_arg); + } + + isc_event_free(&event); +} + +static void +basic_shutdown(isc_task_t *task, isc_event_t *event) { + UNUSED(task); + + if (verbose) { + print_message("# shutdown %s\n", (char *)event->ev_arg); + } + + isc_event_free(&event); +} + +static void +basic_tick(isc_task_t *task, isc_event_t *event) { + UNUSED(task); + + if (verbose) { + print_message("# %s\n", (char *)event->ev_arg); + } + + isc_event_free(&event); +} + +static char one[] = "1"; +static char two[] = "2"; +static char three[] = "3"; +static char four[] = "4"; +static char tick[] = "tick"; +static char tock[] = "tock"; + +static void +basic(void **state) { + isc_result_t result; + isc_task_t *task1 = NULL; + isc_task_t *task2 = NULL; + isc_task_t *task3 = NULL; + isc_task_t *task4 = NULL; + isc_event_t *event = NULL; + isc_timer_t *ti1 = NULL; + isc_timer_t *ti2 = NULL; + isc_time_t absolute; + isc_interval_t interval; + char *testarray[] = { one, one, one, one, one, one, one, one, + one, two, three, four, two, three, four, NULL }; + int i; + + UNUSED(state); + + result = isc_task_create(taskmgr, 0, &task1); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_task_create(taskmgr, 0, &task2); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_task_create(taskmgr, 0, &task3); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_task_create(taskmgr, 0, &task4); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_task_onshutdown(task1, basic_shutdown, one); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_task_onshutdown(task2, basic_shutdown, two); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_task_onshutdown(task3, basic_shutdown, three); + assert_int_equal(result, ISC_R_SUCCESS); + result = isc_task_onshutdown(task4, basic_shutdown, four); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_time_settoepoch(&absolute); + isc_interval_set(&interval, 1, 0); + result = isc_timer_create(timermgr, isc_timertype_ticker, &absolute, + &interval, task1, basic_tick, tick, &ti1); + assert_int_equal(result, ISC_R_SUCCESS); + + ti2 = NULL; + isc_time_settoepoch(&absolute); + isc_interval_set(&interval, 1, 0); + result = isc_timer_create(timermgr, isc_timertype_ticker, &absolute, + &interval, task2, basic_tick, tock, &ti2); + assert_int_equal(result, ISC_R_SUCCESS); + +#ifndef WIN32 + sleep(2); +#else /* ifndef WIN32 */ + Sleep(2000); +#endif /* ifndef WIN32 */ + + for (i = 0; testarray[i] != NULL; i++) { + /* + * Note: (void *)1 is used as a sender here, since some + * compilers don't like casting a function pointer to a + * (void *). + * + * In a real use, it is more likely the sender would be a + * structure (socket, timer, task, etc) but this is just a + * test program. + */ + event = isc_event_allocate(test_mctx, (void *)1, 1, basic_cb, + testarray[i], sizeof(*event)); + assert_non_null(event); + isc_task_send(task1, &event); + } + + (void)isc_task_purge(task3, NULL, 0, 0); + + isc_task_detach(&task1); + isc_task_detach(&task2); + isc_task_detach(&task3); + isc_task_detach(&task4); + +#ifndef WIN32 + sleep(10); +#else /* ifndef WIN32 */ + Sleep(10000); +#endif /* ifndef WIN32 */ + isc_timer_destroy(&ti1); + isc_timer_destroy(&ti2); +} + +/* + * Exclusive mode test: + * When one task enters exclusive mode, all other active + * tasks complete first. + */ +static int +spin(int n) { + int i; + int r = 0; + for (i = 0; i < n; i++) { + r += i; + if (r > 1000000) { + r = 0; + } + } + return (r); +} + +static void +exclusive_cb(isc_task_t *task, isc_event_t *event) { + int taskno = *(int *)(event->ev_arg); + + if (verbose) { + print_message("# task enter %d\n", taskno); + } + + /* task chosen from the middle of the range */ + if (taskno == 6) { + isc_result_t result; + int i; + + result = isc_task_beginexclusive(task); + assert_int_equal(result, ISC_R_SUCCESS); + + for (i = 0; i < 10; i++) { + assert_int_equal(active[i], 0); + } + + isc_task_endexclusive(task); + atomic_store(&done, true); + } else { + active[taskno]++; + (void)spin(10000000); + active[taskno]--; + } + + if (verbose) { + print_message("# task exit %d\n", taskno); + } + + if (atomic_load(&done)) { + isc_mem_put(event->ev_destroy_arg, event->ev_arg, sizeof(int)); + isc_event_free(&event); + atomic_fetch_sub(&counter, 1); + } else { + isc_task_send(task, &event); + } +} + +static void +task_exclusive(void **state) { + isc_task_t *tasks[10]; + isc_result_t result; + int i; + + UNUSED(state); + + atomic_init(&counter, 0); + + for (i = 0; i < 10; i++) { + isc_event_t *event = NULL; + int *v; + + tasks[i] = NULL; + + if (i == 6) { + /* task chosen from the middle of the range */ + result = isc_task_create_bound(taskmgr, 0, &tasks[i], + 0); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_taskmgr_setexcltask(taskmgr, tasks[6]); + } else { + result = isc_task_create(taskmgr, 0, &tasks[i]); + assert_int_equal(result, ISC_R_SUCCESS); + } + + v = isc_mem_get(test_mctx, sizeof *v); + assert_non_null(v); + + *v = i; + + event = isc_event_allocate(test_mctx, NULL, 1, exclusive_cb, v, + sizeof(*event)); + assert_non_null(event); + + isc_task_send(tasks[i], &event); + atomic_fetch_add(&counter, 1); + } + + for (i = 0; i < 10; i++) { + isc_task_detach(&tasks[i]); + } + + while (atomic_load(&counter) > 0) { + isc_test_nap(1000); + } +} + +/* + * Max tasks test: + * The task system can create and execute many tasks. Tests with 10000. + */ +static void +maxtask_shutdown(isc_task_t *task, isc_event_t *event) { + UNUSED(task); + + if (event->ev_arg != NULL) { + isc_task_destroy((isc_task_t **)&event->ev_arg); + } else { + LOCK(&lock); + atomic_store(&done, true); + SIGNAL(&cv); + UNLOCK(&lock); + } + + isc_event_free(&event); +} + +static void +maxtask_cb(isc_task_t *task, isc_event_t *event) { + isc_result_t result; + + if (event->ev_arg != NULL) { + isc_task_t *newtask = NULL; + + event->ev_arg = (void *)(((uintptr_t)event->ev_arg) - 1); + + /* + * Create a new task and forward the message. + */ + result = isc_task_create(taskmgr, 0, &newtask); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_task_onshutdown(newtask, maxtask_shutdown, + (void *)task); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_task_send(newtask, &event); + } else if (task != NULL) { + isc_task_destroy(&task); + isc_event_free(&event); + } +} + +static void +manytasks(void **state) { + isc_mem_t *mctx = NULL; + isc_event_t *event = NULL; + uintptr_t ntasks = 10000; + + UNUSED(state); + + if (verbose) { + print_message("# Testing with %lu tasks\n", + (unsigned long)ntasks); + } + + isc_mutex_init(&lock); + isc_condition_init(&cv); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + isc_mem_create(&mctx); + + isc_managers_create(mctx, 4, 0, &netmgr, &taskmgr); + + atomic_init(&done, false); + + event = isc_event_allocate(mctx, (void *)1, 1, maxtask_cb, + (void *)ntasks, sizeof(*event)); + assert_non_null(event); + + LOCK(&lock); + maxtask_cb(NULL, event); + while (!atomic_load(&done)) { + WAIT(&cv, &lock); + } + UNLOCK(&lock); + + isc_managers_destroy(&netmgr, &taskmgr); + + isc_mem_destroy(&mctx); + isc_condition_destroy(&cv); + isc_mutex_destroy(&lock); +} + +/* + * Shutdown test: + * When isc_task_shutdown() is called, shutdown events are posted + * in LIFO order. + */ + +static int nevents = 0; +static int nsdevents = 0; +static int senders[4]; +atomic_bool ready, all_done; + +static void +sd_sde1(isc_task_t *task, isc_event_t *event) { + UNUSED(task); + + assert_int_equal(nevents, 256); + assert_int_equal(nsdevents, 1); + ++nsdevents; + + if (verbose) { + print_message("# shutdown 1\n"); + } + + isc_event_free(&event); + + atomic_store(&all_done, true); +} + +static void +sd_sde2(isc_task_t *task, isc_event_t *event) { + UNUSED(task); + + assert_int_equal(nevents, 256); + assert_int_equal(nsdevents, 0); + ++nsdevents; + + if (verbose) { + print_message("# shutdown 2\n"); + } + + isc_event_free(&event); +} + +static void +sd_event1(isc_task_t *task, isc_event_t *event) { + UNUSED(task); + + LOCK(&lock); + while (!atomic_load(&ready)) { + WAIT(&cv, &lock); + } + UNLOCK(&lock); + + if (verbose) { + print_message("# event 1\n"); + } + + isc_event_free(&event); +} + +static void +sd_event2(isc_task_t *task, isc_event_t *event) { + UNUSED(task); + + ++nevents; + + if (verbose) { + print_message("# event 2\n"); + } + + isc_event_free(&event); +} + +static void +task_shutdown(void **state) { + isc_result_t result; + isc_eventtype_t event_type; + isc_event_t *event = NULL; + isc_task_t *task = NULL; + int i; + + UNUSED(state); + + nevents = nsdevents = 0; + event_type = 3; + atomic_init(&ready, false); + atomic_init(&all_done, false); + + LOCK(&lock); + + result = isc_task_create(taskmgr, 0, &task); + assert_int_equal(result, ISC_R_SUCCESS); + + /* + * This event causes the task to wait on cv. + */ + event = isc_event_allocate(test_mctx, &senders[1], event_type, + sd_event1, NULL, sizeof(*event)); + assert_non_null(event); + isc_task_send(task, &event); + + /* + * Now we fill up the task's event queue with some events. + */ + for (i = 0; i < 256; ++i) { + event = isc_event_allocate(test_mctx, &senders[1], event_type, + sd_event2, NULL, sizeof(*event)); + assert_non_null(event); + isc_task_send(task, &event); + } + + /* + * Now we register two shutdown events. + */ + result = isc_task_onshutdown(task, sd_sde1, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_task_onshutdown(task, sd_sde2, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_task_shutdown(task); + isc_task_detach(&task); + + /* + * Now we free the task by signaling cv. + */ + atomic_store(&ready, true); + SIGNAL(&cv); + UNLOCK(&lock); + + while (!atomic_load(&all_done)) { + isc_test_nap(1000); + } + + assert_int_equal(nsdevents, 2); +} + +/* + * Post-shutdown test: + * After isc_task_shutdown() has been called, any call to + * isc_task_onshutdown() will return ISC_R_SHUTTINGDOWN. + */ +static void +psd_event1(isc_task_t *task, isc_event_t *event) { + UNUSED(task); + + LOCK(&lock); + + while (!atomic_load(&done)) { + WAIT(&cv, &lock); + } + + UNLOCK(&lock); + + isc_event_free(&event); +} + +static void +psd_sde(isc_task_t *task, isc_event_t *event) { + UNUSED(task); + + isc_event_free(&event); +} + +static void +post_shutdown(void **state) { + isc_result_t result; + isc_eventtype_t event_type; + isc_event_t *event; + isc_task_t *task; + + UNUSED(state); + + atomic_init(&done, false); + event_type = 4; + + isc_condition_init(&cv); + + LOCK(&lock); + + task = NULL; + result = isc_task_create(taskmgr, 0, &task); + assert_int_equal(result, ISC_R_SUCCESS); + + /* + * This event causes the task to wait on cv. + */ + event = isc_event_allocate(test_mctx, &senders[1], event_type, + psd_event1, NULL, sizeof(*event)); + assert_non_null(event); + isc_task_send(task, &event); + + isc_task_shutdown(task); + + result = isc_task_onshutdown(task, psd_sde, NULL); + assert_int_equal(result, ISC_R_SHUTTINGDOWN); + + /* + * Release the task. + */ + atomic_store(&done, true); + + SIGNAL(&cv); + UNLOCK(&lock); + + isc_task_detach(&task); +} + +/* + * Helper for the purge tests below: + */ + +#define SENDERCNT 3 +#define TYPECNT 4 +#define TAGCNT 5 +#define NEVENTS (SENDERCNT * TYPECNT * TAGCNT) + +static bool testrange; +static void *purge_sender; +static isc_eventtype_t purge_type_first; +static isc_eventtype_t purge_type_last; +static void *purge_tag; +static int eventcnt; + +atomic_bool started; + +static void +pg_event1(isc_task_t *task, isc_event_t *event) { + UNUSED(task); + + LOCK(&lock); + while (!atomic_load(&started)) { + WAIT(&cv, &lock); + } + UNLOCK(&lock); + + isc_event_free(&event); +} + +static void +pg_event2(isc_task_t *task, isc_event_t *event) { + bool sender_match = false; + bool type_match = false; + bool tag_match = false; + + UNUSED(task); + + if ((purge_sender == NULL) || (purge_sender == event->ev_sender)) { + sender_match = true; + } + + if (testrange) { + if ((purge_type_first <= event->ev_type) && + (event->ev_type <= purge_type_last)) + { + type_match = true; + } + } else { + if (purge_type_first == event->ev_type) { + type_match = true; + } + } + + if ((purge_tag == NULL) || (purge_tag == event->ev_tag)) { + tag_match = true; + } + + if (sender_match && type_match && tag_match) { + if ((event->ev_attributes & ISC_EVENTATTR_NOPURGE) != 0) { + if (verbose) { + print_message("# event %p,%d,%p " + "matched but was not " + "purgeable\n", + event->ev_sender, + (int)event->ev_type, + event->ev_tag); + } + ++eventcnt; + } else if (verbose) { + print_message("# event %p,%d,%p not purged\n", + event->ev_sender, (int)event->ev_type, + event->ev_tag); + } + } else { + ++eventcnt; + } + + isc_event_free(&event); +} + +static void +pg_sde(isc_task_t *task, isc_event_t *event) { + UNUSED(task); + + LOCK(&lock); + atomic_store(&done, true); + SIGNAL(&cv); + UNLOCK(&lock); + + isc_event_free(&event); +} + +static void +test_purge(int sender, int type, int tag, int exp_purged) { + isc_result_t result; + isc_task_t *task = NULL; + isc_event_t *eventtab[NEVENTS]; + isc_event_t *event = NULL; + isc_interval_t interval; + isc_time_t now; + int sender_cnt, type_cnt, tag_cnt, event_cnt, i; + int purged = 0; + + atomic_init(&started, false); + atomic_init(&done, false); + eventcnt = 0; + + isc_condition_init(&cv); + + result = isc_task_create(taskmgr, 0, &task); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_task_onshutdown(task, pg_sde, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + /* + * Block the task on cv. + */ + event = isc_event_allocate(test_mctx, (void *)1, 9999, pg_event1, NULL, + sizeof(*event)); + + assert_non_null(event); + isc_task_send(task, &event); + + /* + * Fill the task's queue with some messages with varying + * sender, type, tag, and purgeable attribute values. + */ + event_cnt = 0; + for (sender_cnt = 0; sender_cnt < SENDERCNT; ++sender_cnt) { + for (type_cnt = 0; type_cnt < TYPECNT; ++type_cnt) { + for (tag_cnt = 0; tag_cnt < TAGCNT; ++tag_cnt) { + eventtab[event_cnt] = isc_event_allocate( + test_mctx, + &senders[sender + sender_cnt], + (isc_eventtype_t)(type + type_cnt), + pg_event2, NULL, sizeof(*event)); + + assert_non_null(eventtab[event_cnt]); + + eventtab[event_cnt]->ev_tag = + (void *)((uintptr_t)tag + tag_cnt); + + /* + * Mark events as non-purgeable if + * sender, type and tag are all + * odd-numbered. (There should be 4 + * of these out of 60 events total.) + */ + if (((sender_cnt % 2) != 0) && + ((type_cnt % 2) != 0) && + ((tag_cnt % 2) != 0)) + { + eventtab[event_cnt]->ev_attributes |= + ISC_EVENTATTR_NOPURGE; + } + ++event_cnt; + } + } + } + + for (i = 0; i < event_cnt; ++i) { + isc_task_send(task, &eventtab[i]); + } + + if (testrange) { + /* + * We're testing isc_task_purgerange. + */ + purged = isc_task_purgerange( + task, purge_sender, (isc_eventtype_t)purge_type_first, + (isc_eventtype_t)purge_type_last, purge_tag); + assert_int_equal(purged, exp_purged); + } else { + /* + * We're testing isc_task_purge. + */ + if (verbose) { + print_message("# purge events %p,%u,%p\n", purge_sender, + purge_type_first, purge_tag); + } + purged = isc_task_purge(task, purge_sender, + (isc_eventtype_t)purge_type_first, + purge_tag); + if (verbose) { + print_message("# purged %d expected %d\n", purged, + exp_purged); + } + + assert_int_equal(purged, exp_purged); + } + + /* + * Unblock the task, allowing event processing. + */ + LOCK(&lock); + atomic_store(&started, true); + SIGNAL(&cv); + + isc_task_shutdown(task); + + isc_interval_set(&interval, 5, 0); + + /* + * Wait for shutdown processing to complete. + */ + while (!atomic_load(&done)) { + result = isc_time_nowplusinterval(&now, &interval); + assert_int_equal(result, ISC_R_SUCCESS); + + WAITUNTIL(&cv, &lock, &now); + } + + UNLOCK(&lock); + + isc_task_detach(&task); + + assert_int_equal(eventcnt, event_cnt - exp_purged); +} + +/* + * Purge test: + * A call to isc_task_purge(task, sender, type, tag) purges all events of + * type 'type' and with tag 'tag' not marked as unpurgeable from sender + * from the task's " queue and returns the number of events purged. + */ +static void +purge(void **state) { + UNUSED(state); + + /* Try purging on a specific sender. */ + if (verbose) { + print_message("# testing purge on 2,4,8 expecting 1\n"); + } + purge_sender = &senders[2]; + purge_type_first = 4; + purge_type_last = 4; + purge_tag = (void *)8; + testrange = false; + test_purge(1, 4, 7, 1); + + /* Try purging on all senders. */ + if (verbose) { + print_message("# testing purge on 0,4,8 expecting 3\n"); + } + purge_sender = NULL; + purge_type_first = 4; + purge_type_last = 4; + purge_tag = (void *)8; + testrange = false; + test_purge(1, 4, 7, 3); + + /* Try purging on all senders, specified type, all tags. */ + if (verbose) { + print_message("# testing purge on 0,4,0 expecting 15\n"); + } + purge_sender = NULL; + purge_type_first = 4; + purge_type_last = 4; + purge_tag = NULL; + testrange = false; + test_purge(1, 4, 7, 15); + + /* Try purging on a specified tag, no such type. */ + if (verbose) { + print_message("# testing purge on 0,99,8 expecting 0\n"); + } + purge_sender = NULL; + purge_type_first = 99; + purge_type_last = 99; + purge_tag = (void *)8; + testrange = false; + test_purge(1, 4, 7, 0); + + /* Try purging on specified sender, type, all tags. */ + if (verbose) { + print_message("# testing purge on 3,5,0 expecting 5\n"); + } + purge_sender = &senders[3]; + purge_type_first = 5; + purge_type_last = 5; + purge_tag = NULL; + testrange = false; + test_purge(1, 4, 7, 5); +} + +/* + * Purge range test: + * A call to isc_event_purgerange(task, sender, first, last, tag) purges + * all events not marked unpurgeable from sender 'sender' and of type within + * the range 'first' to 'last' inclusive from the task's event queue and + * returns the number of tasks purged. + */ + +static void +purgerange(void **state) { + UNUSED(state); + + /* Now let's try some ranges. */ + /* testing purgerange on 2,4-5,8 expecting 1 */ + purge_sender = &senders[2]; + purge_type_first = 4; + purge_type_last = 5; + purge_tag = (void *)8; + testrange = true; + test_purge(1, 4, 7, 1); + + /* Try purging on all senders. */ + if (verbose) { + print_message("# testing purge on 0,4-5,8 expecting 5\n"); + } + purge_sender = NULL; + purge_type_first = 4; + purge_type_last = 5; + purge_tag = (void *)8; + testrange = true; + test_purge(1, 4, 7, 5); + + /* Try purging on all senders, specified type, all tags. */ + if (verbose) { + print_message("# testing purge on 0,5-6,0 expecting 28\n"); + } + purge_sender = NULL; + purge_type_first = 5; + purge_type_last = 6; + purge_tag = NULL; + testrange = true; + test_purge(1, 4, 7, 28); + + /* Try purging on a specified tag, no such type. */ + if (verbose) { + print_message("# testing purge on 0,99-101,8 expecting 0\n"); + } + purge_sender = NULL; + purge_type_first = 99; + purge_type_last = 101; + purge_tag = (void *)8; + testrange = true; + test_purge(1, 4, 7, 0); + + /* Try purging on specified sender, type, all tags. */ + if (verbose) { + print_message("# testing purge on 3,5-6,0 expecting 10\n"); + } + purge_sender = &senders[3]; + purge_type_first = 5; + purge_type_last = 6; + purge_tag = NULL; + testrange = true; + test_purge(1, 4, 7, 10); +} + +/* + * Helpers for purge event tests + */ +static void +pge_event1(isc_task_t *task, isc_event_t *event) { + UNUSED(task); + + LOCK(&lock); + while (!atomic_load(&started)) { + WAIT(&cv, &lock); + } + UNLOCK(&lock); + + isc_event_free(&event); +} + +static void +pge_event2(isc_task_t *task, isc_event_t *event) { + UNUSED(task); + + ++eventcnt; + isc_event_free(&event); +} + +static void +pge_sde(isc_task_t *task, isc_event_t *event) { + UNUSED(task); + + LOCK(&lock); + atomic_store(&done, true); + SIGNAL(&cv); + UNLOCK(&lock); + + isc_event_free(&event); +} + +static void +try_purgeevent(bool purgeable) { + isc_result_t result; + isc_task_t *task = NULL; + bool purged; + isc_event_t *event1 = NULL; + isc_event_t *event2 = NULL; + isc_event_t *event2_clone = NULL; + isc_time_t now; + isc_interval_t interval; + + atomic_init(&started, false); + atomic_init(&done, false); + eventcnt = 0; + + isc_condition_init(&cv); + + result = isc_task_create(taskmgr, 0, &task); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_task_onshutdown(task, pge_sde, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + /* + * Block the task on cv. + */ + event1 = isc_event_allocate(test_mctx, (void *)1, (isc_eventtype_t)1, + pge_event1, NULL, sizeof(*event1)); + assert_non_null(event1); + isc_task_send(task, &event1); + + event2 = isc_event_allocate(test_mctx, (void *)1, (isc_eventtype_t)1, + pge_event2, NULL, sizeof(*event2)); + assert_non_null(event2); + + event2_clone = event2; + + if (purgeable) { + event2->ev_attributes &= ~ISC_EVENTATTR_NOPURGE; + } else { + event2->ev_attributes |= ISC_EVENTATTR_NOPURGE; + } + + isc_task_send(task, &event2); + + purged = isc_task_purgeevent(task, event2_clone); + assert_int_equal(purgeable, purged); + + /* + * Unblock the task, allowing event processing. + */ + LOCK(&lock); + atomic_store(&started, true); + SIGNAL(&cv); + + isc_task_shutdown(task); + + isc_interval_set(&interval, 5, 0); + + /* + * Wait for shutdown processing to complete. + */ + while (!atomic_load(&done)) { + result = isc_time_nowplusinterval(&now, &interval); + assert_int_equal(result, ISC_R_SUCCESS); + + WAITUNTIL(&cv, &lock, &now); + } + + UNLOCK(&lock); + + isc_task_detach(&task); + + assert_int_equal(eventcnt, (purgeable ? 0 : 1)); +} + +/* + * Purge event test: + * When the event is marked as purgeable, a call to + * isc_task_purgeevent(task, event) purges the event 'event' from the + * task's queue and returns true. + */ + +static void +purgeevent(void **state) { + UNUSED(state); + + try_purgeevent(true); +} + +int +main(int argc, char **argv) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(manytasks), + cmocka_unit_test_setup_teardown(all_events, _setup, _teardown), + cmocka_unit_test_setup_teardown(basic, _setup2, _teardown), + cmocka_unit_test_setup_teardown(create_task, _setup, _teardown), + cmocka_unit_test_setup_teardown(pause_unpause, _setup, + _teardown), + cmocka_unit_test_setup_teardown(post_shutdown, _setup2, + _teardown), + cmocka_unit_test_setup_teardown(privilege_drop, _setup, + _teardown), + cmocka_unit_test_setup_teardown(privileged_events, _setup, + _teardown), + cmocka_unit_test_setup_teardown(purge, _setup2, _teardown), + cmocka_unit_test_setup_teardown(purgeevent, _setup2, _teardown), + cmocka_unit_test_setup_teardown(purgerange, _setup, _teardown), + cmocka_unit_test_setup_teardown(task_shutdown, _setup4, + _teardown), + cmocka_unit_test_setup_teardown(task_exclusive, _setup4, + _teardown), + }; + struct CMUnitTest selected[sizeof(tests) / sizeof(tests[0])]; + size_t i; + int c; + + memset(selected, 0, sizeof(selected)); + + while ((c = isc_commandline_parse(argc, argv, "lt:v")) != -1) { + switch (c) { + case 'l': + for (i = 0; i < (sizeof(tests) / sizeof(tests[0])); i++) + { + if (tests[i].name != NULL) { + fprintf(stdout, "%s\n", tests[i].name); + } + } + return (0); + case 't': + if (!cmocka_add_test_byname( + tests, isc_commandline_argument, selected)) + { + fprintf(stderr, "unknown test '%s'\n", + isc_commandline_argument); + exit(1); + } + break; + case 'v': + verbose = true; + break; + default: + break; + } + } + + if (selected[0].name != NULL) { + return (cmocka_run_group_tests(selected, NULL, NULL)); + } else { + 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 */ diff --git a/lib/isc/tests/taskpool_test.c b/lib/isc/tests/taskpool_test.c new file mode 100644 index 0000000..64a4641 --- /dev/null +++ b/lib/isc/tests/taskpool_test.c @@ -0,0 +1,204 @@ +/* + * 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 <sched.h> /* IWYU pragma: keep */ +#include <setjmp.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define UNIT_TESTING +#include <cmocka.h> + +#include <isc/task.h> +#include <isc/taskpool.h> +#include <isc/util.h> + +#include "isctest.h" + +#define TASK_MAGIC ISC_MAGIC('T', 'A', 'S', 'K') +#define VALID_TASK(t) ISC_MAGIC_VALID(t, TASK_MAGIC) + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + result = isc_test_begin(NULL, true, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + isc_test_end(); + + return (0); +} + +/* Create a taskpool */ +static void +create_pool(void **state) { + isc_result_t result; + isc_taskpool_t *pool = NULL; + + UNUSED(state); + + result = isc_taskpool_create(taskmgr, test_mctx, 8, 2, false, &pool); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(isc_taskpool_size(pool), 8); + + isc_taskpool_destroy(&pool); + assert_null(pool); +} + +/* Resize a taskpool */ +static void +expand_pool(void **state) { + isc_result_t result; + isc_taskpool_t *pool1 = NULL, *pool2 = NULL, *hold = NULL; + + UNUSED(state); + + result = isc_taskpool_create(taskmgr, test_mctx, 10, 2, false, &pool1); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(isc_taskpool_size(pool1), 10); + + /* resizing to a smaller size should have no effect */ + hold = pool1; + result = isc_taskpool_expand(&pool1, 5, false, &pool2); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(isc_taskpool_size(pool2), 10); + assert_ptr_equal(pool2, hold); + assert_null(pool1); + pool1 = pool2; + pool2 = NULL; + + /* resizing to the same size should have no effect */ + hold = pool1; + result = isc_taskpool_expand(&pool1, 10, false, &pool2); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(isc_taskpool_size(pool2), 10); + assert_ptr_equal(pool2, hold); + assert_null(pool1); + pool1 = pool2; + pool2 = NULL; + + /* resizing to larger size should make a new pool */ + hold = pool1; + result = isc_taskpool_expand(&pool1, 20, false, &pool2); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(isc_taskpool_size(pool2), 20); + assert_ptr_not_equal(pool2, hold); + assert_null(pool1); + + isc_taskpool_destroy(&pool2); + assert_null(pool2); +} + +/* Get tasks */ +static void +get_tasks(void **state) { + isc_result_t result; + isc_taskpool_t *pool = NULL; + isc_task_t *task1 = NULL, *task2 = NULL, *task3 = NULL; + + UNUSED(state); + + result = isc_taskpool_create(taskmgr, test_mctx, 2, 2, false, &pool); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(isc_taskpool_size(pool), 2); + + /* two tasks in pool; make sure we can access them more than twice */ + isc_taskpool_gettask(pool, &task1); + assert_true(VALID_TASK(task1)); + + isc_taskpool_gettask(pool, &task2); + assert_true(VALID_TASK(task2)); + + isc_taskpool_gettask(pool, &task3); + assert_true(VALID_TASK(task3)); + + isc_task_destroy(&task1); + isc_task_destroy(&task2); + isc_task_destroy(&task3); + + isc_taskpool_destroy(&pool); + assert_null(pool); +} + +/* Set privileges */ +static void +set_privilege(void **state) { + isc_result_t result; + isc_taskpool_t *pool = NULL; + isc_task_t *task1 = NULL, *task2 = NULL, *task3 = NULL; + + UNUSED(state); + + result = isc_taskpool_create(taskmgr, test_mctx, 2, 2, true, &pool); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(isc_taskpool_size(pool), 2); + + isc_taskpool_gettask(pool, &task1); + isc_taskpool_gettask(pool, &task2); + isc_taskpool_gettask(pool, &task3); + + assert_true(VALID_TASK(task1)); + assert_true(VALID_TASK(task2)); + assert_true(VALID_TASK(task3)); + + assert_true(isc_task_getprivilege(task1)); + assert_true(isc_task_getprivilege(task2)); + assert_true(isc_task_getprivilege(task3)); + + isc_task_destroy(&task1); + isc_task_destroy(&task2); + isc_task_destroy(&task3); + + isc_taskpool_destroy(&pool); + assert_null(pool); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(create_pool, _setup, _teardown), + cmocka_unit_test_setup_teardown(expand_pool, _setup, _teardown), + cmocka_unit_test_setup_teardown(get_tasks, _setup, _teardown), + cmocka_unit_test_setup_teardown(set_privilege, _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 */ diff --git a/lib/isc/tests/testdata/file/keep b/lib/isc/tests/testdata/file/keep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/isc/tests/testdata/file/keep diff --git a/lib/isc/tests/time_test.c b/lib/isc/tests/time_test.c new file mode 100644 index 0000000..870d9bf --- /dev/null +++ b/lib/isc/tests/time_test.c @@ -0,0 +1,430 @@ +/* + * 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 <sched.h> /* IWYU pragma: keep */ +#include <setjmp.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define UNIT_TESTING +#include <cmocka.h> + +#include <isc/result.h> +#include <isc/time.h> +#include <isc/util.h> + +#include "../time.c" + +#define NS_PER_S 1000000000 /*%< Nanoseconds per second. */ +#define MAX_NS (NS_PER_S - 1) + +struct time_vectors { + isc_time_t a; + isc_interval_t b; + isc_time_t r; + isc_result_t result; +}; + +const struct time_vectors vectors_add[8] = { + { { 0, 0 }, { 0, 0 }, { 0, 0 }, ISC_R_SUCCESS }, + { { 0, MAX_NS }, { 0, MAX_NS }, { 1, MAX_NS - 1 }, ISC_R_SUCCESS }, + { { 0, NS_PER_S / 2 }, { 0, NS_PER_S / 2 }, { 1, 0 }, ISC_R_SUCCESS }, + { { UINT_MAX, MAX_NS }, { 0, 0 }, { UINT_MAX, MAX_NS }, ISC_R_SUCCESS }, + { { UINT_MAX, 0 }, { 0, MAX_NS }, { UINT_MAX, MAX_NS }, ISC_R_SUCCESS }, + { { UINT_MAX, 0 }, { 1, 0 }, { 0, 0 }, ISC_R_RANGE }, + { { UINT_MAX, MAX_NS }, { 0, 1 }, { 0, 0 }, ISC_R_RANGE }, + { { UINT_MAX / 2 + 1, NS_PER_S / 2 }, + { UINT_MAX / 2, NS_PER_S / 2 }, + { 0, 0 }, + ISC_R_RANGE }, +}; + +const struct time_vectors vectors_sub[7] = { + { { 0, 0 }, { 0, 0 }, { 0, 0 }, ISC_R_SUCCESS }, + { { 1, 0 }, { 0, MAX_NS }, { 0, 1 }, ISC_R_SUCCESS }, + { { 1, NS_PER_S / 2 }, + { 0, MAX_NS }, + { 0, NS_PER_S / 2 + 1 }, + ISC_R_SUCCESS }, + { { UINT_MAX, MAX_NS }, { UINT_MAX, 0 }, { 0, MAX_NS }, ISC_R_SUCCESS }, + { { 0, 0 }, { 1, 0 }, { 0, 0 }, ISC_R_RANGE }, + { { 0, 0 }, { 0, MAX_NS }, { 0, 0 }, ISC_R_RANGE }, +}; + +static void +isc_time_add_test(void **state) { + UNUSED(state); + + for (size_t i = 0; i < ARRAY_SIZE(vectors_add); i++) { + isc_time_t r = { UINT_MAX, UINT_MAX }; + isc_result_t result = isc_time_add(&(vectors_add[i].a), + &(vectors_add[i].b), &r); + assert_int_equal(result, vectors_add[i].result); + if (result != ISC_R_SUCCESS) { + continue; + } + + assert_int_equal(r.seconds, vectors_add[i].r.seconds); + assert_int_equal(r.nanoseconds, vectors_add[i].r.nanoseconds); + } + + expect_assert_failure((void)isc_time_add(&(isc_time_t){ 0, MAX_NS + 1 }, + &(isc_interval_t){ 0, 0 }, + &(isc_time_t){ 0, 0 })); + expect_assert_failure((void)isc_time_add( + &(isc_time_t){ 0, 0 }, &(isc_interval_t){ 0, MAX_NS + 1 }, + &(isc_time_t){ 0, 0 })); + + expect_assert_failure((void)isc_time_add((isc_time_t *)NULL, + &(isc_interval_t){ 0, 0 }, + &(isc_time_t){ 0, 0 })); + expect_assert_failure((void)isc_time_add(&(isc_time_t){ 0, 0 }, + (isc_interval_t *)NULL, + &(isc_time_t){ 0, 0 })); + expect_assert_failure((void)isc_time_add( + &(isc_time_t){ 0, 0 }, &(isc_interval_t){ 0, 0 }, NULL)); +} + +static void +isc_time_sub_test(void **state) { + UNUSED(state); + + for (size_t i = 0; i < ARRAY_SIZE(vectors_sub); i++) { + isc_time_t r = { UINT_MAX, UINT_MAX }; + isc_result_t result = isc_time_subtract( + &(vectors_sub[i].a), &(vectors_sub[i].b), &r); + assert_int_equal(result, vectors_sub[i].result); + if (result != ISC_R_SUCCESS) { + continue; + } + assert_int_equal(r.seconds, vectors_sub[i].r.seconds); + assert_int_equal(r.nanoseconds, vectors_sub[i].r.nanoseconds); + } + + expect_assert_failure((void)isc_time_subtract( + &(isc_time_t){ 0, MAX_NS + 1 }, &(isc_interval_t){ 0, 0 }, + &(isc_time_t){ 0, 0 })); + expect_assert_failure((void)isc_time_subtract( + &(isc_time_t){ 0, 0 }, &(isc_interval_t){ 0, MAX_NS + 1 }, + &(isc_time_t){ 0, 0 })); + + expect_assert_failure((void)isc_time_subtract((isc_time_t *)NULL, + &(isc_interval_t){ 0, 0 }, + &(isc_time_t){ 0, 0 })); + expect_assert_failure((void)isc_time_subtract(&(isc_time_t){ 0, 0 }, + (isc_interval_t *)NULL, + &(isc_time_t){ 0, 0 })); + expect_assert_failure((void)isc_time_subtract( + &(isc_time_t){ 0, 0 }, &(isc_interval_t){ 0, 0 }, NULL)); +} + +/* parse http time stamp */ +static void +isc_time_parsehttptimestamp_test(void **state) { + isc_result_t result; + isc_time_t t, x; + char buf[ISC_FORMATHTTPTIMESTAMP_SIZE]; + + UNUSED(state); + + setenv("TZ", "America/Los_Angeles", 1); + result = isc_time_now(&t); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_time_formathttptimestamp(&t, buf, sizeof(buf)); + result = isc_time_parsehttptimestamp(buf, &x); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(isc_time_seconds(&t), isc_time_seconds(&x)); +} + +/* print UTC in ISO8601 */ +static void +isc_time_formatISO8601_test(void **state) { + isc_result_t result; + isc_time_t t; + char buf[64]; + + UNUSED(state); + + setenv("TZ", "America/Los_Angeles", 1); + result = isc_time_now(&t); + assert_int_equal(result, ISC_R_SUCCESS); + + /* check formatting: yyyy-mm-ddThh:mm:ssZ */ + memset(buf, 'X', sizeof(buf)); + isc_time_formatISO8601(&t, buf, sizeof(buf)); + assert_int_equal(strlen(buf), 20); + assert_int_equal(buf[4], '-'); + assert_int_equal(buf[7], '-'); + assert_int_equal(buf[10], 'T'); + assert_int_equal(buf[13], ':'); + assert_int_equal(buf[16], ':'); + assert_int_equal(buf[19], 'Z'); + + /* check time conversion correctness */ + memset(buf, 'X', sizeof(buf)); + isc_time_settoepoch(&t); + isc_time_formatISO8601(&t, buf, sizeof(buf)); + assert_string_equal(buf, "1970-01-01T00:00:00Z"); + + memset(buf, 'X', sizeof(buf)); + isc_time_set(&t, 1450000000, 123000000); + isc_time_formatISO8601(&t, buf, sizeof(buf)); + assert_string_equal(buf, "2015-12-13T09:46:40Z"); +} + +/* print UTC in ISO8601 with milliseconds */ +static void +isc_time_formatISO8601ms_test(void **state) { + isc_result_t result; + isc_time_t t; + char buf[64]; + + UNUSED(state); + + setenv("TZ", "America/Los_Angeles", 1); + result = isc_time_now(&t); + assert_int_equal(result, ISC_R_SUCCESS); + + /* check formatting: yyyy-mm-ddThh:mm:ss.sssZ */ + memset(buf, 'X', sizeof(buf)); + isc_time_formatISO8601ms(&t, buf, sizeof(buf)); + assert_int_equal(strlen(buf), 24); + assert_int_equal(buf[4], '-'); + assert_int_equal(buf[7], '-'); + assert_int_equal(buf[10], 'T'); + assert_int_equal(buf[13], ':'); + assert_int_equal(buf[16], ':'); + assert_int_equal(buf[19], '.'); + assert_int_equal(buf[23], 'Z'); + + /* check time conversion correctness */ + memset(buf, 'X', sizeof(buf)); + isc_time_settoepoch(&t); + isc_time_formatISO8601ms(&t, buf, sizeof(buf)); + assert_string_equal(buf, "1970-01-01T00:00:00.000Z"); + + memset(buf, 'X', sizeof(buf)); + isc_time_set(&t, 1450000000, 123000000); + isc_time_formatISO8601ms(&t, buf, sizeof(buf)); + assert_string_equal(buf, "2015-12-13T09:46:40.123Z"); +} + +/* print UTC in ISO8601 with microseconds */ +static void +isc_time_formatISO8601us_test(void **state) { + isc_result_t result; + isc_time_t t; + char buf[64]; + + UNUSED(state); + + setenv("TZ", "America/Los_Angeles", 1); + result = isc_time_now_hires(&t); + assert_int_equal(result, ISC_R_SUCCESS); + + /* check formatting: yyyy-mm-ddThh:mm:ss.ssssssZ */ + memset(buf, 'X', sizeof(buf)); + isc_time_formatISO8601us(&t, buf, sizeof(buf)); + assert_int_equal(strlen(buf), 27); + assert_int_equal(buf[4], '-'); + assert_int_equal(buf[7], '-'); + assert_int_equal(buf[10], 'T'); + assert_int_equal(buf[13], ':'); + assert_int_equal(buf[16], ':'); + assert_int_equal(buf[19], '.'); + assert_int_equal(buf[26], 'Z'); + + /* check time conversion correctness */ + memset(buf, 'X', sizeof(buf)); + isc_time_settoepoch(&t); + isc_time_formatISO8601us(&t, buf, sizeof(buf)); + assert_string_equal(buf, "1970-01-01T00:00:00.000000Z"); + + memset(buf, 'X', sizeof(buf)); + isc_time_set(&t, 1450000000, 123456000); + isc_time_formatISO8601us(&t, buf, sizeof(buf)); + assert_string_equal(buf, "2015-12-13T09:46:40.123456Z"); +} + +/* print local time in ISO8601 */ +static void +isc_time_formatISO8601L_test(void **state) { + isc_result_t result; + isc_time_t t; + char buf[64]; + + UNUSED(state); + + setenv("TZ", "America/Los_Angeles", 1); + result = isc_time_now(&t); + assert_int_equal(result, ISC_R_SUCCESS); + + /* check formatting: yyyy-mm-ddThh:mm:ss */ + memset(buf, 'X', sizeof(buf)); + isc_time_formatISO8601L(&t, buf, sizeof(buf)); + assert_int_equal(strlen(buf), 19); + assert_int_equal(buf[4], '-'); + assert_int_equal(buf[7], '-'); + assert_int_equal(buf[10], 'T'); + assert_int_equal(buf[13], ':'); + assert_int_equal(buf[16], ':'); + + /* check time conversion correctness */ + memset(buf, 'X', sizeof(buf)); + isc_time_settoepoch(&t); + isc_time_formatISO8601L(&t, buf, sizeof(buf)); + assert_string_equal(buf, "1969-12-31T16:00:00"); + + memset(buf, 'X', sizeof(buf)); + isc_time_set(&t, 1450000000, 123000000); + isc_time_formatISO8601L(&t, buf, sizeof(buf)); + assert_string_equal(buf, "2015-12-13T01:46:40"); +} + +/* print local time in ISO8601 with milliseconds */ +static void +isc_time_formatISO8601Lms_test(void **state) { + isc_result_t result; + isc_time_t t; + char buf[64]; + + UNUSED(state); + + setenv("TZ", "America/Los_Angeles", 1); + result = isc_time_now(&t); + assert_int_equal(result, ISC_R_SUCCESS); + + /* check formatting: yyyy-mm-ddThh:mm:ss.sss */ + memset(buf, 'X', sizeof(buf)); + isc_time_formatISO8601Lms(&t, buf, sizeof(buf)); + assert_int_equal(strlen(buf), 23); + assert_int_equal(buf[4], '-'); + assert_int_equal(buf[7], '-'); + assert_int_equal(buf[10], 'T'); + assert_int_equal(buf[13], ':'); + assert_int_equal(buf[16], ':'); + assert_int_equal(buf[19], '.'); + + /* check time conversion correctness */ + memset(buf, 'X', sizeof(buf)); + isc_time_settoepoch(&t); + isc_time_formatISO8601Lms(&t, buf, sizeof(buf)); + assert_string_equal(buf, "1969-12-31T16:00:00.000"); + + memset(buf, 'X', sizeof(buf)); + isc_time_set(&t, 1450000000, 123000000); + isc_time_formatISO8601Lms(&t, buf, sizeof(buf)); + assert_string_equal(buf, "2015-12-13T01:46:40.123"); +} + +/* print local time in ISO8601 with microseconds */ +static void +isc_time_formatISO8601Lus_test(void **state) { + isc_result_t result; + isc_time_t t; + char buf[64]; + + UNUSED(state); + + setenv("TZ", "America/Los_Angeles", 1); + result = isc_time_now_hires(&t); + assert_int_equal(result, ISC_R_SUCCESS); + + /* check formatting: yyyy-mm-ddThh:mm:ss.ssssss */ + memset(buf, 'X', sizeof(buf)); + isc_time_formatISO8601Lus(&t, buf, sizeof(buf)); + assert_int_equal(strlen(buf), 26); + assert_int_equal(buf[4], '-'); + assert_int_equal(buf[7], '-'); + assert_int_equal(buf[10], 'T'); + assert_int_equal(buf[13], ':'); + assert_int_equal(buf[16], ':'); + assert_int_equal(buf[19], '.'); + + /* check time conversion correctness */ + memset(buf, 'X', sizeof(buf)); + isc_time_settoepoch(&t); + isc_time_formatISO8601Lus(&t, buf, sizeof(buf)); + assert_string_equal(buf, "1969-12-31T16:00:00.000000"); + + memset(buf, 'X', sizeof(buf)); + isc_time_set(&t, 1450000000, 123456000); + isc_time_formatISO8601Lus(&t, buf, sizeof(buf)); + assert_string_equal(buf, "2015-12-13T01:46:40.123456"); +} + +/* print UTC time as yyyymmddhhmmsssss */ +static void +isc_time_formatshorttimestamp_test(void **state) { + isc_result_t result; + isc_time_t t; + char buf[64]; + + UNUSED(state); + + setenv("TZ", "America/Los_Angeles", 1); + result = isc_time_now(&t); + assert_int_equal(result, ISC_R_SUCCESS); + + /* check formatting: yyyymmddhhmmsssss */ + memset(buf, 'X', sizeof(buf)); + isc_time_formatshorttimestamp(&t, buf, sizeof(buf)); + assert_int_equal(strlen(buf), 17); + + /* check time conversion correctness */ + memset(buf, 'X', sizeof(buf)); + isc_time_settoepoch(&t); + isc_time_formatshorttimestamp(&t, buf, sizeof(buf)); + assert_string_equal(buf, "19700101000000000"); + + memset(buf, 'X', sizeof(buf)); + isc_time_set(&t, 1450000000, 123000000); + isc_time_formatshorttimestamp(&t, buf, sizeof(buf)); + assert_string_equal(buf, "20151213094640123"); +} + +int +main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(isc_time_add_test), + cmocka_unit_test(isc_time_sub_test), + cmocka_unit_test(isc_time_parsehttptimestamp_test), + cmocka_unit_test(isc_time_formatISO8601_test), + cmocka_unit_test(isc_time_formatISO8601ms_test), + cmocka_unit_test(isc_time_formatISO8601us_test), + cmocka_unit_test(isc_time_formatISO8601L_test), + cmocka_unit_test(isc_time_formatISO8601Lms_test), + cmocka_unit_test(isc_time_formatISO8601Lus_test), + cmocka_unit_test(isc_time_formatshorttimestamp_test), + }; + + 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 */ diff --git a/lib/isc/tests/timer_test.c b/lib/isc/tests/timer_test.c new file mode 100644 index 0000000..9bf4cf7 --- /dev/null +++ b/lib/isc/tests/timer_test.c @@ -0,0 +1,634 @@ +/* + * 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 <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define UNIT_TESTING +#include <cmocka.h> + +#include <isc/atomic.h> +#include <isc/commandline.h> +#include <isc/condition.h> +#include <isc/mem.h> +#include <isc/platform.h> +#include <isc/print.h> +#include <isc/task.h> +#include <isc/time.h> +#include <isc/timer.h> +#include <isc/util.h> + +#include "../timer.c" +#include "isctest.h" + +/* Set to true (or use -v option) for verbose output */ +static bool verbose = false; + +#define FUDGE_SECONDS 0 /* in absence of clock_getres() */ +#define FUDGE_NANOSECONDS 500000000 /* in absence of clock_getres() */ + +static isc_timer_t *timer = NULL; +static isc_condition_t cv; +static isc_mutex_t mx; +static isc_time_t endtime; +static isc_mutex_t lasttime_mx; +static isc_time_t lasttime; +static int seconds; +static int nanoseconds; +static atomic_int_fast32_t eventcnt; +static atomic_uint_fast32_t errcnt; +static int nevents; + +static int +_setup(void **state) { + isc_result_t result; + + UNUSED(state); + + /* Timer tests require two worker threads */ + result = isc_test_begin(NULL, true, 2); + assert_int_equal(result, ISC_R_SUCCESS); + + atomic_init(&errcnt, ISC_R_SUCCESS); + + return (0); +} + +static int +_teardown(void **state) { + UNUSED(state); + + isc_test_end(); + + return (0); +} + +static void +test_shutdown(isc_task_t *task, isc_event_t *event) { + isc_result_t result; + + UNUSED(task); + + /* + * Signal shutdown processing complete. + */ + result = isc_mutex_lock(&mx); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_condition_signal(&cv); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_mutex_unlock(&mx); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_event_free(&event); +} + +static void +setup_test(isc_timertype_t timertype, isc_time_t *expires, + isc_interval_t *interval, + void (*action)(isc_task_t *, isc_event_t *)) { + isc_result_t result; + isc_task_t *task = NULL; + isc_time_settoepoch(&endtime); + atomic_init(&eventcnt, 0); + + isc_mutex_init(&mx); + isc_mutex_init(&lasttime_mx); + + isc_condition_init(&cv); + + atomic_store(&errcnt, ISC_R_SUCCESS); + + LOCK(&mx); + + result = isc_task_create(taskmgr, 0, &task); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_task_onshutdown(task, test_shutdown, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_mutex_lock(&lasttime_mx); + result = isc_time_now(&lasttime); + isc_mutex_unlock(&lasttime_mx); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_timer_create(timermgr, timertype, expires, interval, task, + action, (void *)timertype, &timer); + assert_int_equal(result, ISC_R_SUCCESS); + + /* + * Wait for shutdown processing to complete. + */ + while (atomic_load(&eventcnt) != nevents) { + result = isc_condition_wait(&cv, &mx); + assert_int_equal(result, ISC_R_SUCCESS); + } + + UNLOCK(&mx); + + assert_int_equal(atomic_load(&errcnt), ISC_R_SUCCESS); + + isc_task_detach(&task); + isc_mutex_destroy(&mx); + isc_mutex_destroy(&lasttime_mx); + (void)isc_condition_destroy(&cv); +} + +static void +set_global_error(isc_result_t result) { + (void)atomic_compare_exchange_strong( + &errcnt, &(uint_fast32_t){ ISC_R_SUCCESS }, result); +} + +static void +subthread_assert_true(bool expected, const char *file, unsigned int line) { + if (!expected) { + printf("# %s:%u subthread_assert_true\n", file, line); + set_global_error(ISC_R_UNEXPECTED); + } +} +#define subthread_assert_true(expected) \ + subthread_assert_true(expected, __FILE__, __LINE__) + +static void +subthread_assert_int_equal(int observed, int expected, const char *file, + unsigned int line) { + if (observed != expected) { + printf("# %s:%u subthread_assert_int_equal(%d != %d)\n", file, + line, observed, expected); + set_global_error(ISC_R_UNEXPECTED); + } +} +#define subthread_assert_int_equal(observed, expected) \ + subthread_assert_int_equal(observed, expected, __FILE__, __LINE__) + +static void +subthread_assert_result_equal(isc_result_t result, isc_result_t expected, + const char *file, unsigned int line) { + if (result != expected) { + printf("# %s:%u subthread_assert_result_equal(%u != %u)\n", + file, line, result, expected); + set_global_error(result); + } +} +#define subthread_assert_result_equal(observed, expected) \ + subthread_assert_result_equal(observed, expected, __FILE__, __LINE__) + +static void +ticktock(isc_task_t *task, isc_event_t *event) { + isc_result_t result; + isc_time_t now; + isc_time_t base; + isc_time_t ulim; + isc_time_t llim; + isc_interval_t interval; + isc_eventtype_t expected_event_type; + + int tick = atomic_fetch_add(&eventcnt, 1); + + if (verbose) { + print_message("# tick %d\n", tick); + } + + expected_event_type = ISC_TIMEREVENT_LIFE; + if ((uintptr_t)event->ev_arg == isc_timertype_ticker) { + expected_event_type = ISC_TIMEREVENT_TICK; + } + + if (event->ev_type != expected_event_type) { + print_error("# expected event type %u, got %u\n", + expected_event_type, event->ev_type); + } + + result = isc_time_now(&now); + subthread_assert_result_equal(result, ISC_R_SUCCESS); + + isc_interval_set(&interval, seconds, nanoseconds); + isc_mutex_lock(&lasttime_mx); + result = isc_time_add(&lasttime, &interval, &base); + isc_mutex_unlock(&lasttime_mx); + subthread_assert_result_equal(result, ISC_R_SUCCESS); + + isc_interval_set(&interval, FUDGE_SECONDS, FUDGE_NANOSECONDS); + result = isc_time_add(&base, &interval, &ulim); + subthread_assert_result_equal(result, ISC_R_SUCCESS); + + result = isc_time_subtract(&base, &interval, &llim); + subthread_assert_result_equal(result, ISC_R_SUCCESS); + + subthread_assert_true(isc_time_compare(&llim, &now) <= 0); + subthread_assert_true(isc_time_compare(&ulim, &now) >= 0); + + isc_interval_set(&interval, 0, 0); + isc_mutex_lock(&lasttime_mx); + result = isc_time_add(&now, &interval, &lasttime); + isc_mutex_unlock(&lasttime_mx); + subthread_assert_result_equal(result, ISC_R_SUCCESS); + + isc_event_free(&event); + + if (atomic_load(&eventcnt) == nevents) { + result = isc_time_now(&endtime); + subthread_assert_result_equal(result, ISC_R_SUCCESS); + isc_timer_destroy(&timer); + isc_task_shutdown(task); + } +} + +/* + * Individual unit tests + */ + +/* timer type ticker */ +static void +ticker(void **state) { + isc_time_t expires; + isc_interval_t interval; + + UNUSED(state); + + nevents = 12; + seconds = 0; + nanoseconds = 500000000; + + isc_interval_set(&interval, seconds, nanoseconds); + isc_time_settoepoch(&expires); + + setup_test(isc_timertype_ticker, &expires, &interval, ticktock); +} + +/* timer type once reaches lifetime */ +static void +once_life(void **state) { + isc_result_t result; + isc_time_t expires; + isc_interval_t interval; + + UNUSED(state); + + nevents = 1; + seconds = 1; + nanoseconds = 100000000; + + isc_interval_set(&interval, seconds, nanoseconds); + result = isc_time_nowplusinterval(&expires, &interval); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_interval_set(&interval, 0, 0); + + setup_test(isc_timertype_once, &expires, &interval, ticktock); +} + +static void +test_idle(isc_task_t *task, isc_event_t *event) { + isc_result_t result; + isc_time_t now; + isc_time_t base; + isc_time_t ulim; + isc_time_t llim; + isc_interval_t interval; + + int tick = atomic_fetch_add(&eventcnt, 1); + + if (verbose) { + print_message("# tick %d\n", tick); + } + + result = isc_time_now(&now); + subthread_assert_result_equal(result, ISC_R_SUCCESS); + + isc_interval_set(&interval, seconds, nanoseconds); + isc_mutex_lock(&lasttime_mx); + result = isc_time_add(&lasttime, &interval, &base); + isc_mutex_unlock(&lasttime_mx); + subthread_assert_result_equal(result, ISC_R_SUCCESS); + + isc_interval_set(&interval, FUDGE_SECONDS, FUDGE_NANOSECONDS); + result = isc_time_add(&base, &interval, &ulim); + subthread_assert_result_equal(result, ISC_R_SUCCESS); + + result = isc_time_subtract(&base, &interval, &llim); + subthread_assert_result_equal(result, ISC_R_SUCCESS); + + subthread_assert_true(isc_time_compare(&llim, &now) <= 0); + subthread_assert_true(isc_time_compare(&ulim, &now) >= 0); + + isc_interval_set(&interval, 0, 0); + isc_mutex_lock(&lasttime_mx); + isc_time_add(&now, &interval, &lasttime); + isc_mutex_unlock(&lasttime_mx); + + subthread_assert_int_equal(event->ev_type, ISC_TIMEREVENT_IDLE); + + isc_event_free(&event); + + isc_timer_destroy(&timer); + isc_task_shutdown(task); +} + +/* timer type once idles out */ +static void +once_idle(void **state) { + isc_result_t result; + isc_time_t expires; + isc_interval_t interval; + + UNUSED(state); + + nevents = 1; + seconds = 1; + nanoseconds = 200000000; + + isc_interval_set(&interval, seconds + 1, nanoseconds); + result = isc_time_nowplusinterval(&expires, &interval); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_interval_set(&interval, seconds, nanoseconds); + + setup_test(isc_timertype_once, &expires, &interval, test_idle); +} + +/* timer reset */ +static void +test_reset(isc_task_t *task, isc_event_t *event) { + isc_result_t result; + isc_time_t now; + isc_time_t base; + isc_time_t ulim; + isc_time_t llim; + isc_time_t expires; + isc_interval_t interval; + + int tick = atomic_fetch_add(&eventcnt, 1); + + if (verbose) { + print_message("# tick %d\n", tick); + } + + /* + * Check expired time. + */ + + result = isc_time_now(&now); + subthread_assert_result_equal(result, ISC_R_SUCCESS); + + isc_interval_set(&interval, seconds, nanoseconds); + isc_mutex_lock(&lasttime_mx); + result = isc_time_add(&lasttime, &interval, &base); + isc_mutex_unlock(&lasttime_mx); + subthread_assert_result_equal(result, ISC_R_SUCCESS); + + isc_interval_set(&interval, FUDGE_SECONDS, FUDGE_NANOSECONDS); + result = isc_time_add(&base, &interval, &ulim); + subthread_assert_result_equal(result, ISC_R_SUCCESS); + + result = isc_time_subtract(&base, &interval, &llim); + subthread_assert_result_equal(result, ISC_R_SUCCESS); + + subthread_assert_true(isc_time_compare(&llim, &now) <= 0); + subthread_assert_true(isc_time_compare(&ulim, &now) >= 0); + + isc_interval_set(&interval, 0, 0); + isc_mutex_lock(&lasttime_mx); + isc_time_add(&now, &interval, &lasttime); + isc_mutex_unlock(&lasttime_mx); + + int _eventcnt = atomic_load(&eventcnt); + + if (_eventcnt < 3) { + subthread_assert_int_equal(event->ev_type, ISC_TIMEREVENT_TICK); + + if (_eventcnt == 2) { + isc_interval_set(&interval, seconds, nanoseconds); + result = isc_time_nowplusinterval(&expires, &interval); + subthread_assert_result_equal(result, ISC_R_SUCCESS); + + isc_interval_set(&interval, 0, 0); + result = isc_timer_reset(timer, isc_timertype_once, + &expires, &interval, false); + subthread_assert_result_equal(result, ISC_R_SUCCESS); + } + + isc_event_free(&event); + } else { + subthread_assert_int_equal(event->ev_type, ISC_TIMEREVENT_LIFE); + + isc_event_free(&event); + isc_timer_destroy(&timer); + isc_task_shutdown(task); + } +} + +static void +reset(void **state) { + isc_time_t expires; + isc_interval_t interval; + + UNUSED(state); + + nevents = 3; + seconds = 0; + nanoseconds = 750000000; + + isc_interval_set(&interval, seconds, nanoseconds); + isc_time_settoepoch(&expires); + + setup_test(isc_timertype_ticker, &expires, &interval, test_reset); +} + +static atomic_bool startflag; +static atomic_bool shutdownflag; +static isc_timer_t *tickertimer = NULL; +static isc_timer_t *oncetimer = NULL; +static isc_task_t *task1 = NULL; +static isc_task_t *task2 = NULL; + +/* + * task1 blocks on mx while events accumulate + * in its queue, until signaled by task2. + */ + +static void +tick_event(isc_task_t *task, isc_event_t *event) { + isc_result_t result; + isc_time_t expires; + isc_interval_t interval; + + UNUSED(task); + + if (!atomic_load(&startflag)) { + if (verbose) { + print_message("# tick_event %d\n", -1); + } + isc_event_free(&event); + return; + } + + int tick = atomic_fetch_add(&eventcnt, 1); + if (verbose) { + print_message("# tick_event %d\n", tick); + } + + /* + * On the first tick, purge all remaining tick events + * and then shut down the task. + */ + if (tick == 0) { + isc_time_settoepoch(&expires); + isc_interval_set(&interval, seconds, 0); + result = isc_timer_reset(tickertimer, isc_timertype_ticker, + &expires, &interval, true); + subthread_assert_result_equal(result, ISC_R_SUCCESS); + + isc_task_shutdown(task); + } + + isc_event_free(&event); +} + +static void +once_event(isc_task_t *task, isc_event_t *event) { + if (verbose) { + print_message("# once_event\n"); + } + + /* + * Allow task1 to start processing events. + */ + atomic_store(&startflag, true); + + isc_event_free(&event); + isc_task_shutdown(task); +} + +static void +shutdown_purge(isc_task_t *task, isc_event_t *event) { + UNUSED(task); + UNUSED(event); + + if (verbose) { + print_message("# shutdown_event\n"); + } + + /* + * Signal shutdown processing complete. + */ + atomic_store(&shutdownflag, 1); + + isc_event_free(&event); +} + +/* timer events purged */ +static void +purge(void **state) { + isc_result_t result; + isc_time_t expires; + isc_interval_t interval; + + UNUSED(state); + + atomic_init(&startflag, 0); + atomic_init(&shutdownflag, 0); + atomic_init(&eventcnt, 0); + seconds = 1; + nanoseconds = 0; + + result = isc_task_create(taskmgr, 0, &task1); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_task_onshutdown(task1, shutdown_purge, NULL); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_task_create(taskmgr, 0, &task2); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_time_settoepoch(&expires); + isc_interval_set(&interval, seconds, 0); + + tickertimer = NULL; + result = isc_timer_create(timermgr, isc_timertype_ticker, &expires, + &interval, task1, tick_event, NULL, + &tickertimer); + assert_int_equal(result, ISC_R_SUCCESS); + + oncetimer = NULL; + + isc_interval_set(&interval, (seconds * 2) + 1, 0); + result = isc_time_nowplusinterval(&expires, &interval); + assert_int_equal(result, ISC_R_SUCCESS); + + isc_interval_set(&interval, 0, 0); + result = isc_timer_create(timermgr, isc_timertype_once, &expires, + &interval, task2, once_event, NULL, + &oncetimer); + assert_int_equal(result, ISC_R_SUCCESS); + + /* + * Wait for shutdown processing to complete. + */ + while (!atomic_load(&shutdownflag)) { + isc_test_nap(1000); + } + + assert_int_equal(atomic_load(&errcnt), ISC_R_SUCCESS); + + assert_int_equal(atomic_load(&eventcnt), 1); + + isc_timer_destroy(&tickertimer); + isc_timer_destroy(&oncetimer); + isc_task_destroy(&task1); + isc_task_destroy(&task2); +} + +int +main(int argc, char **argv) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(ticker), cmocka_unit_test(once_life), + cmocka_unit_test(once_idle), cmocka_unit_test(reset), + cmocka_unit_test(purge), + }; + int c; + + while ((c = isc_commandline_parse(argc, argv, "v")) != -1) { + switch (c) { + case 'v': + verbose = true; + break; + default: + break; + } + } + + return (cmocka_run_group_tests(tests, _setup, _teardown)); +} + +#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 */ diff --git a/lib/isc/tests/uv_wrap.h b/lib/isc/tests/uv_wrap.h new file mode 100644 index 0000000..968a624 --- /dev/null +++ b/lib/isc/tests/uv_wrap.h @@ -0,0 +1,323 @@ +/* + * 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 <time.h> +#include <unistd.h> +#include <uv.h> + +#include <isc/atomic.h> + +#define UNIT_TESTING +#include <cmocka.h> + +#include "../netmgr/uv-compat.h" + +/* uv_udp_t */ + +int +__wrap_uv_udp_open(uv_udp_t *handle, uv_os_sock_t sock); +int +__wrap_uv_udp_bind(uv_udp_t *handle, const struct sockaddr *addr, + unsigned int flags); +#if UV_VERSION_HEX >= UV_VERSION(1, 27, 0) +int +__wrap_uv_udp_connect(uv_udp_t *handle, const struct sockaddr *addr); +int +__wrap_uv_udp_getpeername(const uv_udp_t *handle, struct sockaddr *name, + int *namelen); +#endif /* UV_VERSION_HEX >= UV_VERSION(1, 27, 0) */ +int +__wrap_uv_udp_getsockname(const uv_udp_t *handle, struct sockaddr *name, + int *namelen); +int +__wrap_uv_udp_send(uv_udp_send_t *req, uv_udp_t *handle, const uv_buf_t bufs[], + unsigned int nbufs, const struct sockaddr *addr, + uv_udp_send_cb send_cb); +int +__wrap_uv_udp_recv_start(uv_udp_t *handle, uv_alloc_cb alloc_cb, + uv_udp_recv_cb recv_cb); +int +__wrap_uv_udp_recv_stop(uv_udp_t *handle); + +/* uv_tcp_t */ +int +__wrap_uv_tcp_open(uv_tcp_t *handle, uv_os_sock_t sock); +int +__wrap_uv_tcp_bind(uv_tcp_t *handle, const struct sockaddr *addr, + unsigned int flags); +int +__wrap_uv_tcp_getsockname(const uv_tcp_t *handle, struct sockaddr *name, + int *namelen); +int +__wrap_uv_tcp_getpeername(const uv_tcp_t *handle, struct sockaddr *name, + int *namelen); +int +__wrap_uv_tcp_connect(uv_connect_t *req, uv_tcp_t *handle, + const struct sockaddr *addr, uv_connect_cb cb); + +/* uv_stream_t */ +int +__wrap_uv_listen(uv_stream_t *stream, int backlog, uv_connection_cb cb); +int +__wrap_uv_accept(uv_stream_t *server, uv_stream_t *client); + +/* uv_handle_t */ +int +__wrap_uv_send_buffer_size(uv_handle_t *handle, int *value); +int +__wrap_uv_recv_buffer_size(uv_handle_t *handle, int *value); +int +__wrap_uv_fileno(const uv_handle_t *handle, uv_os_fd_t *fd); + +/* uv_timer_t */ +/* FIXME */ +/* + * uv_timer_init + * uv_timer_start + */ + +static atomic_int __state_uv_udp_open = 0; + +int +__wrap_uv_udp_open(uv_udp_t *handle, uv_os_sock_t sock) { + if (atomic_load(&__state_uv_udp_open) == 0) { + return (uv_udp_open(handle, sock)); + } + return (atomic_load(&__state_uv_udp_open)); +} + +static atomic_int __state_uv_udp_bind = 0; + +int +__wrap_uv_udp_bind(uv_udp_t *handle, const struct sockaddr *addr, + unsigned int flags) { + if (atomic_load(&__state_uv_udp_bind) == 0) { + return (uv_udp_bind(handle, addr, flags)); + } + return (atomic_load(&__state_uv_udp_bind)); +} + +static atomic_int __state_uv_udp_connect = 0; +#if UV_VERSION_HEX >= UV_VERSION(1, 27, 0) +int +__wrap_uv_udp_connect(uv_udp_t *handle, const struct sockaddr *addr) { + if (atomic_load(&__state_uv_udp_connect) == 0) { + return (uv_udp_connect(handle, addr)); + } + return (atomic_load(&__state_uv_udp_connect)); +} +#endif /* UV_VERSION_HEX >= UV_VERSION(1, 27, 0) */ + +static atomic_int __state_uv_udp_getpeername = 0; +#if UV_VERSION_HEX >= UV_VERSION(1, 27, 0) +int +__wrap_uv_udp_getpeername(const uv_udp_t *handle, struct sockaddr *name, + int *namelen) { + if (atomic_load(&__state_uv_udp_getpeername) == 0) { + return (uv_udp_getpeername(handle, name, namelen)); + } + return (atomic_load(&__state_uv_udp_getpeername)); +} +#endif /* UV_VERSION_HEX >= UV_VERSION(1, 27, 0) */ + +static atomic_int __state_uv_udp_getsockname = 0; +int +__wrap_uv_udp_getsockname(const uv_udp_t *handle, struct sockaddr *name, + int *namelen) { + if (atomic_load(&__state_uv_udp_getsockname) == 0) { + return (uv_udp_getsockname(handle, name, namelen)); + } + return (atomic_load(&__state_uv_udp_getsockname)); +} + +static atomic_int __state_uv_udp_send = 0; +int +__wrap_uv_udp_send(uv_udp_send_t *req, uv_udp_t *handle, const uv_buf_t bufs[], + unsigned int nbufs, const struct sockaddr *addr, + uv_udp_send_cb send_cb) { + if (atomic_load(&__state_uv_udp_send) == 0) { + return (uv_udp_send(req, handle, bufs, nbufs, addr, send_cb)); + } + return (atomic_load(&__state_uv_udp_send)); +} + +static atomic_int __state_uv_udp_recv_start = 0; +int +__wrap_uv_udp_recv_start(uv_udp_t *handle, uv_alloc_cb alloc_cb, + uv_udp_recv_cb recv_cb) { + if (atomic_load(&__state_uv_udp_recv_start) == 0) { + return (uv_udp_recv_start(handle, alloc_cb, recv_cb)); + } + return (atomic_load(&__state_uv_udp_recv_start)); +} + +static atomic_int __state_uv_udp_recv_stop = 0; +int +__wrap_uv_udp_recv_stop(uv_udp_t *handle) { + if (atomic_load(&__state_uv_udp_recv_stop) == 0) { + return (uv_udp_recv_stop(handle)); + } + return (atomic_load(&__state_uv_udp_recv_stop)); +} + +static atomic_int __state_uv_tcp_open = 0; +int +__wrap_uv_tcp_open(uv_tcp_t *handle, uv_os_sock_t sock) { + if (atomic_load(&__state_uv_tcp_open) == 0) { + return (uv_tcp_open(handle, sock)); + } + return (atomic_load(&__state_uv_tcp_open)); +} + +static atomic_int __state_uv_tcp_bind = 0; +int +__wrap_uv_tcp_bind(uv_tcp_t *handle, const struct sockaddr *addr, + unsigned int flags) { + if (atomic_load(&__state_uv_tcp_bind) == 0) { + return (uv_tcp_bind(handle, addr, flags)); + } + return (atomic_load(&__state_uv_tcp_bind)); +} + +static atomic_int __state_uv_tcp_getsockname = 0; +int +__wrap_uv_tcp_getsockname(const uv_tcp_t *handle, struct sockaddr *name, + int *namelen) { + if (atomic_load(&__state_uv_tcp_getsockname) == 0) { + return (uv_tcp_getsockname(handle, name, namelen)); + } + return (atomic_load(&__state_uv_tcp_getsockname)); +} + +static atomic_int __state_uv_tcp_getpeername = 0; +int +__wrap_uv_tcp_getpeername(const uv_tcp_t *handle, struct sockaddr *name, + int *namelen) { + if (atomic_load(&__state_uv_tcp_getpeername) == 0) { + return (uv_tcp_getpeername(handle, name, namelen)); + } + return (atomic_load(&__state_uv_tcp_getpeername)); +} + +static atomic_int __state_uv_tcp_connect = 0; +int +__wrap_uv_tcp_connect(uv_connect_t *req, uv_tcp_t *handle, + const struct sockaddr *addr, uv_connect_cb cb) { + if (atomic_load(&__state_uv_tcp_connect) == 0) { + return (uv_tcp_connect(req, handle, addr, cb)); + } + return (atomic_load(&__state_uv_tcp_connect)); +} + +static atomic_int __state_uv_listen = 0; +int +__wrap_uv_listen(uv_stream_t *stream, int backlog, uv_connection_cb cb) { + if (atomic_load(&__state_uv_listen) == 0) { + return (uv_listen(stream, backlog, cb)); + } + return (atomic_load(&__state_uv_listen)); +} + +static atomic_int __state_uv_accept = 0; +int +__wrap_uv_accept(uv_stream_t *server, uv_stream_t *client) { + if (atomic_load(&__state_uv_accept) == 0) { + return (uv_accept(server, client)); + } + return (atomic_load(&__state_uv_accept)); +} + +static atomic_int __state_uv_send_buffer_size = 0; +int +__wrap_uv_send_buffer_size(uv_handle_t *handle, int *value) { + if (atomic_load(&__state_uv_send_buffer_size) == 0) { + return (uv_send_buffer_size(handle, value)); + } + return (atomic_load(&__state_uv_send_buffer_size)); +} + +static atomic_int __state_uv_recv_buffer_size = 0; +int +__wrap_uv_recv_buffer_size(uv_handle_t *handle, int *value) { + if (atomic_load(&__state_uv_recv_buffer_size) == 0) { + return (uv_recv_buffer_size(handle, value)); + } + return (atomic_load(&__state_uv_recv_buffer_size)); +} + +static atomic_int __state_uv_fileno = 0; +int +__wrap_uv_fileno(const uv_handle_t *handle, uv_os_fd_t *fd) { + if (atomic_load(&__state_uv_fileno) == 0) { + return (uv_fileno(handle, fd)); + } + return (atomic_load(&__state_uv_fileno)); +} + +#define uv_udp_open(...) __wrap_uv_udp_open(__VA_ARGS__) +#define uv_udp_bind(...) __wrap_uv_udp_bind(__VA_ARGS__) +#if UV_VERSION_HEX >= UV_VERSION(1, 27, 0) +#define uv_udp_connect(...) __wrap_uv_udp_connect(__VA_ARGS__) +#define uv_udp_getpeername(...) __wrap_uv_udp_getpeername(__VA_ARGS__) +#endif /* UV_VERSION_HEX >= UV_VERSION(1, 27, 0) */ +#define uv_udp_getsockname(...) __wrap_uv_udp_getsockname(__VA_ARGS__) +#define uv_udp_send(...) __wrap_uv_udp_send(__VA_ARGS__) +#define uv_udp_recv_start(...) __wrap_uv_udp_recv_start(__VA_ARGS__) +#define uv_udp_recv_stop(...) __wrap_uv_udp_recv_stop(__VA_ARGS__) + +#define uv_tcp_open(...) __wrap_uv_tcp_open(__VA_ARGS__) +#define uv_tcp_bind(...) __wrap_uv_tcp_bind(__VA_ARGS__) +#define uv_tcp_getsockname(...) __wrap_uv_tcp_getsockname(__VA_ARGS__) +#define uv_tcp_getpeername(...) __wrap_uv_tcp_getpeername(__VA_ARGS__) +#define uv_tcp_connect(...) __wrap_uv_tcp_connect(__VA_ARGS__) + +#define uv_listen(...) __wrap_uv_listen(__VA_ARGS__) +#define uv_accept(...) __wrap_uv_accept(__VA_ARGS__) + +#define uv_send_buffer_size(...) __wrap_uv_send_buffer_size(__VA_ARGS__) +#define uv_recv_buffer_size(...) __wrap_uv_recv_buffer_size(__VA_ARGS__) +#define uv_fileno(...) __wrap_uv_fileno(__VA_ARGS__) + +#define RESET_RETURN \ + { \ + atomic_store(&__state_uv_udp_open, 0); \ + atomic_store(&__state_uv_udp_bind, 0); \ + atomic_store(&__state_uv_udp_connect, 0); \ + atomic_store(&__state_uv_udp_getpeername, 0); \ + atomic_store(&__state_uv_udp_getsockname, 0); \ + atomic_store(&__state_uv_udp_send, 0); \ + atomic_store(&__state_uv_udp_recv_start, 0); \ + atomic_store(&__state_uv_udp_recv_stop, 0); \ + atomic_store(&__state_uv_tcp_open, 0); \ + atomic_store(&__state_uv_tcp_bind, 0); \ + atomic_store(&__state_uv_tcp_getpeername, 0); \ + atomic_store(&__state_uv_tcp_getsockname, 0); \ + atomic_store(&__state_uv_tcp_connect, 0); \ + atomic_store(&__state_uv_listen, 0); \ + atomic_store(&__state_uv_accept, 0); \ + atomic_store(&__state_uv_send_buffer_size, 0); \ + atomic_store(&__state_uv_recv_buffer_size, 0); \ + atomic_store(&__state_uv_fileno, 0); \ + } + +#define WILL_RETURN(func, value) atomic_store(&__state_##func, value) + +#endif /* HAVE_CMOCKA */ diff --git a/lib/isc/timer.c b/lib/isc/timer.c new file mode 100644 index 0000000..e659f58 --- /dev/null +++ b/lib/isc/timer.c @@ -0,0 +1,753 @@ +/* + * 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. + */ + +/*! \file */ + +#include <stdbool.h> + +#include <isc/app.h> +#include <isc/condition.h> +#include <isc/heap.h> +#include <isc/log.h> +#include <isc/magic.h> +#include <isc/mem.h> +#include <isc/once.h> +#include <isc/platform.h> +#include <isc/print.h> +#include <isc/refcount.h> +#include <isc/task.h> +#include <isc/thread.h> +#include <isc/time.h> +#include <isc/timer.h> +#include <isc/util.h> + +#ifdef OPENSSL_LEAKS +#include <openssl/err.h> +#endif /* ifdef OPENSSL_LEAKS */ + +#ifdef ISC_TIMER_TRACE +#define XTRACE(s) fprintf(stderr, "%s\n", (s)) +#define XTRACEID(s, t) fprintf(stderr, "%s %p\n", (s), (t)) +#define XTRACETIME(s, d) \ + fprintf(stderr, "%s %u.%09u\n", (s), (d).seconds, (d).nanoseconds) +#define XTRACETIME2(s, d, n) \ + fprintf(stderr, "%s %u.%09u %u.%09u\n", (s), (d).seconds, \ + (d).nanoseconds, (n).seconds, (n).nanoseconds) +#define XTRACETIMER(s, t, d) \ + fprintf(stderr, "%s %p %u.%09u\n", (s), (t), (d).seconds, \ + (d).nanoseconds) +#else /* ifdef ISC_TIMER_TRACE */ +#define XTRACE(s) +#define XTRACEID(s, t) +#define XTRACETIME(s, d) +#define XTRACETIME2(s, d, n) +#define XTRACETIMER(s, t, d) +#endif /* ISC_TIMER_TRACE */ + +#define TIMER_MAGIC ISC_MAGIC('T', 'I', 'M', 'R') +#define VALID_TIMER(t) ISC_MAGIC_VALID(t, TIMER_MAGIC) + +struct isc_timer { + /*! Not locked. */ + unsigned int magic; + isc_timermgr_t *manager; + isc_mutex_t lock; + /*! Locked by timer lock. */ + isc_time_t idle; + ISC_LIST(isc_timerevent_t) active; + /*! Locked by manager lock. */ + isc_timertype_t type; + isc_time_t expires; + isc_interval_t interval; + isc_task_t *task; + isc_taskaction_t action; + void *arg; + unsigned int index; + isc_time_t due; + LINK(isc_timer_t) link; +}; + +#define TIMER_MANAGER_MAGIC ISC_MAGIC('T', 'I', 'M', 'M') +#define VALID_MANAGER(m) ISC_MAGIC_VALID(m, TIMER_MANAGER_MAGIC) + +struct isc_timermgr { + /* Not locked. */ + unsigned int magic; + isc_mem_t *mctx; + isc_mutex_t lock; + /* Locked by manager lock. */ + bool done; + LIST(isc_timer_t) timers; + unsigned int nscheduled; + isc_time_t due; + isc_condition_t wakeup; + isc_thread_t thread; + isc_heap_t *heap; +}; + +void +isc_timermgr_poke(isc_timermgr_t *manager0); + +static isc_result_t +schedule(isc_timer_t *timer, isc_time_t *now, bool signal_ok) { + isc_timermgr_t *manager; + isc_time_t due; + int cmp; + + /*! + * Note: the caller must ensure locking. + */ + + REQUIRE(timer->type != isc_timertype_inactive); + + manager = timer->manager; + + /* + * Compute the new due time. + */ + if (timer->type != isc_timertype_once) { + isc_result_t result = isc_time_add(now, &timer->interval, &due); + if (result != ISC_R_SUCCESS) { + return (result); + } + if (timer->type == isc_timertype_limited && + isc_time_compare(&timer->expires, &due) < 0) + { + due = timer->expires; + } + } else { + if (isc_time_isepoch(&timer->idle)) { + due = timer->expires; + } else if (isc_time_isepoch(&timer->expires)) { + due = timer->idle; + } else if (isc_time_compare(&timer->idle, &timer->expires) < 0) + { + due = timer->idle; + } else { + due = timer->expires; + } + } + + /* + * Schedule the timer. + */ + + if (timer->index > 0) { + /* + * Already scheduled. + */ + cmp = isc_time_compare(&due, &timer->due); + timer->due = due; + switch (cmp) { + case -1: + isc_heap_increased(manager->heap, timer->index); + break; + case 1: + isc_heap_decreased(manager->heap, timer->index); + break; + case 0: + /* Nothing to do. */ + break; + } + } else { + timer->due = due; + isc_heap_insert(manager->heap, timer); + manager->nscheduled++; + } + + XTRACETIMER("schedule", timer, due); + + /* + * If this timer is at the head of the queue, we need to ensure + * that we won't miss it if it has a more recent due time than + * the current "next" timer. We do this either by waking up the + * run thread, or explicitly setting the value in the manager. + */ + + if (timer->index == 1 && signal_ok) { + XTRACE("signal (schedule)"); + SIGNAL(&manager->wakeup); + } + + return (ISC_R_SUCCESS); +} + +static void +deschedule(isc_timer_t *timer) { + bool need_wakeup = false; + isc_timermgr_t *manager; + + /* + * The caller must ensure locking. + */ + + manager = timer->manager; + if (timer->index > 0) { + if (timer->index == 1) { + need_wakeup = true; + } + isc_heap_delete(manager->heap, timer->index); + timer->index = 0; + INSIST(manager->nscheduled > 0); + manager->nscheduled--; + if (need_wakeup) { + XTRACE("signal (deschedule)"); + SIGNAL(&manager->wakeup); + } + } +} + +static void +timerevent_unlink(isc_timer_t *timer, isc_timerevent_t *event) { + REQUIRE(ISC_LINK_LINKED(event, ev_timerlink)); + ISC_LIST_UNLINK(timer->active, event, ev_timerlink); +} + +static void +timerevent_destroy(isc_event_t *event0) { + isc_timer_t *timer = event0->ev_destroy_arg; + isc_timerevent_t *event = (isc_timerevent_t *)event0; + + LOCK(&timer->lock); + if (ISC_LINK_LINKED(event, ev_timerlink)) { + /* The event was unlinked via timer_purge() */ + timerevent_unlink(timer, event); + } + UNLOCK(&timer->lock); + + isc_mem_put(timer->manager->mctx, event, event0->ev_size); +} + +static void +timer_purge(isc_timer_t *timer) { + isc_timerevent_t *event = NULL; + + while ((event = ISC_LIST_HEAD(timer->active)) != NULL) { + timerevent_unlink(timer, event); + UNLOCK(&timer->lock); + (void)isc_task_purgeevent(timer->task, (isc_event_t *)event); + LOCK(&timer->lock); + } +} + +isc_result_t +isc_timer_create(isc_timermgr_t *manager, isc_timertype_t type, + const isc_time_t *expires, const isc_interval_t *interval, + isc_task_t *task, isc_taskaction_t action, void *arg, + isc_timer_t **timerp) { + REQUIRE(VALID_MANAGER(manager)); + REQUIRE(task != NULL); + REQUIRE(action != NULL); + + isc_timer_t *timer; + isc_result_t result; + isc_time_t now; + + /* + * Create a new 'type' timer managed by 'manager'. The timers + * parameters are specified by 'expires' and 'interval'. Events + * will be posted to 'task' and when dispatched 'action' will be + * called with 'arg' as the arg value. The new timer is returned + * in 'timerp'. + */ + if (expires == NULL) { + expires = isc_time_epoch; + } + if (interval == NULL) { + interval = isc_interval_zero; + } + REQUIRE(type == isc_timertype_inactive || + !(isc_time_isepoch(expires) && isc_interval_iszero(interval))); + REQUIRE(timerp != NULL && *timerp == NULL); + REQUIRE(type != isc_timertype_limited || + !(isc_time_isepoch(expires) || isc_interval_iszero(interval))); + + /* + * Get current time. + */ + if (type != isc_timertype_inactive) { + TIME_NOW(&now); + } else { + /* + * We don't have to do this, but it keeps the compiler from + * complaining about "now" possibly being used without being + * set, even though it will never actually happen. + */ + isc_time_settoepoch(&now); + } + + timer = isc_mem_get(manager->mctx, sizeof(*timer)); + + timer->manager = manager; + + if (type == isc_timertype_once && !isc_interval_iszero(interval)) { + result = isc_time_add(&now, interval, &timer->idle); + if (result != ISC_R_SUCCESS) { + isc_mem_put(manager->mctx, timer, sizeof(*timer)); + return (result); + } + } else { + isc_time_settoepoch(&timer->idle); + } + + timer->type = type; + timer->expires = *expires; + timer->interval = *interval; + timer->task = NULL; + isc_task_attach(task, &timer->task); + timer->action = action; + /* + * Removing the const attribute from "arg" is the best of two + * evils here. If the timer->arg member is made const, then + * it affects a great many recipients of the timer event + * which did not pass in an "arg" that was truly const. + * Changing isc_timer_create() to not have "arg" prototyped as const, + * though, can cause compilers warnings for calls that *do* + * have a truly const arg. The caller will have to carefully + * keep track of whether arg started as a true const. + */ + DE_CONST(arg, timer->arg); + timer->index = 0; + isc_mutex_init(&timer->lock); + ISC_LINK_INIT(timer, link); + + ISC_LIST_INIT(timer->active); + + timer->magic = TIMER_MAGIC; + + LOCK(&manager->lock); + + /* + * Note we don't have to lock the timer like we normally would because + * there are no external references to it yet. + */ + + if (type != isc_timertype_inactive) { + result = schedule(timer, &now, true); + } else { + result = ISC_R_SUCCESS; + } + if (result == ISC_R_SUCCESS) { + *timerp = timer; + APPEND(manager->timers, timer, link); + } + + UNLOCK(&manager->lock); + + if (result != ISC_R_SUCCESS) { + timer->magic = 0; + isc_mutex_destroy(&timer->lock); + isc_task_detach(&timer->task); + isc_mem_put(manager->mctx, timer, sizeof(*timer)); + return (result); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_timer_reset(isc_timer_t *timer, isc_timertype_t type, + const isc_time_t *expires, const isc_interval_t *interval, + bool purge) { + isc_time_t now; + isc_timermgr_t *manager; + isc_result_t result; + + /* + * Change the timer's type, expires, and interval values to the given + * values. If 'purge' is true, any pending events from this timer + * are purged from its task's event queue. + */ + + REQUIRE(VALID_TIMER(timer)); + manager = timer->manager; + REQUIRE(VALID_MANAGER(manager)); + + if (expires == NULL) { + expires = isc_time_epoch; + } + if (interval == NULL) { + interval = isc_interval_zero; + } + REQUIRE(type == isc_timertype_inactive || + !(isc_time_isepoch(expires) && isc_interval_iszero(interval))); + REQUIRE(type != isc_timertype_limited || + !(isc_time_isepoch(expires) || isc_interval_iszero(interval))); + + /* + * Get current time. + */ + if (type != isc_timertype_inactive) { + TIME_NOW(&now); + } else { + /* + * We don't have to do this, but it keeps the compiler from + * complaining about "now" possibly being used without being + * set, even though it will never actually happen. + */ + isc_time_settoepoch(&now); + } + + LOCK(&manager->lock); + LOCK(&timer->lock); + + if (purge) { + timer_purge(timer); + } + timer->type = type; + timer->expires = *expires; + timer->interval = *interval; + if (type == isc_timertype_once && !isc_interval_iszero(interval)) { + result = isc_time_add(&now, interval, &timer->idle); + } else { + isc_time_settoepoch(&timer->idle); + result = ISC_R_SUCCESS; + } + + if (result == ISC_R_SUCCESS) { + if (type == isc_timertype_inactive) { + deschedule(timer); + result = ISC_R_SUCCESS; + } else { + result = schedule(timer, &now, true); + } + } + + UNLOCK(&timer->lock); + UNLOCK(&manager->lock); + + return (result); +} + +isc_timertype_t +isc_timer_gettype(isc_timer_t *timer) { + isc_timertype_t t; + + REQUIRE(VALID_TIMER(timer)); + + LOCK(&timer->lock); + t = timer->type; + UNLOCK(&timer->lock); + + return (t); +} + +isc_result_t +isc_timer_touch(isc_timer_t *timer) { + isc_result_t result; + isc_time_t now; + + /* + * Set the last-touched time of 'timer' to the current time. + */ + + REQUIRE(VALID_TIMER(timer)); + + LOCK(&timer->lock); + + /* + * We'd like to + * + * REQUIRE(timer->type == isc_timertype_once); + * + * but we cannot without locking the manager lock too, which we + * don't want to do. + */ + + TIME_NOW(&now); + result = isc_time_add(&now, &timer->interval, &timer->idle); + + UNLOCK(&timer->lock); + + return (result); +} + +void +isc_timer_destroy(isc_timer_t **timerp) { + isc_timer_t *timer = NULL; + isc_timermgr_t *manager = NULL; + + REQUIRE(timerp != NULL && VALID_TIMER(*timerp)); + + timer = *timerp; + *timerp = NULL; + + manager = timer->manager; + + LOCK(&manager->lock); + + LOCK(&timer->lock); + timer_purge(timer); + deschedule(timer); + UNLOCK(&timer->lock); + + UNLINK(manager->timers, timer, link); + + UNLOCK(&manager->lock); + + isc_task_detach(&timer->task); + isc_mutex_destroy(&timer->lock); + timer->magic = 0; + isc_mem_put(manager->mctx, timer, sizeof(*timer)); +} + +static void +timer_post_event(isc_timermgr_t *manager, isc_timer_t *timer, + isc_eventtype_t type) { + isc_timerevent_t *event; + XTRACEID("posting", timer); + + event = (isc_timerevent_t *)isc_event_allocate( + manager->mctx, timer, type, timer->action, timer->arg, + sizeof(*event)); + + ISC_LINK_INIT(event, ev_timerlink); + ((isc_event_t *)event)->ev_destroy = timerevent_destroy; + ((isc_event_t *)event)->ev_destroy_arg = timer; + + event->due = timer->due; + + LOCK(&timer->lock); + ISC_LIST_APPEND(timer->active, event, ev_timerlink); + UNLOCK(&timer->lock); + + isc_task_send(timer->task, ISC_EVENT_PTR(&event)); +} + +static void +dispatch(isc_timermgr_t *manager, isc_time_t *now) { + bool done = false, post_event, need_schedule; + isc_eventtype_t type = 0; + isc_timer_t *timer; + isc_result_t result; + bool idle; + + /*! + * The caller must be holding the manager lock. + */ + + while (manager->nscheduled > 0 && !done) { + timer = isc_heap_element(manager->heap, 1); + INSIST(timer != NULL && timer->type != isc_timertype_inactive); + if (isc_time_compare(now, &timer->due) >= 0) { + if (timer->type == isc_timertype_ticker) { + type = ISC_TIMEREVENT_TICK; + post_event = true; + need_schedule = true; + } else if (timer->type == isc_timertype_limited) { + int cmp; + cmp = isc_time_compare(now, &timer->expires); + if (cmp >= 0) { + type = ISC_TIMEREVENT_LIFE; + post_event = true; + need_schedule = false; + } else { + type = ISC_TIMEREVENT_TICK; + post_event = true; + need_schedule = true; + } + } else if (!isc_time_isepoch(&timer->expires) && + isc_time_compare(now, &timer->expires) >= 0) + { + type = ISC_TIMEREVENT_LIFE; + post_event = true; + need_schedule = false; + } else { + idle = false; + + LOCK(&timer->lock); + if (!isc_time_isepoch(&timer->idle) && + isc_time_compare(now, &timer->idle) >= 0) + { + idle = true; + } + UNLOCK(&timer->lock); + if (idle) { + type = ISC_TIMEREVENT_IDLE; + post_event = true; + need_schedule = false; + } else { + /* + * Idle timer has been touched; + * reschedule. + */ + XTRACEID("idle reschedule", timer); + post_event = false; + need_schedule = true; + } + } + + if (post_event) { + timer_post_event(manager, timer, type); + } + + timer->index = 0; + isc_heap_delete(manager->heap, 1); + manager->nscheduled--; + + if (need_schedule) { + result = schedule(timer, now, false); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "%s: %u", + "couldn't schedule " + "timer", + result); + } + } + } else { + manager->due = timer->due; + done = true; + } + } +} + +static isc_threadresult_t +#ifdef _WIN32 /* XXXDCL */ + WINAPI +#endif /* ifdef _WIN32 */ + run(void *uap) { + isc_timermgr_t *manager = uap; + isc_time_t now; + isc_result_t result; + + LOCK(&manager->lock); + while (!manager->done) { + TIME_NOW(&now); + + XTRACETIME("running", now); + + dispatch(manager, &now); + + if (manager->nscheduled > 0) { + XTRACETIME2("waituntil", manager->due, now); + result = WAITUNTIL(&manager->wakeup, &manager->lock, + &manager->due); + INSIST(result == ISC_R_SUCCESS || + result == ISC_R_TIMEDOUT); + } else { + XTRACETIME("wait", now); + WAIT(&manager->wakeup, &manager->lock); + } + XTRACE("wakeup"); + } + UNLOCK(&manager->lock); + +#ifdef OPENSSL_LEAKS + ERR_remove_state(0); +#endif /* ifdef OPENSSL_LEAKS */ + + return ((isc_threadresult_t)0); +} + +static bool +sooner(void *v1, void *v2) { + isc_timer_t *t1, *t2; + + t1 = v1; + t2 = v2; + REQUIRE(VALID_TIMER(t1)); + REQUIRE(VALID_TIMER(t2)); + + if (isc_time_compare(&t1->due, &t2->due) < 0) { + return (true); + } + return (false); +} + +static void +set_index(void *what, unsigned int index) { + isc_timer_t *timer; + + REQUIRE(VALID_TIMER(what)); + timer = what; + + timer->index = index; +} + +isc_result_t +isc_timermgr_create(isc_mem_t *mctx, isc_timermgr_t **managerp) { + isc_timermgr_t *manager; + + /* + * Create a timer manager. + */ + + REQUIRE(managerp != NULL && *managerp == NULL); + + manager = isc_mem_get(mctx, sizeof(*manager)); + + manager->magic = TIMER_MANAGER_MAGIC; + manager->mctx = NULL; + manager->done = false; + INIT_LIST(manager->timers); + manager->nscheduled = 0; + isc_time_settoepoch(&manager->due); + manager->heap = NULL; + isc_heap_create(mctx, sooner, set_index, 0, &manager->heap); + isc_mutex_init(&manager->lock); + isc_mem_attach(mctx, &manager->mctx); + isc_condition_init(&manager->wakeup); + isc_thread_create(run, manager, &manager->thread); + isc_thread_setname(manager->thread, "isc-timer"); + + *managerp = manager; + + return (ISC_R_SUCCESS); +} + +void +isc_timermgr_poke(isc_timermgr_t *manager) { + REQUIRE(VALID_MANAGER(manager)); + + SIGNAL(&manager->wakeup); +} + +void +isc_timermgr_destroy(isc_timermgr_t **managerp) { + isc_timermgr_t *manager; + + /* + * Destroy a timer manager. + */ + + REQUIRE(managerp != NULL); + manager = *managerp; + REQUIRE(VALID_MANAGER(manager)); + + LOCK(&manager->lock); + + REQUIRE(EMPTY(manager->timers)); + manager->done = true; + + XTRACE("signal (destroy)"); + SIGNAL(&manager->wakeup); + + UNLOCK(&manager->lock); + + /* + * Wait for thread to exit. + */ + isc_thread_join(manager->thread, NULL); + + /* + * Clean up. + */ + (void)isc_condition_destroy(&manager->wakeup); + isc_mutex_destroy(&manager->lock); + isc_heap_destroy(&manager->heap); + manager->magic = 0; + isc_mem_putanddetach(&manager->mctx, manager, sizeof(*manager)); + + *managerp = NULL; +} diff --git a/lib/isc/timer_p.h b/lib/isc/timer_p.h new file mode 100644 index 0000000..84c5b2f --- /dev/null +++ b/lib/isc/timer_p.h @@ -0,0 +1,25 @@ +/* + * 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. + */ + +#ifndef ISC_TIMER_P_H +#define ISC_TIMER_P_H + +/*! \file */ + +isc_result_t +isc__timermgr_nextevent(isc_timermgr_t *timermgr, isc_time_t *when); + +void +isc__timermgr_dispatch(isc_timermgr_t *timermgr); + +#endif /* ISC_TIMER_P_H */ diff --git a/lib/isc/tls.c b/lib/isc/tls.c new file mode 100644 index 0000000..859c73c --- /dev/null +++ b/lib/isc/tls.c @@ -0,0 +1,161 @@ +/* + * 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. + */ + +#include <inttypes.h> + +#include <openssl/bn.h> +#include <openssl/conf.h> +#include <openssl/crypto.h> +#include <openssl/err.h> +#include <openssl/opensslv.h> +#include <openssl/rand.h> +#include <openssl/rsa.h> + +#include <isc/atomic.h> +#include <isc/log.h> +#include <isc/mutex.h> +#include <isc/mutexblock.h> +#include <isc/once.h> +#include <isc/thread.h> +#include <isc/util.h> + +#include "openssl_shim.h" +#include "tls_p.h" + +static isc_once_t init_once = ISC_ONCE_INIT; +static isc_once_t shut_once = ISC_ONCE_INIT; +static atomic_bool init_done = false; +static atomic_bool shut_done = false; + +#if OPENSSL_VERSION_NUMBER < 0x10100000L +static isc_mutex_t *locks = NULL; +static int nlocks; + +static void +isc__tls_lock_callback(int mode, int type, const char *file, int line) { + UNUSED(file); + UNUSED(line); + if ((mode & CRYPTO_LOCK) != 0) { + LOCK(&locks[type]); + } else { + UNLOCK(&locks[type]); + } +} + +static void +isc__tls_set_thread_id(CRYPTO_THREADID *id) { + CRYPTO_THREADID_set_numeric(id, (unsigned long)isc_thread_self()); +} +#endif + +static void +tls_initialize(void) { + REQUIRE(!atomic_load(&init_done)); + +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + RUNTIME_CHECK(OPENSSL_init_ssl(OPENSSL_INIT_ENGINE_ALL_BUILTIN | + OPENSSL_INIT_LOAD_CONFIG, + NULL) == 1); +#else + nlocks = CRYPTO_num_locks(); + /* + * We can't use isc_mem API here, because it's called too + * early and when the isc_mem_debugging flags are changed + * later and ISC_MEM_DEBUGSIZE or ISC_MEM_DEBUGCTX flags are + * added, neither isc_mem_put() nor isc_mem_free() can be used + * to free up the memory allocated here because the flags were + * not set when calling isc_mem_get() or isc_mem_allocate() + * here. + * + * Actually, since this is a single allocation at library load + * and deallocation at library unload, using the standard + * allocator without the tracking is fine for this purpose. + */ + locks = calloc(nlocks, sizeof(locks[0])); + isc_mutexblock_init(locks, nlocks); + CRYPTO_set_locking_callback(isc__tls_lock_callback); + CRYPTO_THREADID_set_callback(isc__tls_set_thread_id); + + CRYPTO_malloc_init(); + ERR_load_crypto_strings(); + SSL_load_error_strings(); + SSL_library_init(); + +#if !defined(OPENSSL_NO_ENGINE) + ENGINE_load_builtin_engines(); +#endif + OpenSSL_add_all_algorithms(); + OPENSSL_load_builtin_modules(); + + CONF_modules_load_file(NULL, NULL, + CONF_MFLAGS_DEFAULT_SECTION | + CONF_MFLAGS_IGNORE_MISSING_FILE); +#endif + + /* Protect ourselves against unseeded PRNG */ + if (RAND_status() != 1) { + FATAL_ERROR(__FILE__, __LINE__, + "OpenSSL pseudorandom number generator " + "cannot be initialized (see the `PRNG not " + "seeded' message in the OpenSSL FAQ)"); + } + + REQUIRE(atomic_compare_exchange_strong(&init_done, &(bool){ false }, + true)); +} + +void +isc__tls_initialize(void) { + isc_result_t result = isc_once_do(&init_once, tls_initialize); + REQUIRE(result == ISC_R_SUCCESS); + REQUIRE(atomic_load(&init_done)); +} + +static void +tls_shutdown(void) { + REQUIRE(atomic_load(&init_done)); + REQUIRE(!atomic_load(&shut_done)); + +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + OPENSSL_cleanup(); +#else + CONF_modules_unload(1); + OBJ_cleanup(); + EVP_cleanup(); +#if !defined(OPENSSL_NO_ENGINE) + ENGINE_cleanup(); +#endif + CRYPTO_cleanup_all_ex_data(); + ERR_remove_thread_state(NULL); + RAND_cleanup(); + ERR_free_strings(); + + CRYPTO_set_locking_callback(NULL); + + if (locks != NULL) { + isc_mutexblock_destroy(locks, nlocks); + free(locks); + locks = NULL; + } +#endif + + REQUIRE(atomic_compare_exchange_strong(&shut_done, &(bool){ false }, + true)); +} + +void +isc__tls_shutdown(void) { + isc_result_t result = isc_once_do(&shut_once, tls_shutdown); + REQUIRE(result == ISC_R_SUCCESS); + REQUIRE(atomic_load(&shut_done)); +} diff --git a/lib/isc/tls_p.h b/lib/isc/tls_p.h new file mode 100644 index 0000000..c0bd693 --- /dev/null +++ b/lib/isc/tls_p.h @@ -0,0 +1,20 @@ +/* + * 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. + */ + +#pragma once + +void +isc__tls_initialize(void); + +void +isc__tls_shutdown(void); diff --git a/lib/isc/tm.c b/lib/isc/tm.c new file mode 100644 index 0000000..19a7bee --- /dev/null +++ b/lib/isc/tm.c @@ -0,0 +1,469 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 AND BSD-2-Clause + * + * 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. + */ + +/*- + * Copyright (c) 1997, 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code was contributed to The NetBSD Foundation by Klaus Klein. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include <isc/tm.h> +#include <isc/util.h> + +/* + * Portable conversion routines for struct tm, replacing + * timegm() and strptime(), which are not available on all + * platforms and don't always behave the same way when they + * are. + */ + +/* + * We do not implement alternate representations. However, we always + * check whether a given modifier is allowed for a certain conversion. + */ +#define ALT_E 0x01 +#define ALT_O 0x02 +#define LEGAL_ALT(x) \ + { \ + if ((alt_format & ~(x)) != 0) \ + return ((0)); \ + } + +#ifndef TM_YEAR_BASE +#define TM_YEAR_BASE 1900 +#endif /* ifndef TM_YEAR_BASE */ + +static const char *day[7] = { "Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday" }; +static const char *abday[7] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" +}; +static const char *mon[12] = { + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" +}; +static const char *abmon[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; +static const char *am_pm[2] = { "AM", "PM" }; + +static int +conv_num(const char **buf, int *dest, int llim, int ulim) { + int result = 0; + + /* The limit also determines the number of valid digits. */ + int rulim = ulim; + + if (!isdigit((unsigned char)**buf)) { + return (0); + } + + do { + result *= 10; + result += *(*buf)++ - '0'; + rulim /= 10; + } while ((result * 10 <= ulim) && rulim && **buf >= '0' && + **buf <= '9'); + + if (result < llim || result > ulim) { + return (0); + } + + *dest = result; + return (1); +} + +time_t +isc_tm_timegm(struct tm *tm) { + time_t ret; + int i, yday = 0, leapday; + int mdays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30 }; + + leapday = ((((tm->tm_year + 1900) % 4) == 0 && + ((tm->tm_year + 1900) % 100) != 0) || + ((tm->tm_year + 1900) % 400) == 0) + ? 1 + : 0; + mdays[1] += leapday; + + yday = tm->tm_mday - 1; + for (i = 1; i <= tm->tm_mon; i++) { + yday += mdays[i - 1]; + } + ret = tm->tm_sec + (60 * tm->tm_min) + (3600 * tm->tm_hour) + + (86400 * + (yday + ((tm->tm_year - 70) * 365) + ((tm->tm_year - 69) / 4) - + ((tm->tm_year - 1) / 100) + ((tm->tm_year + 299) / 400))); + return (ret); +} + +char * +isc_tm_strptime(const char *buf, const char *fmt, struct tm *tm) { + char c, *ret; + const char *bp; + size_t len = 0; + int alt_format, i, split_year = 0; + + REQUIRE(buf != NULL); + REQUIRE(fmt != NULL); + REQUIRE(tm != NULL); + + memset(tm, 0, sizeof(struct tm)); + + bp = buf; + + while ((c = *fmt) != '\0') { + /* Clear `alternate' modifier prior to new conversion. */ + alt_format = 0; + + /* Eat up white-space. */ + if (isspace((unsigned char)c)) { + while (isspace((unsigned char)*bp)) { + bp++; + } + + fmt++; + continue; + } + + if ((c = *fmt++) != '%') { + goto literal; + } + + again: + switch (c = *fmt++) { + case '%': /* "%%" is converted to "%". */ + literal: + if (c != *bp++) { + return (0); + } + break; + + /* + * "Alternative" modifiers. Just set the appropriate flag + * and start over again. + */ + case 'E': /* "%E?" alternative conversion modifier. */ + LEGAL_ALT(0); + alt_format |= ALT_E; + goto again; + + case 'O': /* "%O?" alternative conversion modifier. */ + LEGAL_ALT(0); + alt_format |= ALT_O; + goto again; + + /* + * "Complex" conversion rules, implemented through recursion. + */ + case 'c': /* Date and time, using the locale's format. */ + LEGAL_ALT(ALT_E); + if (!(bp = isc_tm_strptime(bp, "%x %X", tm))) { + return (0); + } + break; + + case 'D': /* The date as "%m/%d/%y". */ + LEGAL_ALT(0); + if (!(bp = isc_tm_strptime(bp, "%m/%d/%y", tm))) { + return (0); + } + break; + + case 'R': /* The time as "%H:%M". */ + LEGAL_ALT(0); + if (!(bp = isc_tm_strptime(bp, "%H:%M", tm))) { + return (0); + } + break; + + case 'r': /* The time in 12-hour clock representation. */ + LEGAL_ALT(0); + if (!(bp = isc_tm_strptime(bp, "%I:%M:%S %p", tm))) { + return (0); + } + break; + + case 'T': /* The time as "%H:%M:%S". */ + LEGAL_ALT(0); + if (!(bp = isc_tm_strptime(bp, "%H:%M:%S", tm))) { + return (0); + } + break; + + case 'X': /* The time, using the locale's format. */ + LEGAL_ALT(ALT_E); + if (!(bp = isc_tm_strptime(bp, "%H:%M:%S", tm))) { + return (0); + } + break; + + case 'x': /* The date, using the locale's format. */ + LEGAL_ALT(ALT_E); + if (!(bp = isc_tm_strptime(bp, "%m/%d/%y", tm))) { + return (0); + } + break; + + /* + * "Elementary" conversion rules. + */ + case 'A': /* The day of week, using the locale's form. */ + case 'a': + LEGAL_ALT(0); + for (i = 0; i < 7; i++) { + /* Full name. */ + len = strlen(day[i]); + if (strncasecmp(day[i], bp, len) == 0) { + break; + } + + /* Abbreviated name. */ + len = strlen(abday[i]); + if (strncasecmp(abday[i], bp, len) == 0) { + break; + } + } + + /* Nothing matched. */ + if (i == 7) { + return (0); + } + + tm->tm_wday = i; + bp += len; + break; + + case 'B': /* The month, using the locale's form. */ + case 'b': + case 'h': + LEGAL_ALT(0); + for (i = 0; i < 12; i++) { + /* Full name. */ + len = strlen(mon[i]); + if (strncasecmp(mon[i], bp, len) == 0) { + break; + } + + /* Abbreviated name. */ + len = strlen(abmon[i]); + if (strncasecmp(abmon[i], bp, len) == 0) { + break; + } + } + + /* Nothing matched. */ + if (i == 12) { + return (0); + } + + tm->tm_mon = i; + bp += len; + break; + + case 'C': /* The century number. */ + LEGAL_ALT(ALT_E); + if (!(conv_num(&bp, &i, 0, 99))) { + return (0); + } + + if (split_year) { + tm->tm_year = (tm->tm_year % 100) + (i * 100); + } else { + tm->tm_year = i * 100; + split_year = 1; + } + break; + + case 'd': /* The day of month. */ + case 'e': + LEGAL_ALT(ALT_O); + if (!(conv_num(&bp, &tm->tm_mday, 1, 31))) { + return (0); + } + break; + + case 'k': /* The hour (24-hour clock representation). */ + LEGAL_ALT(0); + FALLTHROUGH; + case 'H': + LEGAL_ALT(ALT_O); + if (!(conv_num(&bp, &tm->tm_hour, 0, 23))) { + return (0); + } + break; + + case 'l': /* The hour (12-hour clock representation). */ + LEGAL_ALT(0); + FALLTHROUGH; + case 'I': + LEGAL_ALT(ALT_O); + if (!(conv_num(&bp, &tm->tm_hour, 1, 12))) { + return (0); + } + if (tm->tm_hour == 12) { + tm->tm_hour = 0; + } + break; + + case 'j': /* The day of year. */ + LEGAL_ALT(0); + if (!(conv_num(&bp, &i, 1, 366))) { + return (0); + } + tm->tm_yday = i - 1; + break; + + case 'M': /* The minute. */ + LEGAL_ALT(ALT_O); + if (!(conv_num(&bp, &tm->tm_min, 0, 59))) { + return (0); + } + break; + + case 'm': /* The month. */ + LEGAL_ALT(ALT_O); + if (!(conv_num(&bp, &i, 1, 12))) { + return (0); + } + tm->tm_mon = i - 1; + break; + + case 'p': /* The locale's equivalent of AM/PM. */ + LEGAL_ALT(0); + /* AM? */ + if (strcasecmp(am_pm[0], bp) == 0) { + if (tm->tm_hour > 11) { + return (0); + } + + bp += strlen(am_pm[0]); + break; + } + /* PM? */ + else if (strcasecmp(am_pm[1], bp) == 0) + { + if (tm->tm_hour > 11) { + return (0); + } + + tm->tm_hour += 12; + bp += strlen(am_pm[1]); + break; + } + + /* Nothing matched. */ + return (0); + + case 'S': /* The seconds. */ + LEGAL_ALT(ALT_O); + if (!(conv_num(&bp, &tm->tm_sec, 0, 61))) { + return (0); + } + break; + + case 'U': /* The week of year, beginning on sunday. */ + case 'W': /* The week of year, beginning on monday. */ + LEGAL_ALT(ALT_O); + /* + * XXX This is bogus, as we can not assume any valid + * information present in the tm structure at this + * point to calculate a real value, so just check the + * range for now. + */ + if (!(conv_num(&bp, &i, 0, 53))) { + return (0); + } + break; + + case 'w': /* The day of week, beginning on sunday. */ + LEGAL_ALT(ALT_O); + if (!(conv_num(&bp, &tm->tm_wday, 0, 6))) { + return (0); + } + break; + + case 'Y': /* The year. */ + LEGAL_ALT(ALT_E); + if (!(conv_num(&bp, &i, 0, 9999))) { + return (0); + } + + tm->tm_year = i - TM_YEAR_BASE; + break; + + case 'y': /* The year within 100 years of the epoch. */ + LEGAL_ALT(ALT_E | ALT_O); + if (!(conv_num(&bp, &i, 0, 99))) { + return (0); + } + + if (split_year) { + tm->tm_year = ((tm->tm_year / 100) * 100) + i; + break; + } + split_year = 1; + if (i <= 68) { + tm->tm_year = i + 2000 - TM_YEAR_BASE; + } else { + tm->tm_year = i + 1900 - TM_YEAR_BASE; + } + break; + + /* + * Miscellaneous conversions. + */ + case 'n': /* Any kind of white-space. */ + case 't': + LEGAL_ALT(0); + while (isspace((unsigned char)*bp)) { + bp++; + } + break; + + default: /* Unknown/unsupported conversion. */ + return (0); + } + } + + /* LINTED functional specification */ + DE_CONST(bp, ret); + return (ret); +} diff --git a/lib/isc/trampoline.c b/lib/isc/trampoline.c new file mode 100644 index 0000000..be451a9 --- /dev/null +++ b/lib/isc/trampoline.c @@ -0,0 +1,218 @@ +/* + * 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. + */ + +/*! \file */ + +#include <inttypes.h> +#include <stdlib.h> +#include <uv.h> + +#include <isc/mem.h> +#include <isc/once.h> +#include <isc/thread.h> +#include <isc/util.h> + +#include "trampoline_p.h" + +#define ISC__TRAMPOLINE_UNUSED 0 + +struct isc__trampoline { + int tid; /* const */ + uintptr_t self; + isc_threadfunc_t start; + isc_threadarg_t arg; + void *jemalloc_enforce_init; +}; + +/* + * We can't use isc_mem API here, because it's called too + * early and when the isc_mem_debugging flags are changed + * later and ISC_MEM_DEBUGSIZE or ISC_MEM_DEBUGCTX flags are + * added, neither isc_mem_put() nor isc_mem_free() can be used + * to free up the memory allocated here because the flags were + * not set when calling isc_mem_get() or isc_mem_allocate() + * here. + * + * Since this is a single allocation at library load and deallocation at library + * unload, using the standard allocator without the tracking is fine for this + * single purpose. + * + * We can't use isc_mutex API either, because we track whether the mutexes get + * properly destroyed, and we intentionally leak the static mutex here without + * destroying it to prevent data race between library destructor running while + * thread is being still created. + */ + +static uv_mutex_t isc__trampoline_lock; +static isc__trampoline_t **trampolines; +#if defined(HAVE_THREAD_LOCAL) +#include <threads.h> +thread_local size_t isc_tid_v = SIZE_MAX; +#elif defined(HAVE___THREAD) +__thread size_t isc_tid_v = SIZE_MAX; +#elif HAVE___DECLSPEC_THREAD +__declspec(thread) size_t isc_tid_v = SIZE_MAX; +#endif /* if defined(HAVE_THREAD_LOCAL) */ +static size_t isc__trampoline_min = 1; +static size_t isc__trampoline_max = 65; + +static isc_once_t start_once = ISC_ONCE_INIT; +static isc_once_t stop_once = ISC_ONCE_INIT; + +static isc__trampoline_t * +isc__trampoline_new(int tid, isc_threadfunc_t start, isc_threadarg_t arg) { + isc__trampoline_t *trampoline = calloc(1, sizeof(*trampoline)); + RUNTIME_CHECK(trampoline != NULL); + + *trampoline = (isc__trampoline_t){ + .tid = tid, + .start = start, + .arg = arg, + .self = ISC__TRAMPOLINE_UNUSED, + }; + + return (trampoline); +} + +static void +do_init(void) { + uv_mutex_init(&isc__trampoline_lock); + + trampolines = calloc(isc__trampoline_max, sizeof(trampolines[0])); + RUNTIME_CHECK(trampolines != NULL); + + /* Get the trampoline slot 0 for the main thread */ + trampolines[0] = isc__trampoline_new(0, NULL, NULL); + isc_tid_v = trampolines[0]->tid; + trampolines[0]->self = isc_thread_self(); + + /* Initialize the other trampolines */ + for (size_t i = 1; i < isc__trampoline_max; i++) { + trampolines[i] = NULL; + } + isc__trampoline_min = 1; +} + +void +isc__trampoline_initialize(void) { + isc_once_do(&start_once, do_init); +} + +static void +do_shutdown(void) { + /* + * When the program using the library exits abruptly and the library + * gets unloaded, there might be some existing trampolines from unjoined + * threads. We intentionally ignore those and don't check whether all + * trampolines have been cleared before exiting, so we leak a little bit + * of resources here, including the lock. + */ + free(trampolines[0]); +} + +void +isc__trampoline_shutdown(void) { + isc_once_do(&stop_once, do_shutdown); +} + +isc__trampoline_t * +isc__trampoline_get(isc_threadfunc_t start, isc_threadarg_t arg) { + isc__trampoline_t **tmp = NULL; + isc__trampoline_t *trampoline = NULL; + uv_mutex_lock(&isc__trampoline_lock); +again: + for (size_t i = isc__trampoline_min; i < isc__trampoline_max; i++) { + if (trampolines[i] == NULL) { + trampoline = isc__trampoline_new(i, start, arg); + trampolines[i] = trampoline; + isc__trampoline_min = i + 1; + goto done; + } + } + tmp = calloc(2 * isc__trampoline_max, sizeof(trampolines[0])); + RUNTIME_CHECK(tmp != NULL); + for (size_t i = 0; i < isc__trampoline_max; i++) { + tmp[i] = trampolines[i]; + } + for (size_t i = isc__trampoline_max; i < 2 * isc__trampoline_max; i++) { + tmp[i] = NULL; + } + free(trampolines); + trampolines = tmp; + isc__trampoline_max = isc__trampoline_max * 2; + goto again; +done: + INSIST(trampoline != NULL); + uv_mutex_unlock(&isc__trampoline_lock); + + return (trampoline); +} + +void +isc__trampoline_detach(isc__trampoline_t *trampoline) { + uv_mutex_lock(&isc__trampoline_lock); + REQUIRE(trampoline->self == isc_thread_self()); + REQUIRE(trampoline->tid > 0); + REQUIRE((size_t)trampoline->tid < isc__trampoline_max); + REQUIRE(trampolines[trampoline->tid] == trampoline); + + trampolines[trampoline->tid] = NULL; + + if (isc__trampoline_min > (size_t)trampoline->tid) { + isc__trampoline_min = trampoline->tid; + } + + free(trampoline->jemalloc_enforce_init); + free(trampoline); + + uv_mutex_unlock(&isc__trampoline_lock); + return; +} + +void +isc__trampoline_attach(isc__trampoline_t *trampoline) { + uv_mutex_lock(&isc__trampoline_lock); + REQUIRE(trampoline->self == ISC__TRAMPOLINE_UNUSED); + REQUIRE(trampoline->tid > 0); + REQUIRE((size_t)trampoline->tid < isc__trampoline_max); + REQUIRE(trampolines[trampoline->tid] == trampoline); + + /* Initialize the trampoline */ + isc_tid_v = trampoline->tid; + trampoline->self = isc_thread_self(); + + /* + * Ensure every thread starts with a malloc() call to prevent memory + * bloat caused by a jemalloc quirk. While this dummy allocation is + * not used for anything, free() must not be immediately called for it + * so that an optimizing compiler does not strip away such a pair of + * malloc() + free() calls altogether, as it would foil the fix. + */ + trampoline->jemalloc_enforce_init = malloc(8); + uv_mutex_unlock(&isc__trampoline_lock); +} + +isc_threadresult_t +isc__trampoline_run(isc_threadarg_t arg) { + isc__trampoline_t *trampoline = (isc__trampoline_t *)arg; + isc_threadresult_t result; + + isc__trampoline_attach(trampoline); + + /* Run the main function */ + result = (trampoline->start)(trampoline->arg); + + isc__trampoline_detach(trampoline); + + return (result); +} diff --git a/lib/isc/trampoline_p.h b/lib/isc/trampoline_p.h new file mode 100644 index 0000000..4599ea1 --- /dev/null +++ b/lib/isc/trampoline_p.h @@ -0,0 +1,90 @@ +/* + * 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. + */ + +#pragma once + +#include <isc/thread.h> + +/*! \file isc/trampoline_p.h + * \brief isc__trampoline: allows safe reuse of thread ID numbers. + * + * The 'isc_hp' hazard pointer API uses an internal thread ID + * variable ('tid_v') that is incremented for each new thread that uses + * hazard pointers. This thread ID is then used as an index into a global + * shared table of hazard pointer state. + * + * Since the thread ID is only incremented and never decremented, the + * table can overflow if threads are frequently created and destroyed. + * + * A trampoline is a thin wrapper around any function to be called from + * a newly launched thread. It maintains a table of thread IDs used by + * current and previous threads; when a thread is destroyed, its slot in + * the trampoline table becomes available, and the next thread to occupy + * that slot can use the same thread ID that its predecessor did. + * + * The trampoline table initially has space for 64 worker threads in + * addition to the main thread. If more threads than that are in + * concurrent use, the table is reallocated with twice as much space. + * (Note that the number of concurrent threads is currently capped at + * 128 by the queue and hazard pointer implementations.) + */ + +typedef struct isc__trampoline isc__trampoline_t; + +void +isc__trampoline_initialize(void); +/*%< + * Initialize the thread trampoline internal structures, must be called only + * once as a library constructor (see lib/isc/lib.c). + */ + +void +isc__trampoline_shutdown(void); +/*%< + * Destroy the thread trampoline internal structures, must be called only + * once as a library destructor (see lib/isc/lib.c). + */ + +isc__trampoline_t * +isc__trampoline_get(isc_threadfunc_t start_routine, isc_threadarg_t arg); +/*%< + * Get a free thread trampoline structure and initialize it with + * start_routine and arg passed to start_routine. + * + * Requires: + *\li 'start_routine' is a valid non-NULL thread start_routine + */ + +void +isc__trampoline_attach(isc__trampoline_t *trampoline); +void +isc__trampoline_detach(isc__trampoline_t *trampoline); +/*%< + * Attach/detach the trampoline to/from the current thread. + * + * Requires: + * \li 'trampoline' is a valid isc__trampoline_t + */ + +isc_threadresult_t +isc__trampoline_run(isc_threadarg_t arg); +/*%< + * Run the thread trampoline, this will get passed to the actual + * pthread_create() (or Windows equivalent), initialize the isc_tid_v. + * + * Requires: + *\li 'arg' is a valid isc_trampoline_t + * + * Returns: + *\li return value from start_routine (see isc__trampoline_get()) + */ diff --git a/lib/isc/unix/Makefile.in b/lib/isc/unix/Makefile.in new file mode 100644 index 0000000..4af7d6e --- /dev/null +++ b/lib/isc/unix/Makefile.in @@ -0,0 +1,48 @@ +# 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. + +srcdir = @srcdir@ +VPATH = @srcdir@ +top_srcdir = @top_srcdir@ + +CINCLUDES = -I${srcdir}/include \ + -I${srcdir}/../pthreads/include \ + -I../include \ + -I${srcdir}/../include \ + -I${srcdir}/.. \ + ${OPENSSL_CFLAGS} \ + ${JSON_C_CFLAGS} \ + ${LIBXML2_CFLAGS} + +CDEFINES = +CWARNINGS = + +# Alphabetically +OBJS = pk11_api.@O@ \ + dir.@O@ errno.@O@ errno2result.@O@ \ + file.@O@ fsaccess.@O@ interfaceiter.@O@ \ + meminfo.@O@ \ + net.@O@ os.@O@ resource.@O@ socket.@O@ stdio.@O@ stdtime.@O@ \ + syslog.@O@ time.@O@ + +# Alphabetically +SRCS = pk11_api.c \ + dir.c errno.c errno2result.c \ + file.c fsaccess.c interfaceiter.c meminfo.c \ + net.c os.c resource.c socket.c stdio.c stdtime.c \ + syslog.c time.c + +SUBDIRS = include +TARGETS = ${OBJS} + +@BIND9_MAKE_RULES@ + +interfaceiter.@O@: interfaceiter.c ifiter_getifaddrs.c diff --git a/lib/isc/unix/dir.c b/lib/isc/unix/dir.c new file mode 100644 index 0000000..b7eabe9 --- /dev/null +++ b/lib/isc/unix/dir.c @@ -0,0 +1,268 @@ +/* + * 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. + */ + +/*! \file */ + +#include <ctype.h> +#include <errno.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <isc/dir.h> +#include <isc/magic.h> +#include <isc/netdb.h> +#include <isc/print.h> +#include <isc/string.h> +#include <isc/util.h> + +#include "errno2result.h" + +#define ISC_DIR_MAGIC ISC_MAGIC('D', 'I', 'R', '*') +#define VALID_DIR(dir) ISC_MAGIC_VALID(dir, ISC_DIR_MAGIC) + +void +isc_dir_init(isc_dir_t *dir) { + REQUIRE(dir != NULL); + + dir->entry.name[0] = '\0'; + dir->entry.length = 0; + + dir->handle = NULL; + + dir->magic = ISC_DIR_MAGIC; +} + +/*! + * \brief Allocate workspace and open directory stream. If either one fails, + * NULL will be returned. + */ +isc_result_t +isc_dir_open(isc_dir_t *dir, const char *dirname) { + char *p; + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(VALID_DIR(dir)); + REQUIRE(dirname != NULL); + + /* + * Copy directory name. Need to have enough space for the name, + * a possible path separator, the wildcard, and the final NUL. + */ + if (strlen(dirname) + 3 > sizeof(dir->dirname)) { + /* XXXDCL ? */ + return (ISC_R_NOSPACE); + } + strlcpy(dir->dirname, dirname, sizeof(dir->dirname)); + + /* + * Append path separator, if needed, and "*". + */ + p = dir->dirname + strlen(dir->dirname); + if (dir->dirname < p && *(p - 1) != '/') { + *p++ = '/'; + } + *p++ = '*'; + *p = '\0'; + + /* + * Open stream. + */ + dir->handle = opendir(dirname); + + if (dir->handle == NULL) { + return (isc__errno2result(errno)); + } + + return (result); +} + +/*! + * \brief Return previously retrieved file or get next one. + * + * Unix's dirent has + * separate open and read functions, but the Win32 and DOS interfaces open + * the dir stream and reads the first file in one operation. + */ +isc_result_t +isc_dir_read(isc_dir_t *dir) { + struct dirent *entry; + + REQUIRE(VALID_DIR(dir) && dir->handle != NULL); + + /* + * Fetch next file in directory. + */ + entry = readdir(dir->handle); + + if (entry == NULL) { + return (ISC_R_NOMORE); + } + + /* + * Make sure that the space for the name is long enough. + */ + if (sizeof(dir->entry.name) <= strlen(entry->d_name)) { + return (ISC_R_UNEXPECTED); + } + + strlcpy(dir->entry.name, entry->d_name, sizeof(dir->entry.name)); + + /* + * Some dirents have d_namlen, but it is not portable. + */ + dir->entry.length = strlen(entry->d_name); + + return (ISC_R_SUCCESS); +} + +/*! + * \brief Close directory stream. + */ +void +isc_dir_close(isc_dir_t *dir) { + REQUIRE(VALID_DIR(dir) && dir->handle != NULL); + + (void)closedir(dir->handle); + dir->handle = NULL; +} + +/*! + * \brief Reposition directory stream at start. + */ +isc_result_t +isc_dir_reset(isc_dir_t *dir) { + REQUIRE(VALID_DIR(dir) && dir->handle != NULL); + + rewinddir(dir->handle); + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_dir_chdir(const char *dirname) { + /*! + * \brief Change the current directory to 'dirname'. + */ + + REQUIRE(dirname != NULL); + + if (chdir(dirname) < 0) { + return (isc__errno2result(errno)); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_dir_chroot(const char *dirname) { +#ifdef HAVE_CHROOT + void *tmp; +#endif /* ifdef HAVE_CHROOT */ + + REQUIRE(dirname != NULL); + +#ifdef HAVE_CHROOT + /* + * Try to use getservbyname and getprotobyname before chroot. + * If WKS records are used in a zone under chroot, Name Service Switch + * may fail to load library in chroot. + * Do not report errors if it fails, we do not need any result now. + */ + tmp = getprotobyname("udp"); + if (tmp != NULL) { + (void)getservbyname("domain", "udp"); + } + + if (chroot(dirname) < 0 || chdir("/") < 0) { + return (isc__errno2result(errno)); + } + + return (ISC_R_SUCCESS); +#else /* ifdef HAVE_CHROOT */ + return (ISC_R_NOTIMPLEMENTED); +#endif /* ifdef HAVE_CHROOT */ +} + +isc_result_t +isc_dir_createunique(char *templet) { + isc_result_t result; + char *x; + char *p; + int i; + int pid; + + REQUIRE(templet != NULL); + + /*! + * \brief mkdtemp is not portable, so this emulates it. + */ + + pid = getpid(); + + /* + * Replace trailing Xs with the process-id, zero-filled. + */ + for (x = templet + strlen(templet) - 1; *x == 'X' && x >= templet; + x--, pid /= 10) + { + *x = pid % 10 + '0'; + } + + x++; /* Set x to start of ex-Xs. */ + + do { + i = mkdir(templet, 0700); + if (i == 0 || errno != EEXIST) { + break; + } + + /* + * The BSD algorithm. + */ + p = x; + while (*p != '\0') { + if (isdigit((unsigned char)*p)) { + *p = 'a'; + } else if (*p != 'z') { + ++*p; + } else { + /* + * Reset character and move to next. + */ + *p++ = 'a'; + continue; + } + + break; + } + + if (*p == '\0') { + /* + * Tried all combinations. errno should already + * be EEXIST, but ensure it is anyway for + * isc__errno2result(). + */ + errno = EEXIST; + break; + } + } while (1); + + if (i == -1) { + result = isc__errno2result(errno); + } else { + result = ISC_R_SUCCESS; + } + + return (result); +} diff --git a/lib/isc/unix/errno.c b/lib/isc/unix/errno.c new file mode 100644 index 0000000..5875f3b --- /dev/null +++ b/lib/isc/unix/errno.c @@ -0,0 +1,24 @@ +/* + * 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. + */ + +/*! \file */ + +#include <isc/errno.h> +#include <isc/util.h> + +#include "errno2result.h" + +isc_result_t +isc_errno_toresult(int err) { + return (isc___errno2result(err, false, 0, 0)); +} diff --git a/lib/isc/unix/errno2result.c b/lib/isc/unix/errno2result.c new file mode 100644 index 0000000..6f752df --- /dev/null +++ b/lib/isc/unix/errno2result.c @@ -0,0 +1,131 @@ +/* + * 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. + */ + +/*! \file */ + +#include "errno2result.h" +#include <stdbool.h> + +#include <isc/platform.h> +#include <isc/result.h> +#include <isc/strerr.h> +#include <isc/string.h> +#include <isc/util.h> + +/*% + * Convert a POSIX errno value into an isc_result_t. The + * list of supported errno values is not complete; new users + * of this function should add any expected errors that are + * not already there. + */ +isc_result_t +isc___errno2result(int posixerrno, bool dolog, const char *file, + unsigned int line) { + char strbuf[ISC_STRERRORSIZE]; + + switch (posixerrno) { + case ENOTDIR: + case ELOOP: + case EINVAL: /* XXX sometimes this is not for files */ + case ENAMETOOLONG: + case EBADF: + return (ISC_R_INVALIDFILE); + case ENOENT: + return (ISC_R_FILENOTFOUND); + case EACCES: + case EPERM: + return (ISC_R_NOPERM); + case EEXIST: + return (ISC_R_FILEEXISTS); + case EIO: + return (ISC_R_IOERROR); + case ENOMEM: + return (ISC_R_NOMEMORY); + case ENFILE: + case EMFILE: + return (ISC_R_TOOMANYOPENFILES); +#ifdef EDQUOT + case EDQUOT: + return (ISC_R_DISCQUOTA); +#endif /* ifdef EDQUOT */ + case ENOSPC: + return (ISC_R_DISCFULL); +#ifdef EOVERFLOW + case EOVERFLOW: + return (ISC_R_RANGE); +#endif /* ifdef EOVERFLOW */ + case EPIPE: +#ifdef ECONNRESET + case ECONNRESET: +#endif /* ifdef ECONNRESET */ +#ifdef ECONNABORTED + case ECONNABORTED: +#endif /* ifdef ECONNABORTED */ + return (ISC_R_CONNECTIONRESET); +#ifdef ENOTCONN + case ENOTCONN: + return (ISC_R_NOTCONNECTED); +#endif /* ifdef ENOTCONN */ +#ifdef ETIMEDOUT + case ETIMEDOUT: + return (ISC_R_TIMEDOUT); +#endif /* ifdef ETIMEDOUT */ +#ifdef ENOBUFS + case ENOBUFS: + return (ISC_R_NORESOURCES); +#endif /* ifdef ENOBUFS */ +#ifdef EAFNOSUPPORT + case EAFNOSUPPORT: + return (ISC_R_FAMILYNOSUPPORT); +#endif /* ifdef EAFNOSUPPORT */ +#ifdef ENETDOWN + case ENETDOWN: + return (ISC_R_NETDOWN); +#endif /* ifdef ENETDOWN */ +#ifdef EHOSTDOWN + case EHOSTDOWN: + return (ISC_R_HOSTDOWN); +#endif /* ifdef EHOSTDOWN */ +#ifdef ENETUNREACH + case ENETUNREACH: + return (ISC_R_NETUNREACH); +#endif /* ifdef ENETUNREACH */ +#ifdef EHOSTUNREACH + case EHOSTUNREACH: + return (ISC_R_HOSTUNREACH); +#endif /* ifdef EHOSTUNREACH */ +#ifdef EADDRINUSE + case EADDRINUSE: + return (ISC_R_ADDRINUSE); +#endif /* ifdef EADDRINUSE */ + case EADDRNOTAVAIL: + return (ISC_R_ADDRNOTAVAIL); + case ECONNREFUSED: + return (ISC_R_CONNREFUSED); + default: + if (dolog) { + strerror_r(posixerrno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(file, line, + "unable to convert errno " + "to isc_result: %d: %s", + posixerrno, strbuf); + } + /* + * XXXDCL would be nice if perhaps this function could + * return the system's error string, so the caller + * might have something more descriptive than "unexpected + * error" to log with. + */ + return (ISC_R_UNEXPECTED); + } +} diff --git a/lib/isc/unix/errno2result.h b/lib/isc/unix/errno2result.h new file mode 100644 index 0000000..05de1f2 --- /dev/null +++ b/lib/isc/unix/errno2result.h @@ -0,0 +1,37 @@ +/* + * 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. + */ + +#ifndef UNIX_ERRNO2RESULT_H +#define UNIX_ERRNO2RESULT_H 1 + +/*! \file */ + +/* XXXDCL this should be moved to lib/isc/include/isc/errno2result.h. */ + +#include <errno.h> /* Provides errno. */ +#include <stdbool.h> + +#include <isc/lang.h> +#include <isc/types.h> + +ISC_LANG_BEGINDECLS + +#define isc__errno2result(x) isc___errno2result(x, true, __FILE__, __LINE__) + +isc_result_t +isc___errno2result(int posixerrno, bool dolog, const char *file, + unsigned int line); + +ISC_LANG_ENDDECLS + +#endif /* UNIX_ERRNO2RESULT_H */ diff --git a/lib/isc/unix/file.c b/lib/isc/unix/file.c new file mode 100644 index 0000000..0604f14 --- /dev/null +++ b/lib/isc/unix/file.c @@ -0,0 +1,840 @@ +/* + * 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. + */ + +/* + * Portions Copyright (c) 1987, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/*! \file */ + +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <limits.h> +#include <stdbool.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <time.h> /* Required for utimes on some platforms. */ +#include <unistd.h> /* Required for mkstemp on NetBSD. */ + +#ifdef HAVE_SYS_MMAN_H +#include <sys/mman.h> +#endif /* ifdef HAVE_SYS_MMAN_H */ + +#include <isc/dir.h> +#include <isc/file.h> +#include <isc/log.h> +#include <isc/md.h> +#include <isc/mem.h> +#include <isc/platform.h> +#include <isc/print.h> +#include <isc/random.h> +#include <isc/string.h> +#include <isc/time.h> +#include <isc/util.h> + +#include "errno2result.h" + +/* + * XXXDCL As the API for accessing file statistics undoubtedly gets expanded, + * it might be good to provide a mechanism that allows for the results + * of a previous stat() to be used again without having to do another stat, + * such as perl's mechanism of using "_" in place of a file name to indicate + * that the results of the last stat should be used. But then you get into + * annoying MP issues. BTW, Win32 has stat(). + */ +static isc_result_t +file_stats(const char *file, struct stat *stats) { + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(file != NULL); + REQUIRE(stats != NULL); + + if (stat(file, stats) != 0) { + result = isc__errno2result(errno); + } + + return (result); +} + +static isc_result_t +fd_stats(int fd, struct stat *stats) { + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(stats != NULL); + + if (fstat(fd, stats) != 0) { + result = isc__errno2result(errno); + } + + return (result); +} + +isc_result_t +isc_file_getsizefd(int fd, off_t *size) { + isc_result_t result; + struct stat stats; + + REQUIRE(size != NULL); + + result = fd_stats(fd, &stats); + + if (result == ISC_R_SUCCESS) { + *size = stats.st_size; + } + + return (result); +} + +isc_result_t +isc_file_mode(const char *file, mode_t *modep) { + isc_result_t result; + struct stat stats; + + REQUIRE(modep != NULL); + + result = file_stats(file, &stats); + if (result == ISC_R_SUCCESS) { + *modep = (stats.st_mode & 07777); + } + + return (result); +} + +isc_result_t +isc_file_getmodtime(const char *file, isc_time_t *modtime) { + isc_result_t result; + struct stat stats; + + REQUIRE(file != NULL); + REQUIRE(modtime != NULL); + + result = file_stats(file, &stats); + + if (result == ISC_R_SUCCESS) { +#if defined(HAVE_STAT_NSEC) + isc_time_set(modtime, stats.st_mtime, stats.st_mtim.tv_nsec); +#else /* if defined(HAVE_STAT_NSEC) */ + isc_time_set(modtime, stats.st_mtime, 0); +#endif /* if defined(HAVE_STAT_NSEC) */ + } + + return (result); +} + +isc_result_t +isc_file_getsize(const char *file, off_t *size) { + isc_result_t result; + struct stat stats; + + REQUIRE(file != NULL); + REQUIRE(size != NULL); + + result = file_stats(file, &stats); + + if (result == ISC_R_SUCCESS) { + *size = stats.st_size; + } + + return (result); +} + +isc_result_t +isc_file_settime(const char *file, isc_time_t *when) { + struct timeval times[2]; + + REQUIRE(file != NULL && when != NULL); + + /* + * tv_sec is at least a 32 bit quantity on all platforms we're + * dealing with, but it is signed on most (all?) of them, + * so we need to make sure the high bit isn't set. This unfortunately + * loses when either: + * * tv_sec becomes a signed 64 bit integer but long is 32 bits + * and isc_time_seconds > LONG_MAX, or + * * isc_time_seconds is changed to be > 32 bits but long is 32 bits + * and isc_time_seconds has at least 33 significant bits. + */ + times[0].tv_sec = times[1].tv_sec = (long)isc_time_seconds(when); + + /* + * Here is the real check for the high bit being set. + */ + if ((times[0].tv_sec & + (1ULL << (sizeof(times[0].tv_sec) * CHAR_BIT - 1))) != 0) + { + return (ISC_R_RANGE); + } + + /* + * isc_time_nanoseconds guarantees a value that divided by 1000 will + * fit into the minimum possible size tv_usec field. + */ + times[0].tv_usec = times[1].tv_usec = + (int32_t)(isc_time_nanoseconds(when) / 1000); + + if (utimes(file, times) < 0) { + return (isc__errno2result(errno)); + } + + return (ISC_R_SUCCESS); +} + +#undef TEMPLATE +#define TEMPLATE "tmp-XXXXXXXXXX" /*%< 14 characters. */ + +isc_result_t +isc_file_mktemplate(const char *path, char *buf, size_t buflen) { + return (isc_file_template(path, TEMPLATE, buf, buflen)); +} + +isc_result_t +isc_file_template(const char *path, const char *templet, char *buf, + size_t buflen) { + const char *s; + + REQUIRE(templet != NULL); + REQUIRE(buf != NULL); + + if (path == NULL) { + path = ""; + } + + s = strrchr(templet, '/'); + if (s != NULL) { + templet = s + 1; + } + + s = strrchr(path, '/'); + + if (s != NULL) { + size_t prefixlen = s - path + 1; + if ((prefixlen + strlen(templet) + 1) > buflen) { + return (ISC_R_NOSPACE); + } + + /* Copy 'prefixlen' bytes and NUL terminate. */ + strlcpy(buf, path, ISC_MIN(prefixlen + 1, buflen)); + strlcat(buf, templet, buflen); + } else { + if ((strlen(templet) + 1) > buflen) { + return (ISC_R_NOSPACE); + } + + strlcpy(buf, templet, buflen); + } + + return (ISC_R_SUCCESS); +} + +static const char alphnum[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuv" + "wxyz0123456789"; + +isc_result_t +isc_file_renameunique(const char *file, char *templet) { + char *x; + char *cp; + + REQUIRE(file != NULL); + REQUIRE(templet != NULL); + + cp = templet; + while (*cp != '\0') { + cp++; + } + if (cp == templet) { + return (ISC_R_FAILURE); + } + + x = cp--; + while (cp >= templet && *cp == 'X') { + *cp = alphnum[isc_random_uniform(sizeof(alphnum) - 1)]; + x = cp--; + } + while (link(file, templet) == -1) { + if (errno != EEXIST) { + return (isc__errno2result(errno)); + } + for (cp = x;;) { + const char *t; + if (*cp == '\0') { + return (ISC_R_FAILURE); + } + t = strchr(alphnum, *cp); + if (t == NULL || *++t == '\0') { + *cp++ = alphnum[0]; + } else { + *cp = *t; + break; + } + } + } + if (unlink(file) < 0) { + if (errno != ENOENT) { + return (isc__errno2result(errno)); + } + } + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_file_openunique(char *templet, FILE **fp) { + int mode = S_IWUSR | S_IRUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + return (isc_file_openuniquemode(templet, mode, fp)); +} + +isc_result_t +isc_file_openuniqueprivate(char *templet, FILE **fp) { + int mode = S_IWUSR | S_IRUSR; + return (isc_file_openuniquemode(templet, mode, fp)); +} + +isc_result_t +isc_file_openuniquemode(char *templet, int mode, FILE **fp) { + int fd; + FILE *f; + isc_result_t result = ISC_R_SUCCESS; + char *x; + char *cp; + + REQUIRE(templet != NULL); + REQUIRE(fp != NULL && *fp == NULL); + + cp = templet; + while (*cp != '\0') { + cp++; + } + if (cp == templet) { + return (ISC_R_FAILURE); + } + + x = cp--; + while (cp >= templet && *cp == 'X') { + *cp = alphnum[isc_random_uniform(sizeof(alphnum) - 1)]; + x = cp--; + } + + while ((fd = open(templet, O_RDWR | O_CREAT | O_EXCL, mode)) == -1) { + if (errno != EEXIST) { + return (isc__errno2result(errno)); + } + for (cp = x;;) { + char *t; + if (*cp == '\0') { + return (ISC_R_FAILURE); + } + t = strchr(alphnum, *cp); + if (t == NULL || *++t == '\0') { + *cp++ = alphnum[0]; + } else { + *cp = *t; + break; + } + } + } + f = fdopen(fd, "w+"); + if (f == NULL) { + result = isc__errno2result(errno); + if (remove(templet) < 0) { + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_FILE, ISC_LOG_ERROR, + "remove '%s': failed", templet); + } + (void)close(fd); + } else { + *fp = f; + } + + return (result); +} + +isc_result_t +isc_file_bopenunique(char *templet, FILE **fp) { + int mode = S_IWUSR | S_IRUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + return (isc_file_openuniquemode(templet, mode, fp)); +} + +isc_result_t +isc_file_bopenuniqueprivate(char *templet, FILE **fp) { + int mode = S_IWUSR | S_IRUSR; + return (isc_file_openuniquemode(templet, mode, fp)); +} + +isc_result_t +isc_file_bopenuniquemode(char *templet, int mode, FILE **fp) { + return (isc_file_openuniquemode(templet, mode, fp)); +} + +isc_result_t +isc_file_remove(const char *filename) { + int r; + + REQUIRE(filename != NULL); + + r = unlink(filename); + if (r == 0) { + return (ISC_R_SUCCESS); + } else { + return (isc__errno2result(errno)); + } +} + +isc_result_t +isc_file_rename(const char *oldname, const char *newname) { + int r; + + REQUIRE(oldname != NULL); + REQUIRE(newname != NULL); + + r = rename(oldname, newname); + if (r == 0) { + return (ISC_R_SUCCESS); + } else { + return (isc__errno2result(errno)); + } +} + +bool +isc_file_exists(const char *pathname) { + struct stat stats; + + REQUIRE(pathname != NULL); + + return (file_stats(pathname, &stats) == ISC_R_SUCCESS); +} + +isc_result_t +isc_file_isplainfile(const char *filename) { + /* + * This function returns success if filename is a plain file. + */ + struct stat filestat; + memset(&filestat, 0, sizeof(struct stat)); + + if ((stat(filename, &filestat)) == -1) { + return (isc__errno2result(errno)); + } + + if (!S_ISREG(filestat.st_mode)) { + return (ISC_R_INVALIDFILE); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_file_isplainfilefd(int fd) { + /* + * This function returns success if filename is a plain file. + */ + struct stat filestat; + memset(&filestat, 0, sizeof(struct stat)); + + if ((fstat(fd, &filestat)) == -1) { + return (isc__errno2result(errno)); + } + + if (!S_ISREG(filestat.st_mode)) { + return (ISC_R_INVALIDFILE); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_file_isdirectory(const char *filename) { + /* + * This function returns success if filename exists and is a + * directory. + */ + struct stat filestat; + memset(&filestat, 0, sizeof(struct stat)); + + if ((stat(filename, &filestat)) == -1) { + return (isc__errno2result(errno)); + } + + if (!S_ISDIR(filestat.st_mode)) { + return (ISC_R_INVALIDFILE); + } + + return (ISC_R_SUCCESS); +} + +bool +isc_file_isabsolute(const char *filename) { + REQUIRE(filename != NULL); + return (filename[0] == '/'); +} + +bool +isc_file_iscurrentdir(const char *filename) { + REQUIRE(filename != NULL); + return (filename[0] == '.' && filename[1] == '\0'); +} + +bool +isc_file_ischdiridempotent(const char *filename) { + REQUIRE(filename != NULL); + if (isc_file_isabsolute(filename)) { + return (true); + } + if (isc_file_iscurrentdir(filename)) { + return (true); + } + return (false); +} + +const char * +isc_file_basename(const char *filename) { + const char *s; + + REQUIRE(filename != NULL); + + s = strrchr(filename, '/'); + if (s == NULL) { + return (filename); + } + + return (s + 1); +} + +isc_result_t +isc_file_progname(const char *filename, char *buf, size_t buflen) { + const char *base; + size_t len; + + REQUIRE(filename != NULL); + REQUIRE(buf != NULL); + + base = isc_file_basename(filename); + len = strlen(base) + 1; + + if (len > buflen) { + return (ISC_R_NOSPACE); + } + memmove(buf, base, len); + + return (ISC_R_SUCCESS); +} + +/* + * Put the absolute name of the current directory into 'dirname', which is + * a buffer of at least 'length' characters. End the string with the + * appropriate path separator, such that the final product could be + * concatenated with a relative pathname to make a valid pathname string. + */ +static isc_result_t +dir_current(char *dirname, size_t length) { + char *cwd; + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(dirname != NULL); + REQUIRE(length > 0U); + + cwd = getcwd(dirname, length); + + if (cwd == NULL) { + if (errno == ERANGE) { + result = ISC_R_NOSPACE; + } else { + result = isc__errno2result(errno); + } + } else { + if (strlen(dirname) + 1 == length) { + result = ISC_R_NOSPACE; + } else if (dirname[1] != '\0') { + strlcat(dirname, "/", length); + } + } + + return (result); +} + +isc_result_t +isc_file_absolutepath(const char *filename, char *path, size_t pathlen) { + isc_result_t result; + result = dir_current(path, pathlen); + if (result != ISC_R_SUCCESS) { + return (result); + } + if (strlen(path) + strlen(filename) + 1 > pathlen) { + return (ISC_R_NOSPACE); + } + strlcat(path, filename, pathlen); + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_file_truncate(const char *filename, isc_offset_t size) { + isc_result_t result = ISC_R_SUCCESS; + + if (truncate(filename, size) < 0) { + result = isc__errno2result(errno); + } + return (result); +} + +isc_result_t +isc_file_safecreate(const char *filename, FILE **fp) { + isc_result_t result; + int flags; + struct stat sb; + FILE *f; + int fd; + + REQUIRE(filename != NULL); + REQUIRE(fp != NULL && *fp == NULL); + + result = file_stats(filename, &sb); + if (result == ISC_R_SUCCESS) { + if ((sb.st_mode & S_IFREG) == 0) { + return (ISC_R_INVALIDFILE); + } + flags = O_WRONLY | O_TRUNC; + } else if (result == ISC_R_FILENOTFOUND) { + flags = O_WRONLY | O_CREAT | O_EXCL; + } else { + return (result); + } + + fd = open(filename, flags, S_IRUSR | S_IWUSR); + if (fd == -1) { + return (isc__errno2result(errno)); + } + + f = fdopen(fd, "w"); + if (f == NULL) { + result = isc__errno2result(errno); + close(fd); + return (result); + } + + *fp = f; + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_file_splitpath(isc_mem_t *mctx, const char *path, char **dirname, + char const **bname) { + char *dir; + const char *file, *slash; + + if (path == NULL) { + return (ISC_R_INVALIDFILE); + } + + slash = strrchr(path, '/'); + + if (slash == path) { + file = ++slash; + dir = isc_mem_strdup(mctx, "/"); + } else if (slash != NULL) { + file = ++slash; + dir = isc_mem_allocate(mctx, slash - path); + strlcpy(dir, path, slash - path); + } else { + file = path; + dir = isc_mem_strdup(mctx, "."); + } + + if (dir == NULL) { + return (ISC_R_NOMEMORY); + } + + if (*file == '\0') { + isc_mem_free(mctx, dir); + return (ISC_R_INVALIDFILE); + } + + *dirname = dir; + *bname = file; + + return (ISC_R_SUCCESS); +} + +void * +isc_file_mmap(void *addr, size_t len, int prot, int flags, int fd, + off_t offset) { +#ifdef HAVE_MMAP + return (mmap(addr, len, prot, flags, fd, offset)); +#else /* ifdef HAVE_MMAP */ + void *buf; + ssize_t ret; + off_t end; + + UNUSED(addr); + UNUSED(prot); + UNUSED(flags); + + end = lseek(fd, 0, SEEK_END); + lseek(fd, offset, SEEK_SET); + if (end - offset < (off_t)len) { + len = end - offset; + } + + buf = malloc(len); + if (buf == NULL) { + return (NULL); + } + + ret = read(fd, buf, len); + if (ret != (ssize_t)len) { + free(buf); + buf = NULL; + } + + return (buf); +#endif /* ifdef HAVE_MMAP */ +} + +int +isc_file_munmap(void *addr, size_t len) { +#ifdef HAVE_MMAP + return (munmap(addr, len)); +#else /* ifdef HAVE_MMAP */ + UNUSED(len); + + free(addr); + return (0); +#endif /* ifdef HAVE_MMAP */ +} + +#define DISALLOW "\\/ABCDEFGHIJKLMNOPQRSTUVWXYZ" + +static isc_result_t +digest2hex(unsigned char *digest, unsigned int digestlen, char *hash, + size_t hashlen) { + unsigned int i; + int ret; + for (i = 0; i < digestlen; i++) { + size_t left = hashlen - i * 2; + ret = snprintf(hash + i * 2, left, "%02x", digest[i]); + if (ret < 0 || (size_t)ret >= left) { + return (ISC_R_NOSPACE); + } + } + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_file_sanitize(const char *dir, const char *base, const char *ext, + char *path, size_t length) { + char buf[PATH_MAX]; + unsigned char digest[ISC_MAX_MD_SIZE]; + unsigned int digestlen; + char hash[ISC_MAX_MD_SIZE * 2 + 1]; + size_t l = 0; + isc_result_t err; + + REQUIRE(base != NULL); + REQUIRE(path != NULL); + + l = strlen(base) + 1; + + /* + * allow room for a full sha256 hash (64 chars + * plus null terminator) + */ + if (l < 65U) { + l = 65; + } + + if (dir != NULL) { + l += strlen(dir) + 1; + } + if (ext != NULL) { + l += strlen(ext) + 1; + } + + if (l > length || l > (unsigned)PATH_MAX) { + return (ISC_R_NOSPACE); + } + + /* Check whether the full-length SHA256 hash filename exists */ + err = isc_md(ISC_MD_SHA256, (const unsigned char *)base, strlen(base), + digest, &digestlen); + if (err != ISC_R_SUCCESS) { + return (err); + } + + err = digest2hex(digest, digestlen, hash, sizeof(hash)); + if (err != ISC_R_SUCCESS) { + return (err); + } + + snprintf(buf, sizeof(buf), "%s%s%s%s%s", dir != NULL ? dir : "", + dir != NULL ? "/" : "", hash, ext != NULL ? "." : "", + ext != NULL ? ext : ""); + if (isc_file_exists(buf)) { + strlcpy(path, buf, length); + return (ISC_R_SUCCESS); + } + + /* Check for a truncated SHA256 hash filename */ + hash[16] = '\0'; + snprintf(buf, sizeof(buf), "%s%s%s%s%s", dir != NULL ? dir : "", + dir != NULL ? "/" : "", hash, ext != NULL ? "." : "", + ext != NULL ? ext : ""); + if (isc_file_exists(buf)) { + strlcpy(path, buf, length); + return (ISC_R_SUCCESS); + } + + /* + * If neither hash filename already exists, then we'll use + * the original base name if it has no disallowed characters, + * or the truncated hash name if it does. + */ + if (strpbrk(base, DISALLOW) != NULL) { + strlcpy(path, buf, length); + return (ISC_R_SUCCESS); + } + + snprintf(buf, sizeof(buf), "%s%s%s%s%s", dir != NULL ? dir : "", + dir != NULL ? "/" : "", base, ext != NULL ? "." : "", + ext != NULL ? ext : ""); + strlcpy(path, buf, length); + return (ISC_R_SUCCESS); +} + +bool +isc_file_isdirwritable(const char *path) { + return (access(path, W_OK | X_OK) == 0); +} diff --git a/lib/isc/unix/fsaccess.c b/lib/isc/unix/fsaccess.c new file mode 100644 index 0000000..306cdfd --- /dev/null +++ b/lib/isc/unix/fsaccess.c @@ -0,0 +1,87 @@ +/* + * 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. + */ + +#include <errno.h> +#include <stdbool.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "errno2result.h" + +/*! \file + * \brief + * The OS-independent part of the API is in lib/isc. + */ +#include "../fsaccess.c" + +isc_result_t +isc_fsaccess_set(const char *path, isc_fsaccess_t access) { + struct stat statb; + mode_t mode; + bool is_dir = false; + isc_fsaccess_t bits; + isc_result_t result; + + if (stat(path, &statb) != 0) { + return (isc__errno2result(errno)); + } + + if ((statb.st_mode & S_IFDIR) != 0) { + is_dir = true; + } else if ((statb.st_mode & S_IFREG) == 0) { + return (ISC_R_INVALIDFILE); + } + + result = check_bad_bits(access, is_dir); + if (result != ISC_R_SUCCESS) { + return (result); + } + + /* + * Done with checking bad bits. Set mode_t. + */ + mode = 0; + +#define SET_AND_CLEAR1(modebit) \ + if ((access & bits) != 0) { \ + mode |= modebit; \ + access &= ~bits; \ + } +#define SET_AND_CLEAR(user, group, other) \ + SET_AND_CLEAR1(user); \ + bits <<= STEP; \ + SET_AND_CLEAR1(group); \ + bits <<= STEP; \ + SET_AND_CLEAR1(other); + + bits = ISC_FSACCESS_READ | ISC_FSACCESS_LISTDIRECTORY; + + SET_AND_CLEAR(S_IRUSR, S_IRGRP, S_IROTH); + + bits = ISC_FSACCESS_WRITE | ISC_FSACCESS_CREATECHILD | + ISC_FSACCESS_DELETECHILD; + + SET_AND_CLEAR(S_IWUSR, S_IWGRP, S_IWOTH); + + bits = ISC_FSACCESS_EXECUTE | ISC_FSACCESS_ACCESSCHILD; + + SET_AND_CLEAR(S_IXUSR, S_IXGRP, S_IXOTH); + + INSIST(access == 0); + + if (chmod(path, mode) < 0) { + return (isc__errno2result(errno)); + } + + return (ISC_R_SUCCESS); +} diff --git a/lib/isc/unix/ifiter_getifaddrs.c b/lib/isc/unix/ifiter_getifaddrs.c new file mode 100644 index 0000000..9f8b31a --- /dev/null +++ b/lib/isc/unix/ifiter_getifaddrs.c @@ -0,0 +1,240 @@ +/* + * 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. + */ + +/*! \file + * \brief + * Obtain the list of network interfaces using the getifaddrs(3) library. + */ + +#include <ifaddrs.h> +#include <stdbool.h> + +#include <isc/strerr.h> + +/*% Iterator Magic */ +#define IFITER_MAGIC ISC_MAGIC('I', 'F', 'I', 'G') +/*% Valid Iterator */ +#define VALID_IFITER(t) ISC_MAGIC_VALID(t, IFITER_MAGIC) + +#ifdef __linux +static bool seenv6 = false; +#endif /* ifdef __linux */ + +/*% Iterator structure */ +struct isc_interfaceiter { + unsigned int magic; /*%< Magic number. */ + isc_mem_t *mctx; + void *buf; /*%< (unused) */ + unsigned int bufsize; /*%< (always 0) */ + struct ifaddrs *ifaddrs; /*%< List of ifaddrs */ + struct ifaddrs *pos; /*%< Ptr to current ifaddr */ + isc_interface_t current; /*%< Current interface data. */ + isc_result_t result; /*%< Last result code. */ +#ifdef __linux + FILE *proc; + char entry[ISC_IF_INET6_SZ]; + isc_result_t valid; +#endif /* ifdef __linux */ +}; + +isc_result_t +isc_interfaceiter_create(isc_mem_t *mctx, isc_interfaceiter_t **iterp) { + isc_interfaceiter_t *iter; + isc_result_t result; + char strbuf[ISC_STRERRORSIZE]; + + REQUIRE(mctx != NULL); + REQUIRE(iterp != NULL); + REQUIRE(*iterp == NULL); + + iter = isc_mem_get(mctx, sizeof(*iter)); + + iter->mctx = mctx; + iter->buf = NULL; + iter->bufsize = 0; + iter->ifaddrs = NULL; +#ifdef __linux + /* + * Only open "/proc/net/if_inet6" if we have never seen a IPv6 + * address returned by getifaddrs(). + */ + if (!seenv6) { + iter->proc = fopen("/proc/net/if_inet6", "r"); + } else { + iter->proc = NULL; + } + iter->valid = ISC_R_FAILURE; +#endif /* ifdef __linux */ + + if (getifaddrs(&iter->ifaddrs) < 0) { + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "getting interface addresses: getifaddrs: %s", + strbuf); + result = ISC_R_UNEXPECTED; + goto failure; + } + + /* + * A newly created iterator has an undefined position + * until isc_interfaceiter_first() is called. + */ + iter->pos = NULL; + iter->result = ISC_R_FAILURE; + + iter->magic = IFITER_MAGIC; + *iterp = iter; + return (ISC_R_SUCCESS); + +failure: +#ifdef __linux + if (iter->proc != NULL) { + fclose(iter->proc); + } +#endif /* ifdef __linux */ + if (iter->ifaddrs != NULL) { /* just in case */ + freeifaddrs(iter->ifaddrs); + } + isc_mem_put(mctx, iter, sizeof(*iter)); + return (result); +} + +/* + * Get information about the current interface to iter->current. + * If successful, return ISC_R_SUCCESS. + * If the interface has an unsupported address family, + * return ISC_R_IGNORE. + */ + +static isc_result_t +internal_current(isc_interfaceiter_t *iter) { + struct ifaddrs *ifa; + int family; + unsigned int namelen; + + REQUIRE(VALID_IFITER(iter)); + + ifa = iter->pos; + +#ifdef __linux + if (iter->pos == NULL) { + return (linux_if_inet6_current(iter)); + } +#endif /* ifdef __linux */ + + INSIST(ifa != NULL); + INSIST(ifa->ifa_name != NULL); + + if (ifa->ifa_addr == NULL) { + return (ISC_R_IGNORE); + } + + family = ifa->ifa_addr->sa_family; + if (family != AF_INET && family != AF_INET6) { + return (ISC_R_IGNORE); + } + +#ifdef __linux + if (family == AF_INET6) { + seenv6 = true; + } +#endif /* ifdef __linux */ + + memset(&iter->current, 0, sizeof(iter->current)); + + namelen = strlen(ifa->ifa_name); + if (namelen > sizeof(iter->current.name) - 1) { + namelen = sizeof(iter->current.name) - 1; + } + + memset(iter->current.name, 0, sizeof(iter->current.name)); + memmove(iter->current.name, ifa->ifa_name, namelen); + + iter->current.flags = 0; + + if ((ifa->ifa_flags & IFF_UP) != 0) { + iter->current.flags |= INTERFACE_F_UP; + } + + if ((ifa->ifa_flags & IFF_POINTOPOINT) != 0) { + iter->current.flags |= INTERFACE_F_POINTTOPOINT; + } + + if ((ifa->ifa_flags & IFF_LOOPBACK) != 0) { + iter->current.flags |= INTERFACE_F_LOOPBACK; + } + + iter->current.af = family; + + get_addr(family, &iter->current.address, ifa->ifa_addr, ifa->ifa_name); + + if (ifa->ifa_netmask != NULL) { + get_addr(family, &iter->current.netmask, ifa->ifa_netmask, + ifa->ifa_name); + } + + if (ifa->ifa_dstaddr != NULL && + (iter->current.flags & INTERFACE_F_POINTTOPOINT) != 0) + { + get_addr(family, &iter->current.dstaddress, ifa->ifa_dstaddr, + ifa->ifa_name); + } + + return (ISC_R_SUCCESS); +} + +/* + * Step the iterator to the next interface. Unlike + * isc_interfaceiter_next(), this may leave the iterator + * positioned on an interface that will ultimately + * be ignored. Return ISC_R_NOMORE if there are no more + * interfaces, otherwise ISC_R_SUCCESS. + */ +static isc_result_t +internal_next(isc_interfaceiter_t *iter) { + if (iter->pos != NULL) { + iter->pos = iter->pos->ifa_next; + } + if (iter->pos == NULL) { +#ifdef __linux + if (!seenv6) { + return (linux_if_inet6_next(iter)); + } +#endif /* ifdef __linux */ + return (ISC_R_NOMORE); + } + + return (ISC_R_SUCCESS); +} + +static void +internal_destroy(isc_interfaceiter_t *iter) { +#ifdef __linux + if (iter->proc != NULL) { + fclose(iter->proc); + } + iter->proc = NULL; +#endif /* ifdef __linux */ + if (iter->ifaddrs) { + freeifaddrs(iter->ifaddrs); + } + iter->ifaddrs = NULL; +} + +static void +internal_first(isc_interfaceiter_t *iter) { +#ifdef __linux + linux_if_inet6_first(iter); +#endif /* ifdef __linux */ + iter->pos = iter->ifaddrs; +} diff --git a/lib/isc/unix/include/.clang-format b/lib/isc/unix/include/.clang-format new file mode 120000 index 0000000..e919bba --- /dev/null +++ b/lib/isc/unix/include/.clang-format @@ -0,0 +1 @@ +../../../../.clang-format.headers
\ No newline at end of file diff --git a/lib/isc/unix/include/Makefile.in b/lib/isc/unix/include/Makefile.in new file mode 100644 index 0000000..60a0a67 --- /dev/null +++ b/lib/isc/unix/include/Makefile.in @@ -0,0 +1,19 @@ +# 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. + +srcdir = @srcdir@ +VPATH = @srcdir@ +top_srcdir = @top_srcdir@ + +SUBDIRS = isc +TARGETS = + +@BIND9_MAKE_RULES@ diff --git a/lib/isc/unix/include/isc/Makefile.in b/lib/isc/unix/include/isc/Makefile.in new file mode 100644 index 0000000..06f7806 --- /dev/null +++ b/lib/isc/unix/include/isc/Makefile.in @@ -0,0 +1,37 @@ +# 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. + +srcdir = @srcdir@ +VPATH = @srcdir@ +top_srcdir = @top_srcdir@ + +VERSION=@BIND9_VERSION@ + +HEADERS = align.h dir.h net.h netdb.h offset.h stat.h \ + stdatomic.h stdtime.h syslog.h time.h + +SUBDIRS = +TARGETS = + +@BIND9_MAKE_RULES@ + +installdirs: + $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${includedir}/isc + +install:: installdirs + for i in ${HEADERS}; do \ + ${INSTALL_DATA} $(srcdir)/$$i ${DESTDIR}${includedir}/isc || exit 1; \ + done + +uninstall:: + for i in ${HEADERS}; do \ + rm -f ${DESTDIR}${includedir}/isc/$$i || exit 1; \ + done diff --git a/lib/isc/unix/include/isc/align.h b/lib/isc/unix/include/isc/align.h new file mode 100644 index 0000000..7b72e9d --- /dev/null +++ b/lib/isc/unix/include/isc/align.h @@ -0,0 +1,20 @@ +/* + * 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. + */ + +#pragma once + +#ifdef HAVE_STDALIGN_H +#include <stdalign.h> +#else /* ifdef HAVE_STDALIGN_H */ +#define alignas(x) __attribute__((__aligned__(x))) +#endif /* ifdef HAVE_STDALIGN_H */ diff --git a/lib/isc/unix/include/isc/dir.h b/lib/isc/unix/include/isc/dir.h new file mode 100644 index 0000000..89d1338 --- /dev/null +++ b/lib/isc/unix/include/isc/dir.h @@ -0,0 +1,75 @@ +/* + * 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. + */ + +#ifndef ISC_DIR_H +#define ISC_DIR_H 1 + +/*! \file */ + +#include <dirent.h> + +#include <isc/lang.h> +#include <isc/platform.h> +#include <isc/result.h> + +#include <sys/types.h> /* Required on some systems. */ + +/*% Directory Entry */ +typedef struct isc_direntry { + char name[NAME_MAX]; + unsigned int length; +} isc_direntry_t; + +/*% Directory */ +typedef struct isc_dir { + unsigned int magic; + char dirname[PATH_MAX]; + isc_direntry_t entry; + DIR *handle; +} isc_dir_t; + +ISC_LANG_BEGINDECLS + +void +isc_dir_init(isc_dir_t *dir); + +isc_result_t +isc_dir_open(isc_dir_t *dir, const char *dirname); + +isc_result_t +isc_dir_read(isc_dir_t *dir); + +isc_result_t +isc_dir_reset(isc_dir_t *dir); + +void +isc_dir_close(isc_dir_t *dir); + +isc_result_t +isc_dir_chdir(const char *dirname); + +isc_result_t +isc_dir_chroot(const char *dirname); + +isc_result_t +isc_dir_createunique(char *templet); +/*!< + * Use a templet (such as from isc_file_mktemplate()) to create a uniquely + * named, empty directory. The templet string is modified in place. + * If result == ISC_R_SUCCESS, it is the name of the directory that was + * created. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_DIR_H */ diff --git a/lib/isc/unix/include/isc/net.h b/lib/isc/unix/include/isc/net.h new file mode 100644 index 0000000..ead9c7f --- /dev/null +++ b/lib/isc/unix/include/isc/net.h @@ -0,0 +1,310 @@ +/* + * 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. + */ + +#ifndef ISC_NET_H +#define ISC_NET_H 1 + +/***** +***** Module Info +*****/ + +/*! \file + * \brief + * Basic Networking Types + * + * This module is responsible for defining the following basic networking + * types: + * + *\li struct in_addr + *\li struct in6_addr + *\li struct in6_pktinfo + *\li struct sockaddr + *\li struct sockaddr_in + *\li struct sockaddr_in6 + *\li struct sockaddr_storage + *\li in_port_t + * + * It ensures that the AF_ and PF_ macros are defined. + * + * It declares ntoh[sl]() and hton[sl](). + * + * It declares inet_ntop(), and inet_pton(). + * + * It ensures that #INADDR_LOOPBACK, #INADDR_ANY, #IN6ADDR_ANY_INIT, + * IN6ADDR_V4MAPPED_INIT, in6addr_any, and in6addr_loopback are available. + * + * It ensures that IN_MULTICAST() is available to check for multicast + * addresses. + * + * MP: + *\li No impact. + * + * Reliability: + *\li No anticipated impact. + * + * Resources: + *\li N/A. + * + * Security: + *\li No anticipated impact. + * + * Standards: + *\li BSD Socket API + *\li RFC2553 + */ + +/*** + *** Imports. + ***/ +#include <inttypes.h> + +#include <isc/lang.h> +#include <isc/platform.h> +#include <isc/types.h> + +#include <arpa/inet.h> /* Contractual promise. */ +#include <net/if.h> +#include <netinet/in.h> /* Contractual promise. */ +#include <sys/socket.h> /* Contractual promise. */ +#include <sys/types.h> + +#ifndef IN6ADDR_LOOPBACK_INIT +#ifdef s6_addr +/*% IPv6 address loopback init */ +#define IN6ADDR_LOOPBACK_INIT \ + { \ + { \ + { \ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 \ + } \ + } \ + } +#else /* ifdef s6_addr */ +#define IN6ADDR_LOOPBACK_INIT \ + { \ + { \ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 \ + } \ + } +#endif /* ifdef s6_addr */ +#endif /* ifndef IN6ADDR_LOOPBACK_INIT */ + +#ifndef IN6ADDR_V4MAPPED_INIT +#ifdef s6_addr +/*% IPv6 v4mapped prefix init */ +#define IN6ADDR_V4MAPPED_INIT \ + { \ + { \ + { \ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0, \ + 0, 0, 0 \ + } \ + } \ + } +#else /* ifdef s6_addr */ +#define IN6ADDR_V4MAPPED_INIT \ + { \ + { \ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0, 0, 0, 0 \ + } \ + } +#endif /* ifdef s6_addr */ +#endif /* ifndef IN6ADDR_V4MAPPED_INIT */ + +#ifndef IN6_IS_ADDR_V4MAPPED +/*% Is IPv6 address V4 mapped? */ +#define IN6_IS_ADDR_V4MAPPED(x) \ + (memcmp((x)->s6_addr, in6addr_any.s6_addr, 10) == 0 && \ + (x)->s6_addr[10] == 0xff && (x)->s6_addr[11] == 0xff) +#endif /* ifndef IN6_IS_ADDR_V4MAPPED */ + +#ifndef IN6_IS_ADDR_V4COMPAT +/*% Is IPv6 address V4 compatible? */ +#define IN6_IS_ADDR_V4COMPAT(x) \ + (memcmp((x)->s6_addr, in6addr_any.s6_addr, 12) == 0 && \ + ((x)->s6_addr[12] != 0 || (x)->s6_addr[13] != 0 || \ + (x)->s6_addr[14] != 0 || \ + ((x)->s6_addr[15] != 0 && (x)->s6_addr[15] != 1))) +#endif /* ifndef IN6_IS_ADDR_V4COMPAT */ + +#ifndef IN6_IS_ADDR_MULTICAST +/*% Is IPv6 address multicast? */ +#define IN6_IS_ADDR_MULTICAST(a) ((a)->s6_addr[0] == 0xff) +#endif /* ifndef IN6_IS_ADDR_MULTICAST */ + +#ifndef IN6_IS_ADDR_LINKLOCAL +/*% Is IPv6 address linklocal? */ +#define IN6_IS_ADDR_LINKLOCAL(a) \ + (((a)->s6_addr[0] == 0xfe) && (((a)->s6_addr[1] & 0xc0) == 0x80)) +#endif /* ifndef IN6_IS_ADDR_LINKLOCAL */ + +#ifndef IN6_IS_ADDR_SITELOCAL +/*% is IPv6 address sitelocal? */ +#define IN6_IS_ADDR_SITELOCAL(a) \ + (((a)->s6_addr[0] == 0xfe) && (((a)->s6_addr[1] & 0xc0) == 0xc0)) +#endif /* ifndef IN6_IS_ADDR_SITELOCAL */ + +#ifndef IN6_IS_ADDR_LOOPBACK +/*% is IPv6 address loopback? */ +#define IN6_IS_ADDR_LOOPBACK(x) \ + (memcmp((x)->s6_addr, in6addr_loopback.s6_addr, 16) == 0) +#endif /* ifndef IN6_IS_ADDR_LOOPBACK */ + +#ifndef AF_INET6 +/*% IPv6 */ +#define AF_INET6 99 +#endif /* ifndef AF_INET6 */ + +#ifndef PF_INET6 +/*% IPv6 */ +#define PF_INET6 AF_INET6 +#endif /* ifndef PF_INET6 */ + +#ifndef INADDR_ANY +/*% inaddr any */ +#define INADDR_ANY 0x00000000UL +#endif /* ifndef INADDR_ANY */ + +#ifndef INADDR_LOOPBACK +/*% inaddr loopback */ +#define INADDR_LOOPBACK 0x7f000001UL +#endif /* ifndef INADDR_LOOPBACK */ + +#ifndef MSG_TRUNC +/*% + * If this system does not have MSG_TRUNC (as returned from recvmsg()) + * ISC_PLATFORM_RECVOVERFLOW will be defined. This will enable the MSG_TRUNC + * faking code in socket.c. + */ +#define ISC_PLATFORM_RECVOVERFLOW +#endif /* ifndef MSG_TRUNC */ + +/*% IP address. */ +#define ISC__IPADDR(x) ((uint32_t)htonl((uint32_t)(x))) + +/*% Is IP address multicast? */ +#define ISC_IPADDR_ISMULTICAST(i) \ + (((uint32_t)(i)&ISC__IPADDR(0xf0000000)) == ISC__IPADDR(0xe0000000)) + +#define ISC_IPADDR_ISEXPERIMENTAL(i) \ + (((uint32_t)(i)&ISC__IPADDR(0xf0000000)) == ISC__IPADDR(0xf0000000)) + +/*** + *** Functions. + ***/ + +ISC_LANG_BEGINDECLS + +isc_result_t +isc_net_probeipv4(void); +/*%< + * Check if the system's kernel supports IPv4. + * + * Returns: + * + *\li #ISC_R_SUCCESS IPv4 is supported. + *\li #ISC_R_NOTFOUND IPv4 is not supported. + *\li #ISC_R_DISABLED IPv4 is disabled. + *\li #ISC_R_UNEXPECTED + */ + +isc_result_t +isc_net_probeipv6(void); +/*%< + * Check if the system's kernel supports IPv6. + * + * Returns: + * + *\li #ISC_R_SUCCESS IPv6 is supported. + *\li #ISC_R_NOTFOUND IPv6 is not supported. + *\li #ISC_R_DISABLED IPv6 is disabled. + *\li #ISC_R_UNEXPECTED + */ + +isc_result_t +isc_net_probe_ipv6only(void); +/*%< + * Check if the system's kernel supports the IPV6_V6ONLY socket option. + * + * Returns: + * + *\li #ISC_R_SUCCESS the option is supported for both TCP and UDP. + *\li #ISC_R_NOTFOUND IPv6 itself or the option is not supported. + *\li #ISC_R_UNEXPECTED + */ + +isc_result_t +isc_net_probe_ipv6pktinfo(void); +/* + * Check if the system's kernel supports the IPV6_(RECV)PKTINFO socket option + * for UDP sockets. + * + * Returns: + * + * \li #ISC_R_SUCCESS the option is supported. + * \li #ISC_R_NOTFOUND IPv6 itself or the option is not supported. + * \li #ISC_R_UNEXPECTED + */ + +void +isc_net_disableipv4(void); + +void +isc_net_disableipv6(void); + +void +isc_net_enableipv4(void); + +void +isc_net_enableipv6(void); + +isc_result_t +isc_net_probeunix(void); +/* + * Returns whether UNIX domain sockets are supported. + */ + +#define ISC_NET_DSCPRECVV4 0x01 /* Can receive sent DSCP value IPv4 */ +#define ISC_NET_DSCPRECVV6 0x02 /* Can receive sent DSCP value IPv6 */ +#define ISC_NET_DSCPSETV4 0x04 /* Can set DSCP on socket IPv4 */ +#define ISC_NET_DSCPSETV6 0x08 /* Can set DSCP on socket IPv6 */ +#define ISC_NET_DSCPPKTV4 0x10 /* Can set DSCP on per packet IPv4 */ +#define ISC_NET_DSCPPKTV6 0x20 /* Can set DSCP on per packet IPv6 */ +#define ISC_NET_DSCPALL 0x3f /* All valid flags */ + +unsigned int +isc_net_probedscp(void); +/*%< + * Probe the level of DSCP support. + */ + +isc_result_t +isc_net_getudpportrange(int af, in_port_t *low, in_port_t *high); +/*%< + * Returns system's default range of ephemeral UDP ports, if defined. + * If the range is not available or unknown, ISC_NET_PORTRANGELOW and + * ISC_NET_PORTRANGEHIGH will be returned. + * + * Requires: + * + *\li 'low' and 'high' must be non NULL. + * + * Returns: + * + *\li *low and *high will be the ports specifying the low and high ends of + * the range. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_NET_H */ diff --git a/lib/isc/unix/include/isc/netdb.h b/lib/isc/unix/include/isc/netdb.h new file mode 100644 index 0000000..dab845a --- /dev/null +++ b/lib/isc/unix/include/isc/netdb.h @@ -0,0 +1,51 @@ +/* + * 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. + */ + +#ifndef ISC_NETDB_H +#define ISC_NETDB_H 1 + +/***** +***** Module Info +*****/ + +/*! \file + * \brief + * Portable netdb.h support. + * + * This module is responsible for defining the get<x>by<y> APIs. + * + * MP: + *\li No impact. + * + * Reliability: + *\li No anticipated impact. + * + * Resources: + *\li N/A. + * + * Security: + *\li No anticipated impact. + * + * Standards: + *\li BSD API + */ + +/*** + *** Imports. + ***/ + +#include <netdb.h> + +#include <isc/net.h> + +#endif /* ISC_NETDB_H */ diff --git a/lib/isc/unix/include/isc/offset.h b/lib/isc/unix/include/isc/offset.h new file mode 100644 index 0000000..fa24ae4 --- /dev/null +++ b/lib/isc/unix/include/isc/offset.h @@ -0,0 +1,28 @@ +/* + * 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. + */ + +#ifndef ISC_OFFSET_H +#define ISC_OFFSET_H 1 + +/*! \file + * \brief + * File offsets are operating-system dependent. + */ +#include <limits.h> /* Required for CHAR_BIT. */ +#include <stddef.h> /* For Linux Standard Base. */ + +#include <sys/types.h> + +typedef off_t isc_offset_t; + +#endif /* ISC_OFFSET_H */ diff --git a/lib/isc/unix/include/isc/stat.h b/lib/isc/unix/include/isc/stat.h new file mode 100644 index 0000000..6ddddd7 --- /dev/null +++ b/lib/isc/unix/include/isc/stat.h @@ -0,0 +1,47 @@ +/* + * 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. + */ + +#ifndef ISC_STAT_H +#define ISC_STAT_H 1 + +/***** +***** Module Info +*****/ + +/* + * Portable <sys/stat.h> support. + * + * This module is responsible for defining S_IS??? macros. + * + * MP: + * No impact. + * + * Reliability: + * No anticipated impact. + * + * Resources: + * N/A. + * + * Security: + * No anticipated impact. + * + */ + +/*** + *** Imports. + ***/ + +#include <sys/stat.h> +#include <sys/types.h> + +#endif /* ISC_STAT_H */ diff --git a/lib/isc/unix/include/isc/stdatomic.h b/lib/isc/unix/include/isc/stdatomic.h new file mode 100644 index 0000000..f071b6a --- /dev/null +++ b/lib/isc/unix/include/isc/stdatomic.h @@ -0,0 +1,224 @@ +/* + * 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. + */ + +#pragma once + +#include <inttypes.h> +#include <stdbool.h> +#include <stddef.h> +#if HAVE_UCHAR_H +#include <uchar.h> +#endif /* HAVE_UCHAR_H */ + +#if !defined(__has_feature) +#define __has_feature(x) 0 +#endif /* if !defined(__has_feature) */ + +#if !defined(__has_extension) +#define __has_extension(x) __has_feature(x) +#endif /* if !defined(__has_extension) */ + +#if !defined(__GNUC_PREREQ__) +#if defined(__GNUC__) && defined(__GNUC_MINOR__) +#define __GNUC_PREREQ__(maj, min) \ + ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min)) +#else /* if defined(__GNUC__) && defined(__GNUC_MINOR__) */ +#define __GNUC_PREREQ__(maj, min) 0 +#endif /* if defined(__GNUC__) && defined(__GNUC_MINOR__) */ +#endif /* if !defined(__GNUC_PREREQ__) */ + +#if !defined(__CLANG_ATOMICS) && !defined(__GNUC_ATOMICS) +#if __has_extension(c_atomic) || __has_extension(cxx_atomic) +#define __CLANG_ATOMICS +#elif __GNUC_PREREQ__(4, 7) +#define __GNUC_ATOMICS +#elif !defined(__GNUC__) +#error "isc/stdatomic.h does not support your compiler" +#endif /* if __has_extension(c_atomic) || __has_extension(cxx_atomic) */ +#endif /* if !defined(__CLANG_ATOMICS) && !defined(__GNUC_ATOMICS) */ + +#ifndef __ATOMIC_RELAXED +#define __ATOMIC_RELAXED 0 +#endif /* ifndef __ATOMIC_RELAXED */ +#ifndef __ATOMIC_CONSUME +#define __ATOMIC_CONSUME 1 +#endif /* ifndef __ATOMIC_CONSUME */ +#ifndef __ATOMIC_ACQUIRE +#define __ATOMIC_ACQUIRE 2 +#endif /* ifndef __ATOMIC_ACQUIRE */ +#ifndef __ATOMIC_RELEASE +#define __ATOMIC_RELEASE 3 +#endif /* ifndef __ATOMIC_RELEASE */ +#ifndef __ATOMIC_ACQ_REL +#define __ATOMIC_ACQ_REL 4 +#endif /* ifndef __ATOMIC_ACQ_REL */ +#ifndef __ATOMIC_SEQ_CST +#define __ATOMIC_SEQ_CST 5 +#endif /* ifndef __ATOMIC_SEQ_CST */ + +enum memory_order { + memory_order_relaxed = __ATOMIC_RELAXED, + memory_order_consume = __ATOMIC_CONSUME, + memory_order_acquire = __ATOMIC_ACQUIRE, + memory_order_release = __ATOMIC_RELEASE, + memory_order_acq_rel = __ATOMIC_ACQ_REL, + memory_order_seq_cst = __ATOMIC_SEQ_CST +}; + +typedef enum memory_order memory_order; + +#ifndef HAVE_UCHAR_H +typedef uint_least16_t char16_t; +typedef uint_least32_t char32_t; +#endif /* HAVE_UCHAR_H */ + +typedef bool atomic_bool; +typedef char atomic_char; +typedef signed char atomic_schar; +typedef unsigned char atomic_uchar; +typedef short atomic_short; +typedef unsigned short atomic_ushort; +typedef int atomic_int; +typedef unsigned int atomic_uint; +typedef long atomic_long; +typedef unsigned long atomic_ulong; +typedef long long atomic_llong; +typedef unsigned long long atomic_ullong; +typedef char16_t atomic_char16_t; +typedef char32_t atomic_char32_t; +typedef wchar_t atomic_wchar_t; +typedef int_least8_t atomic_int_least8_t; +typedef uint_least8_t atomic_uint_least8_t; +typedef int_least16_t atomic_int_least16_t; +typedef uint_least16_t atomic_uint_least16_t; +typedef int_least32_t atomic_int_least32_t; +typedef uint_least32_t atomic_uint_least32_t; +typedef int_least64_t atomic_int_least64_t; +typedef uint_least64_t atomic_uint_least64_t; +typedef int_fast8_t atomic_int_fast8_t; +typedef uint_fast8_t atomic_uint_fast8_t; +typedef int_fast16_t atomic_int_fast16_t; +typedef uint_fast16_t atomic_uint_fast16_t; +typedef int_fast32_t atomic_int_fast32_t; +typedef uint_fast32_t atomic_uint_fast32_t; +typedef int_fast64_t atomic_int_fast64_t; +typedef uint_fast64_t atomic_uint_fast64_t; +typedef intptr_t atomic_intptr_t; +typedef uintptr_t atomic_uintptr_t; +typedef size_t atomic_size_t; +typedef ptrdiff_t atomic_ptrdiff_t; +typedef intmax_t atomic_intmax_t; +typedef uintmax_t atomic_uintmax_t; + +#if defined(__CLANG_ATOMICS) /* __c11_atomic builtins */ +#define atomic_init(obj, desired) __c11_atomic_init(obj, desired) +#define atomic_load_explicit(obj, order) __c11_atomic_load(obj, order) +#define atomic_store_explicit(obj, desired, order) \ + __c11_atomic_store(obj, desired, order) +#define atomic_fetch_add_explicit(obj, arg, order) \ + __c11_atomic_fetch_add(obj, arg, order) +#define atomic_fetch_sub_explicit(obj, arg, order) \ + __c11_atomic_fetch_sub(obj, arg, order) +#define atomic_fetch_and_explicit(obj, arg, order) \ + __c11_atomic_fetch_and(obj, arg, order) +#define atomic_fetch_or_explicit(obj, arg, order) \ + __c11_atomic_fetch_or(obj, arg, order) +#define atomic_compare_exchange_strong_explicit(obj, expected, desired, succ, \ + fail) \ + __c11_atomic_compare_exchange_strong_explicit(obj, expected, desired, \ + succ, fail) +#define atomic_compare_exchange_weak_explicit(obj, expected, desired, succ, \ + fail) \ + __c11_atomic_compare_exchange_weak_explicit(obj, expected, desired, \ + succ, fail) +#define atomic_exchange_explicit(obj, desired, order) \ + __c11_atomic_exchange_explicit(obj, expected, order) +#elif defined(__GNUC_ATOMICS) /* __atomic builtins */ +#define atomic_init(obj, desired) (*obj = desired) +#define atomic_load_explicit(obj, order) __atomic_load_n(obj, order) +#define atomic_store_explicit(obj, desired, order) \ + __atomic_store_n(obj, desired, order) +#define atomic_fetch_add_explicit(obj, arg, order) \ + __atomic_fetch_add(obj, arg, order) +#define atomic_fetch_sub_explicit(obj, arg, order) \ + __atomic_fetch_sub(obj, arg, order) +#define atomic_fetch_and_explicit(obj, arg, order) \ + __atomic_fetch_and(obj, arg, order) +#define atomic_fetch_or_explicit(obj, arg, order) \ + __atomic_fetch_or(obj, arg, order) +#define atomic_compare_exchange_strong_explicit(obj, expected, desired, succ, \ + fail) \ + __atomic_compare_exchange_n(obj, expected, desired, 0, succ, fail) +#define atomic_compare_exchange_weak_explicit(obj, expected, desired, succ, \ + fail) \ + __atomic_compare_exchange_n(obj, expected, desired, 1, succ, fail) +#define atomic_exchange_explicit(obj, desired, order) \ + __atomic_exchange_n(obj, desired, order) +#else /* __sync builtins */ +#define atomic_init(obj, desired) (*obj = desired) +#define atomic_load_explicit(obj, order) __sync_fetch_and_add(obj, 0) +#define atomic_store_explicit(obj, desired, order) \ + do { \ + __sync_synchronize(); \ + *obj = desired; \ + __sync_synchronize(); \ + } while (0); +#define atomic_fetch_add_explicit(obj, arg, order) \ + __sync_fetch_and_add(obj, arg) +#define atomic_fetch_sub_explicit(obj, arg, order) \ + __sync_fetch_and_sub(obj, arg, order) +#define atomic_fetch_and_explicit(obj, arg, order) \ + __sync_fetch_and_and(obj, arg, order) +#define atomic_fetch_or_explicit(obj, arg, order) \ + __sync_fetch_and_or(obj, arg, order) +#define atomic_compare_exchange_strong_explicit(obj, expected, desired, succ, \ + fail) \ + ({ \ + __typeof__(obj) __v; \ + _Bool __r; \ + __v = (__typeof__(obj))__sync_val_compare_and_swap( \ + obj, *(expected), desired); \ + __r = ((__typeof__(obj))*(expected) == __v); \ + *(expected) = __v; \ + __r; \ + }) +#define atomic_compare_exchange_weak_explicit(obj, expected, desired, succ, \ + fail) \ + atomic_compare_exchange_strong_explicit(obj, expected, desired, succ, \ + fail) +#define atomic_exchange_explicit(obj, desired, order) \ + __sync_lock_test_and_set(obj, desired) + +#endif /* if defined(__CLANG_ATOMICS) */ + +#define atomic_load(obj) atomic_load_explicit(obj, memory_order_seq_cst) +#define atomic_store(obj, arg) \ + atomic_store_explicit(obj, arg, memory_order_seq_cst) +#define atomic_fetch_add(obj, arg) \ + atomic_fetch_add_explicit(obj, arg, memory_order_seq_cst) +#define atomic_fetch_sub(obj, arg) \ + atomic_fetch_sub_explicit(obj, arg, memory_order_seq_cst) +#define atomic_fetch_and(obj, arg) \ + atomic_fetch_and_explicit(obj, arg, memory_order_seq_cst) +#define atomic_fetch_or(obj, arg) \ + atomic_fetch_or_explicit(obj, arg, memory_order_seq_cst) +#define atomic_compare_exchange_strong(obj, expected, desired) \ + atomic_compare_exchange_strong_explicit(obj, expected, desired, \ + memory_order_seq_cst, \ + memory_order_seq_cst) +#define atomic_compare_exchange_weak(obj, expected, desired) \ + atomic_compare_exchange_weak_explicit(obj, expected, desired, \ + memory_order_seq_cst, \ + memory_order_seq_cst) +#define atomic_exchange(obj, desired) \ + atomic_exchange_explicit(obj, desired, memory_order_seq_cst) diff --git a/lib/isc/unix/include/isc/stdtime.h b/lib/isc/unix/include/isc/stdtime.h new file mode 100644 index 0000000..e38a2c6 --- /dev/null +++ b/lib/isc/unix/include/isc/stdtime.h @@ -0,0 +1,64 @@ +/* + * 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. + */ + +#ifndef ISC_STDTIME_H +#define ISC_STDTIME_H 1 + +/*! \file */ + +#include <inttypes.h> +#include <stdlib.h> + +#include <isc/lang.h> + +/*% + * It's public information that 'isc_stdtime_t' is an unsigned integral type. + * Applications that want maximum portability should not assume anything + * about its size. + */ +typedef uint32_t isc_stdtime_t; + +ISC_LANG_BEGINDECLS +/* */ +void +isc_stdtime_get(isc_stdtime_t *t); +/*%< + * Set 't' to the number of seconds since 00:00:00 UTC, January 1, 1970. + * + * Requires: + * + *\li 't' is a valid pointer. + */ + +void +isc_stdtime_tostring(isc_stdtime_t t, char *out, size_t outlen); +/* + * Convert 't' into a null-terminated string of the form + * "Wed Jun 30 21:49:08 1993". Store the string in the 'out' + * buffer. + * + * Requires: + * + * 't' is a valid time. + * 'out' is a valid pointer. + * 'outlen' is at least 26. + */ + +#define isc_stdtime_convert32(t, t32p) (*(t32p) = t) +/* + * Convert the standard time to its 32-bit version. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_STDTIME_H */ diff --git a/lib/isc/unix/include/isc/syslog.h b/lib/isc/unix/include/isc/syslog.h new file mode 100644 index 0000000..f46dc1a --- /dev/null +++ b/lib/isc/unix/include/isc/syslog.h @@ -0,0 +1,41 @@ +/* + * 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. + */ + +#ifndef ISC_SYSLOG_H +#define ISC_SYSLOG_H 1 + +/*! \file */ + +#include <isc/lang.h> +#include <isc/types.h> + +ISC_LANG_BEGINDECLS + +isc_result_t +isc_syslog_facilityfromstring(const char *str, int *facilityp); +/*%< + * Convert 'str' to the appropriate syslog facility constant. + * + * Requires: + * + *\li 'str' is not NULL + *\li 'facilityp' is not NULL + * + * Returns: + * \li #ISC_R_SUCCESS + * \li #ISC_R_NOTFOUND + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_SYSLOG_H */ diff --git a/lib/isc/unix/include/isc/time.h b/lib/isc/unix/include/isc/time.h new file mode 100644 index 0000000..ecec64d --- /dev/null +++ b/lib/isc/unix/include/isc/time.h @@ -0,0 +1,452 @@ +/* + * 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. + */ + +#ifndef ISC_TIME_H +#define ISC_TIME_H 1 + +/*! \file */ + +#include <inttypes.h> +#include <stdbool.h> + +#include <isc/lang.h> +#include <isc/types.h> + +/*** + *** Intervals + ***/ + +/*! + * \brief + * The contents of this structure are private, and MUST NOT be accessed + * directly by callers. + * + * The contents are exposed only to allow callers to avoid dynamic allocation. + */ +struct isc_interval { + unsigned int seconds; + unsigned int nanoseconds; +}; + +extern const isc_interval_t *const isc_interval_zero; + +/* + * ISC_FORMATHTTPTIMESTAMP_SIZE needs to be 30 in C locale and potentially + * more for other locales to handle longer national abbreviations when + * expanding strftime's %a and %b. + */ +#define ISC_FORMATHTTPTIMESTAMP_SIZE 50 + +ISC_LANG_BEGINDECLS + +void +isc_interval_set(isc_interval_t *i, unsigned int seconds, + unsigned int nanoseconds); +/*%< + * Set 'i' to a value representing an interval of 'seconds' seconds and + * 'nanoseconds' nanoseconds, suitable for use in isc_time_add() and + * isc_time_subtract(). + * + * Requires: + * + *\li 't' is a valid pointer. + *\li nanoseconds < 1000000000. + */ + +bool +isc_interval_iszero(const isc_interval_t *i); +/*%< + * Returns true iff. 'i' is the zero interval. + * + * Requires: + * + *\li 'i' is a valid pointer. + */ + +/*** + *** Absolute Times + ***/ + +/*% + * The contents of this structure are private, and MUST NOT be accessed + * directly by callers. + * + * The contents are exposed only to allow callers to avoid dynamic allocation. + */ + +struct isc_time { + unsigned int seconds; + unsigned int nanoseconds; +}; + +extern const isc_time_t *const isc_time_epoch; + +void +isc_time_set(isc_time_t *t, unsigned int seconds, unsigned int nanoseconds); +/*%< + * Set 't' to a value which represents the given number of seconds and + * nanoseconds since 00:00:00 January 1, 1970, UTC. + * + * Notes: + *\li The Unix version of this call is equivalent to: + *\code + * isc_time_settoepoch(t); + * isc_interval_set(i, seconds, nanoseconds); + * isc_time_add(t, i, t); + *\endcode + * + * Requires: + *\li 't' is a valid pointer. + *\li nanoseconds < 1000000000. + */ + +void +isc_time_settoepoch(isc_time_t *t); +/*%< + * Set 't' to the time of the epoch. + * + * Notes: + *\li The date of the epoch is platform-dependent. + * + * Requires: + * + *\li 't' is a valid pointer. + */ + +bool +isc_time_isepoch(const isc_time_t *t); +/*%< + * Returns true iff. 't' is the epoch ("time zero"). + * + * Requires: + * + *\li 't' is a valid pointer. + */ + +isc_result_t +isc_time_now(isc_time_t *t); +/*%< + * Set 't' to the current absolute time. + * + * Requires: + * + *\li 't' is a valid pointer. + * + * Returns: + * + *\li Success + *\li Unexpected error + * Getting the time from the system failed. + *\li Out of range + * The time from the system is too large to be represented + * in the current definition of isc_time_t. + */ + +isc_result_t +isc_time_now_hires(isc_time_t *t); +/*%< + * Set 't' to the current absolute time. Uses higher resolution clocks + * recommended when microsecond accuracy is required. + * + * Requires: + * + *\li 't' is a valid pointer. + * + * Returns: + * + *\li Success + *\li Unexpected error + * Getting the time from the system failed. + *\li Out of range + * The time from the system is too large to be represented + * in the current definition of isc_time_t. + */ + +isc_result_t +isc_time_nowplusinterval(isc_time_t *t, const isc_interval_t *i); +/*%< + * Set *t to the current absolute time + i. + * + * Note: + *\li This call is equivalent to: + * + *\code + * isc_time_now(t); + * isc_time_add(t, i, t); + *\endcode + * + * Requires: + * + *\li 't' and 'i' are valid pointers. + * + * Returns: + * + *\li Success + *\li Unexpected error + * Getting the time from the system failed. + *\li Out of range + * The interval added to the time from the system is too large to + * be represented in the current definition of isc_time_t. + */ + +int +isc_time_compare(const isc_time_t *t1, const isc_time_t *t2); +/*%< + * Compare the times referenced by 't1' and 't2' + * + * Requires: + * + *\li 't1' and 't2' are valid pointers. + * + * Returns: + * + *\li -1 t1 < t2 (comparing times, not pointers) + *\li 0 t1 = t2 + *\li 1 t1 > t2 + */ + +isc_result_t +isc_time_add(const isc_time_t *t, const isc_interval_t *i, isc_time_t *result); +/*%< + * Add 'i' to 't', storing the result in 'result'. + * + * Requires: + * + *\li 't', 'i', and 'result' are valid pointers. + * + * Returns: + *\li Success + *\li Out of range + * The interval added to the time is too large to + * be represented in the current definition of isc_time_t. + */ + +isc_result_t +isc_time_subtract(const isc_time_t *t, const isc_interval_t *i, + isc_time_t *result); +/*%< + * Subtract 'i' from 't', storing the result in 'result'. + * + * Requires: + * + *\li 't', 'i', and 'result' are valid pointers. + * + * Returns: + *\li Success + *\li Out of range + * The interval is larger than the time since the epoch. + */ + +uint64_t +isc_time_microdiff(const isc_time_t *t1, const isc_time_t *t2); +/*%< + * Find the difference in microseconds between time t1 and time t2. + * t2 is the subtrahend of t1; ie, difference = t1 - t2. + * + * Requires: + * + *\li 't1' and 't2' are valid pointers. + * + * Returns: + *\li The difference of t1 - t2, or 0 if t1 <= t2. + */ + +uint32_t +isc_time_seconds(const isc_time_t *t); +/*%< + * Return the number of seconds since the epoch stored in a time structure. + * + * Requires: + * + *\li 't' is a valid pointer. + */ + +isc_result_t +isc_time_secondsastimet(const isc_time_t *t, time_t *secondsp); +/*%< + * Ensure the number of seconds in an isc_time_t is representable by a time_t. + * + * Notes: + *\li The number of seconds stored in an isc_time_t might be larger + * than the number of seconds a time_t is able to handle. Since + * time_t is mostly opaque according to the ANSI/ISO standard + * (essentially, all you can be sure of is that it is an arithmetic type, + * not even necessarily integral), it can be tricky to ensure that + * the isc_time_t is in the range a time_t can handle. Use this + * function in place of isc_time_seconds() any time you need to set a + * time_t from an isc_time_t. + * + * Requires: + *\li 't' is a valid pointer. + * + * Returns: + *\li Success + *\li Out of range + */ + +uint32_t +isc_time_nanoseconds(const isc_time_t *t); +/*%< + * Return the number of nanoseconds stored in a time structure. + * + * Notes: + *\li This is the number of nanoseconds in excess of the number + * of seconds since the epoch; it will always be less than one + * full second. + * + * Requires: + *\li 't' is a valid pointer. + * + * Ensures: + *\li The returned value is less than 1*10^9. + */ + +void +isc_time_formattimestamp(const isc_time_t *t, char *buf, unsigned int len); +/*%< + * Format the time 't' into the buffer 'buf' of length 'len', + * using a format like "30-Aug-2000 04:06:47.997" and the local time zone. + * If the text does not fit in the buffer, the result is indeterminate, + * but is always guaranteed to be null terminated. + * + * Requires: + *\li 'len' > 0 + *\li 'buf' points to an array of at least len chars + * + */ + +void +isc_time_formathttptimestamp(const isc_time_t *t, char *buf, unsigned int len); +/*%< + * Format the time 't' into the buffer 'buf' of length 'len', + * using a format like "Mon, 30 Aug 2000 04:06:47 GMT" + * If the text does not fit in the buffer, the result is indeterminate, + * but is always guaranteed to be null terminated. + * + * Requires: + *\li 'len' > 0 + *\li 'buf' points to an array of at least len chars + * + */ + +isc_result_t +isc_time_parsehttptimestamp(char *input, isc_time_t *t); +/*%< + * Parse the time in 'input' into the isc_time_t pointed to by 't', + * expecting a format like "Mon, 30 Aug 2000 04:06:47 GMT" + * + * Requires: + *\li 'buf' and 't' are not NULL. + */ + +void +isc_time_formatISO8601L(const isc_time_t *t, char *buf, unsigned int len); +/*%< + * Format the time 't' into the buffer 'buf' of length 'len', + * using the ISO8601 format: "yyyy-mm-ddThh:mm:ss" + * If the text does not fit in the buffer, the result is indeterminate, + * but is always guaranteed to be null terminated. + * + * Requires: + *\li 'len' > 0 + *\li 'buf' points to an array of at least len chars + * + */ + +void +isc_time_formatISO8601Lms(const isc_time_t *t, char *buf, unsigned int len); +/*%< + * Format the time 't' into the buffer 'buf' of length 'len', + * using the ISO8601 format: "yyyy-mm-ddThh:mm:ss.sss" + * If the text does not fit in the buffer, the result is indeterminate, + * but is always guaranteed to be null terminated. + * + * Requires: + *\li 'len' > 0 + *\li 'buf' points to an array of at least len chars + * + */ + +void +isc_time_formatISO8601Lus(const isc_time_t *t, char *buf, unsigned int len); +/*%< + * Format the time 't' into the buffer 'buf' of length 'len', + * using the ISO8601 format: "yyyy-mm-ddThh:mm:ss.ssssss" + * If the text does not fit in the buffer, the result is indeterminate, + * but is always guaranteed to be null terminated. + * + * Requires: + *\li 'len' > 0 + *\li 'buf' points to an array of at least len chars + * + */ + +void +isc_time_formatISO8601(const isc_time_t *t, char *buf, unsigned int len); +/*%< + * Format the time 't' into the buffer 'buf' of length 'len', + * using the ISO8601 format: "yyyy-mm-ddThh:mm:ssZ" + * If the text does not fit in the buffer, the result is indeterminate, + * but is always guaranteed to be null terminated. + * + * Requires: + *\li 'len' > 0 + *\li 'buf' points to an array of at least len chars + * + */ + +void +isc_time_formatISO8601ms(const isc_time_t *t, char *buf, unsigned int len); +/*%< + * Format the time 't' into the buffer 'buf' of length 'len', + * using the ISO8601 format: "yyyy-mm-ddThh:mm:ss.sssZ" + * If the text does not fit in the buffer, the result is indeterminate, + * but is always guaranteed to be null terminated. + * + * Requires: + *\li 'len' > 0 + *\li 'buf' points to an array of at least len chars + * + */ + +void +isc_time_formatISO8601us(const isc_time_t *t, char *buf, unsigned int len); +/*%< + * Format the time 't' into the buffer 'buf' of length 'len', + * using the ISO8601 format: "yyyy-mm-ddThh:mm:ss.ssssssZ" + * If the text does not fit in the buffer, the result is indeterminate, + * but is always guaranteed to be null terminated. + * + * Requires: + *\li 'len' > 0 + *\li 'buf' points to an array of at least len chars + * + */ + +void +isc_time_formatshorttimestamp(const isc_time_t *t, char *buf, unsigned int len); +/*%< + * Format the time 't' into the buffer 'buf' of length 'len', + * using the format "yyyymmddhhmmsssss" useful for file timestamping. + * If the text does not fit in the buffer, the result is indeterminate, + * but is always guaranteed to be null terminated. + * + * Requires: + *\li 'len' > 0 + *\li 'buf' points to an array of at least len chars + * + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_TIME_H */ diff --git a/lib/isc/unix/interfaceiter.c b/lib/isc/unix/interfaceiter.c new file mode 100644 index 0000000..a1f6c9d --- /dev/null +++ b/lib/isc/unix/interfaceiter.c @@ -0,0 +1,301 @@ +/* + * 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. + */ + +/*! \file */ + +#include <sys/ioctl.h> +#include <sys/types.h> +#ifdef HAVE_SYS_SOCKIO_H +#include <sys/sockio.h> /* Required for ifiter_ioctl.c. */ +#endif /* ifdef HAVE_SYS_SOCKIO_H */ + +#include <errno.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include <isc/interfaceiter.h> +#include <isc/log.h> +#include <isc/magic.h> +#include <isc/mem.h> +#include <isc/net.h> +#include <isc/print.h> +#include <isc/result.h> +#include <isc/strerr.h> +#include <isc/string.h> +#include <isc/types.h> +#include <isc/util.h> + +/* Must follow <isc/net.h>. */ +#ifdef HAVE_NET_IF6_H +#include <net/if6.h> +#endif /* ifdef HAVE_NET_IF6_H */ +#include <net/if.h> + +/* Common utility functions */ + +/*% + * Extract the network address part from a "struct sockaddr". + * \brief + * The address family is given explicitly + * instead of using src->sa_family, because the latter does not work + * for copying a network mask obtained by SIOCGIFNETMASK (it does + * not have a valid address family). + */ + +static void +get_addr(unsigned int family, isc_netaddr_t *dst, struct sockaddr *src, + char *ifname) { + struct sockaddr_in6 *sa6; + +#if !defined(HAVE_IF_NAMETOINDEX) + UNUSED(ifname); +#endif /* if !defined(HAVE_IF_NAMETOINDEX) */ + + /* clear any remaining value for safety */ + memset(dst, 0, sizeof(*dst)); + + dst->family = family; + switch (family) { + case AF_INET: + memmove(&dst->type.in, &((struct sockaddr_in *)src)->sin_addr, + sizeof(struct in_addr)); + break; + case AF_INET6: + sa6 = (struct sockaddr_in6 *)src; + memmove(&dst->type.in6, &sa6->sin6_addr, + sizeof(struct in6_addr)); + if (sa6->sin6_scope_id != 0) { + isc_netaddr_setzone(dst, sa6->sin6_scope_id); + } else { + /* + * BSD variants embed scope zone IDs in the 128bit + * address as a kernel internal form. Unfortunately, + * the embedded IDs are not hidden from applications + * when getting access to them by sysctl or ioctl. + * We convert the internal format to the pure address + * part and the zone ID part. + * Since multicast addresses should not appear here + * and they cannot be distinguished from netmasks, + * we only consider unicast link-local addresses. + */ + if (IN6_IS_ADDR_LINKLOCAL(&sa6->sin6_addr)) { + uint16_t zone16; + + memmove(&zone16, &sa6->sin6_addr.s6_addr[2], + sizeof(zone16)); + zone16 = ntohs(zone16); + if (zone16 != 0) { + /* the zone ID is embedded */ + isc_netaddr_setzone(dst, + (uint32_t)zone16); + dst->type.in6.s6_addr[2] = 0; + dst->type.in6.s6_addr[3] = 0; +#ifdef HAVE_IF_NAMETOINDEX + } else if (ifname != NULL) { + unsigned int zone; + + /* + * sin6_scope_id is still not provided, + * but the corresponding interface name + * is know. Use the interface ID as + * the link ID. + */ + zone = if_nametoindex(ifname); + if (zone != 0) { + isc_netaddr_setzone( + dst, (uint32_t)zone); + } +#endif /* ifdef HAVE_IF_NAMETOINDEX */ + } + } + } + break; + default: + UNREACHABLE(); + } +} + +/* + * Include system-dependent code. + */ + +#ifdef __linux +#define ISC_IF_INET6_SZ \ + sizeof("00000000000000000000000000000001 01 80 10 80 " \ + "XXXXXXloXXXXXXXX\n") +static isc_result_t +linux_if_inet6_next(isc_interfaceiter_t *); +static isc_result_t +linux_if_inet6_current(isc_interfaceiter_t *); +static void +linux_if_inet6_first(isc_interfaceiter_t *iter); +#endif /* ifdef __linux */ + +#include "ifiter_getifaddrs.c" + +#ifdef __linux +static void +linux_if_inet6_first(isc_interfaceiter_t *iter) { + if (iter->proc != NULL) { + rewind(iter->proc); + (void)linux_if_inet6_next(iter); + } else { + iter->valid = ISC_R_NOMORE; + } +} + +static isc_result_t +linux_if_inet6_next(isc_interfaceiter_t *iter) { + if (iter->proc != NULL && + fgets(iter->entry, sizeof(iter->entry), iter->proc) != NULL) + { + iter->valid = ISC_R_SUCCESS; + } else { + iter->valid = ISC_R_NOMORE; + } + return (iter->valid); +} + +static isc_result_t +linux_if_inet6_current(isc_interfaceiter_t *iter) { + char address[33]; + char name[IF_NAMESIZE + 1]; + struct in6_addr addr6; + unsigned int ifindex, prefix, flag3, flag4; + int res; + unsigned int i; + + if (iter->valid != ISC_R_SUCCESS) { + return (iter->valid); + } + if (iter->proc == NULL) { + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_INTERFACE, ISC_LOG_ERROR, + "/proc/net/if_inet6:iter->proc == NULL"); + return (ISC_R_FAILURE); + } + + res = sscanf(iter->entry, "%32[a-f0-9] %x %x %x %x %16s\n", address, + &ifindex, &prefix, &flag3, &flag4, name); + if (res != 6) { + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_INTERFACE, ISC_LOG_ERROR, + "/proc/net/if_inet6:sscanf() -> %d (expected 6)", + res); + return (ISC_R_FAILURE); + } + if (strlen(address) != 32) { + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_INTERFACE, ISC_LOG_ERROR, + "/proc/net/if_inet6:strlen(%s) != 32", address); + return (ISC_R_FAILURE); + } + for (i = 0; i < 16; i++) { + unsigned char byte; + static const char hex[] = "0123456789abcdef"; + byte = ((strchr(hex, address[i * 2]) - hex) << 4) | + (strchr(hex, address[i * 2 + 1]) - hex); + addr6.s6_addr[i] = byte; + } + iter->current.af = AF_INET6; + iter->current.flags = INTERFACE_F_UP; + isc_netaddr_fromin6(&iter->current.address, &addr6); + if (isc_netaddr_islinklocal(&iter->current.address)) { + isc_netaddr_setzone(&iter->current.address, (uint32_t)ifindex); + } + for (i = 0; i < 16; i++) { + if (prefix > 8) { + addr6.s6_addr[i] = 0xff; + prefix -= 8; + } else { + addr6.s6_addr[i] = (0xff << (8 - prefix)) & 0xff; + prefix = 0; + } + } + isc_netaddr_fromin6(&iter->current.netmask, &addr6); + strlcpy(iter->current.name, name, sizeof(iter->current.name)); + return (ISC_R_SUCCESS); +} +#endif /* ifdef __linux */ + +/* + * The remaining code is common to the sysctl and ioctl case. + */ + +isc_result_t +isc_interfaceiter_current(isc_interfaceiter_t *iter, isc_interface_t *ifdata) { + REQUIRE(iter->result == ISC_R_SUCCESS); + memmove(ifdata, &iter->current, sizeof(*ifdata)); + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_interfaceiter_first(isc_interfaceiter_t *iter) { + isc_result_t result; + + REQUIRE(VALID_IFITER(iter)); + + internal_first(iter); + for (;;) { + result = internal_current(iter); + if (result != ISC_R_IGNORE) { + break; + } + result = internal_next(iter); + if (result != ISC_R_SUCCESS) { + break; + } + } + iter->result = result; + return (result); +} + +isc_result_t +isc_interfaceiter_next(isc_interfaceiter_t *iter) { + isc_result_t result; + + REQUIRE(VALID_IFITER(iter)); + REQUIRE(iter->result == ISC_R_SUCCESS); + + for (;;) { + result = internal_next(iter); + if (result != ISC_R_SUCCESS) { + break; + } + result = internal_current(iter); + if (result != ISC_R_IGNORE) { + break; + } + } + iter->result = result; + return (result); +} + +void +isc_interfaceiter_destroy(isc_interfaceiter_t **iterp) { + isc_interfaceiter_t *iter; + REQUIRE(iterp != NULL); + iter = *iterp; + *iterp = NULL; + REQUIRE(VALID_IFITER(iter)); + + internal_destroy(iter); + if (iter->buf != NULL) { + isc_mem_put(iter->mctx, iter->buf, iter->bufsize); + } + + iter->magic = 0; + isc_mem_put(iter->mctx, iter, sizeof(*iter)); +} diff --git a/lib/isc/unix/meminfo.c b/lib/isc/unix/meminfo.c new file mode 100644 index 0000000..612ccea --- /dev/null +++ b/lib/isc/unix/meminfo.c @@ -0,0 +1,50 @@ +/* + * 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. + */ + +#include <inttypes.h> +#include <unistd.h> + +#include <isc/meminfo.h> +#if defined(HAVE_SYS_SYSCTL_H) && !defined(__linux__) +#include <sys/sysctl.h> +#endif /* if defined(HAVE_SYS_SYSCTL_H) && !defined(__linux__) */ + +uint64_t +isc_meminfo_totalphys(void) { +#if defined(CTL_HW) && (defined(HW_PHYSMEM64) || defined(HW_MEMSIZE)) + int mib[2]; + mib[0] = CTL_HW; +#if defined(HW_MEMSIZE) + mib[1] = HW_MEMSIZE; +#elif defined(HW_PHYSMEM64) + mib[1] = HW_PHYSMEM64; +#endif /* if defined(HW_MEMSIZE) */ + uint64_t size = 0; + size_t len = sizeof(size); + if (sysctl(mib, 2, &size, &len, NULL, 0) == 0) { + return (size); + } +#endif /* if defined(CTL_HW) && (defined(HW_PHYSMEM64) || defined(HW_MEMSIZE)) \ + * */ +#if defined(_SC_PHYS_PAGES) && defined(_SC_PAGESIZE) + long pages = sysconf(_SC_PHYS_PAGES); + long pagesize = sysconf(_SC_PAGESIZE); + + if (pages == -1 || pagesize == -1) { + return (0); + } + + return ((size_t)pages * pagesize); +#endif /* if defined(_SC_PHYS_PAGES) && defined(_SC_PAGESIZE) */ + return (0); +} diff --git a/lib/isc/unix/net.c b/lib/isc/unix/net.c new file mode 100644 index 0000000..1e43b4b --- /dev/null +++ b/lib/isc/unix/net.c @@ -0,0 +1,860 @@ +/* + * 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. + */ + +#include <stdbool.h> +#include <sys/types.h> + +#if defined(HAVE_SYS_SYSCTL_H) && !defined(__linux__) +#if defined(HAVE_SYS_PARAM_H) +#include <sys/param.h> +#endif /* if defined(HAVE_SYS_PARAM_H) */ +#include <sys/sysctl.h> +#endif /* if defined(HAVE_SYS_SYSCTL_H) && !defined(__linux__) */ +#include <errno.h> +#include <fcntl.h> +#include <sys/uio.h> +#include <unistd.h> + +#include <isc/log.h> +#include <isc/net.h> +#include <isc/netdb.h> +#include <isc/once.h> +#include <isc/strerr.h> +#include <isc/string.h> +#include <isc/util.h> + +#ifndef socklen_t +#define socklen_t unsigned int +#endif /* ifndef socklen_t */ + +/*% + * Definitions about UDP port range specification. This is a total mess of + * portability variants: some use sysctl (but the sysctl names vary), some use + * system-specific interfaces, some have the same interface for IPv4 and IPv6, + * some separate them, etc... + */ + +/*% + * The last resort defaults: use all non well known port space + */ +#ifndef ISC_NET_PORTRANGELOW +#define ISC_NET_PORTRANGELOW 1024 +#endif /* ISC_NET_PORTRANGELOW */ +#ifndef ISC_NET_PORTRANGEHIGH +#define ISC_NET_PORTRANGEHIGH 65535 +#endif /* ISC_NET_PORTRANGEHIGH */ + +#ifdef HAVE_SYSCTLBYNAME + +/*% + * sysctl variants + */ +#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__DragonFly__) +#define USE_SYSCTL_PORTRANGE +#define SYSCTL_V4PORTRANGE_LOW "net.inet.ip.portrange.hifirst" +#define SYSCTL_V4PORTRANGE_HIGH "net.inet.ip.portrange.hilast" +#define SYSCTL_V6PORTRANGE_LOW "net.inet.ip.portrange.hifirst" +#define SYSCTL_V6PORTRANGE_HIGH "net.inet.ip.portrange.hilast" +#endif /* if defined(__FreeBSD__) || defined(__APPLE__) || \ + * defined(__DragonFly__) */ + +#ifdef __NetBSD__ +#define USE_SYSCTL_PORTRANGE +#define SYSCTL_V4PORTRANGE_LOW "net.inet.ip.anonportmin" +#define SYSCTL_V4PORTRANGE_HIGH "net.inet.ip.anonportmax" +#define SYSCTL_V6PORTRANGE_LOW "net.inet6.ip6.anonportmin" +#define SYSCTL_V6PORTRANGE_HIGH "net.inet6.ip6.anonportmax" +#endif /* ifdef __NetBSD__ */ + +#else /* !HAVE_SYSCTLBYNAME */ + +#ifdef __OpenBSD__ +#define USE_SYSCTL_PORTRANGE +#define SYSCTL_V4PORTRANGE_LOW \ + { \ + CTL_NET, PF_INET, IPPROTO_IP, IPCTL_IPPORT_HIFIRSTAUTO \ + } +#define SYSCTL_V4PORTRANGE_HIGH \ + { \ + CTL_NET, PF_INET, IPPROTO_IP, IPCTL_IPPORT_HILASTAUTO \ + } +/* Same for IPv6 */ +#define SYSCTL_V6PORTRANGE_LOW SYSCTL_V4PORTRANGE_LOW +#define SYSCTL_V6PORTRANGE_HIGH SYSCTL_V4PORTRANGE_HIGH +#endif /* ifdef __OpenBSD__ */ + +#endif /* HAVE_SYSCTLBYNAME */ + +static isc_once_t once_ipv6only = ISC_ONCE_INIT; +#ifdef __notyet__ +static isc_once_t once_ipv6pktinfo = ISC_ONCE_INIT; +#endif /* ifdef __notyet__ */ + +#ifndef ISC_CMSG_IP_TOS +#ifdef __APPLE__ +#define ISC_CMSG_IP_TOS 0 /* As of 10.8.2. */ +#else /* ! __APPLE__ */ +#define ISC_CMSG_IP_TOS 1 +#endif /* ! __APPLE__ */ +#endif /* ! ISC_CMSG_IP_TOS */ + +static isc_once_t once = ISC_ONCE_INIT; +static isc_once_t once_dscp = ISC_ONCE_INIT; + +static isc_result_t ipv4_result = ISC_R_NOTFOUND; +static isc_result_t ipv6_result = ISC_R_NOTFOUND; +static isc_result_t unix_result = ISC_R_NOTFOUND; +static isc_result_t ipv6only_result = ISC_R_NOTFOUND; +static isc_result_t ipv6pktinfo_result = ISC_R_NOTFOUND; +static unsigned int dscp_result = 0; + +static isc_result_t +try_proto(int domain) { + int s; + isc_result_t result = ISC_R_SUCCESS; + char strbuf[ISC_STRERRORSIZE]; + + s = socket(domain, SOCK_STREAM, 0); + if (s == -1) { + switch (errno) { +#ifdef EAFNOSUPPORT + case EAFNOSUPPORT: +#endif /* ifdef EAFNOSUPPORT */ +#ifdef EPFNOSUPPORT + case EPFNOSUPPORT: +#endif /* ifdef EPFNOSUPPORT */ +#ifdef EPROTONOSUPPORT + case EPROTONOSUPPORT: +#endif /* ifdef EPROTONOSUPPORT */ +#ifdef EINVAL + case EINVAL: +#endif /* ifdef EINVAL */ + return (ISC_R_NOTFOUND); + default: + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "socket() failed: %s", strbuf); + return (ISC_R_UNEXPECTED); + } + } + + if (domain == PF_INET6) { + struct sockaddr_in6 sin6; + unsigned int len; + + /* + * Check to see if IPv6 is broken, as is common on Linux. + */ + len = sizeof(sin6); + if (getsockname(s, (struct sockaddr *)&sin6, (void *)&len) < 0) + { + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_SOCKET, ISC_LOG_ERROR, + "retrieving the address of an IPv6 " + "socket from the kernel failed."); + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_SOCKET, ISC_LOG_ERROR, + "IPv6 is not supported."); + result = ISC_R_NOTFOUND; + } else { + if (len == sizeof(struct sockaddr_in6)) { + result = ISC_R_SUCCESS; + } else { + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_SOCKET, + ISC_LOG_ERROR, + "IPv6 structures in kernel and " + "user space do not match."); + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_SOCKET, + ISC_LOG_ERROR, + "IPv6 is not supported."); + result = ISC_R_NOTFOUND; + } + } + } + + (void)close(s); + + return (result); +} + +static void +initialize_action(void) { + ipv4_result = try_proto(PF_INET); + ipv6_result = try_proto(PF_INET6); +#ifdef ISC_PLATFORM_HAVESYSUNH + unix_result = try_proto(PF_UNIX); +#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */ +} + +static void +initialize(void) { + RUNTIME_CHECK(isc_once_do(&once, initialize_action) == ISC_R_SUCCESS); +} + +isc_result_t +isc_net_probeipv4(void) { + initialize(); + return (ipv4_result); +} + +isc_result_t +isc_net_probeipv6(void) { + initialize(); + return (ipv6_result); +} + +isc_result_t +isc_net_probeunix(void) { + initialize(); + return (unix_result); +} + +static void +try_ipv6only(void) { +#ifdef IPV6_V6ONLY + int s, on; + char strbuf[ISC_STRERRORSIZE]; +#endif /* ifdef IPV6_V6ONLY */ + isc_result_t result; + + result = isc_net_probeipv6(); + if (result != ISC_R_SUCCESS) { + ipv6only_result = result; + return; + } + +#ifndef IPV6_V6ONLY + ipv6only_result = ISC_R_NOTFOUND; + return; +#else /* ifndef IPV6_V6ONLY */ + /* check for TCP sockets */ + s = socket(PF_INET6, SOCK_STREAM, 0); + if (s == -1) { + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, "socket() failed: %s", + strbuf); + ipv6only_result = ISC_R_UNEXPECTED; + return; + } + + on = 1; + if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) < 0) { + ipv6only_result = ISC_R_NOTFOUND; + goto close; + } + + close(s); + + /* check for UDP sockets */ + s = socket(PF_INET6, SOCK_DGRAM, 0); + if (s == -1) { + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, "socket() failed: %s", + strbuf); + ipv6only_result = ISC_R_UNEXPECTED; + return; + } + + on = 1; + if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) < 0) { + ipv6only_result = ISC_R_NOTFOUND; + goto close; + } + + ipv6only_result = ISC_R_SUCCESS; + +close: + close(s); + return; +#endif /* IPV6_V6ONLY */ +} + +static void +initialize_ipv6only(void) { + RUNTIME_CHECK(isc_once_do(&once_ipv6only, try_ipv6only) == + ISC_R_SUCCESS); +} + +#ifdef __notyet__ +static void +try_ipv6pktinfo(void) { + int s, on; + char strbuf[ISC_STRERRORSIZE]; + isc_result_t result; + int optname; + + result = isc_net_probeipv6(); + if (result != ISC_R_SUCCESS) { + ipv6pktinfo_result = result; + return; + } + + /* we only use this for UDP sockets */ + s = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP); + if (s == -1) { + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, "socket() failed: %s", + strbuf); + ipv6pktinfo_result = ISC_R_UNEXPECTED; + return; + } + +#ifdef IPV6_RECVPKTINFO + optname = IPV6_RECVPKTINFO; +#else /* ifdef IPV6_RECVPKTINFO */ + optname = IPV6_PKTINFO; +#endif /* ifdef IPV6_RECVPKTINFO */ + on = 1; + if (setsockopt(s, IPPROTO_IPV6, optname, &on, sizeof(on)) < 0) { + ipv6pktinfo_result = ISC_R_NOTFOUND; + goto close; + } + + ipv6pktinfo_result = ISC_R_SUCCESS; + +close: + close(s); + return; +} + +static void +initialize_ipv6pktinfo(void) { + RUNTIME_CHECK(isc_once_do(&once_ipv6pktinfo, try_ipv6pktinfo) == + ISC_R_SUCCESS); +} +#endif /* ifdef __notyet__ */ + +isc_result_t +isc_net_probe_ipv6only(void) { + initialize_ipv6only(); + return (ipv6only_result); +} + +isc_result_t +isc_net_probe_ipv6pktinfo(void) { +/* + * XXXWPK if pktinfo were supported then we could listen on :: for ipv6 and get + * the information about the destination address from pktinfo structure passed + * in recvmsg but this method is not portable and libuv doesn't support it - so + * we need to listen on all interfaces. + * We should verify that this doesn't impact performance (we already do it for + * ipv4) and either remove all the ipv6pktinfo detection code from above + * or think of fixing libuv. + */ +#ifdef __notyet__ + initialize_ipv6pktinfo(); +#endif /* ifdef __notyet__ */ + return (ipv6pktinfo_result); +} + +#if ISC_CMSG_IP_TOS || defined(IPV6_TCLASS) + +static socklen_t +cmsg_len(socklen_t len) { +#ifdef CMSG_LEN + return (CMSG_LEN(len)); +#else /* ifdef CMSG_LEN */ + socklen_t hdrlen; + + /* + * Cast NULL so that any pointer arithmetic performed by CMSG_DATA + * is correct. + */ + hdrlen = (socklen_t)CMSG_DATA(((struct cmsghdr *)NULL)); + return (hdrlen + len); +#endif /* ifdef CMSG_LEN */ +} + +static socklen_t +cmsg_space(socklen_t len) { +#ifdef CMSG_SPACE + return (CMSG_SPACE(len)); +#else /* ifdef CMSG_SPACE */ + struct msghdr msg; + struct cmsghdr *cmsgp; + /* + * XXX: The buffer length is an ad-hoc value, but should be enough + * in a practical sense. + */ + char dummybuf[sizeof(struct cmsghdr) + 1024]; + + memset(&msg, 0, sizeof(msg)); + msg.msg_control = dummybuf; + msg.msg_controllen = sizeof(dummybuf); + + cmsgp = (struct cmsghdr *)dummybuf; + cmsgp->cmsg_len = cmsg_len(len); + + cmsgp = CMSG_NXTHDR(&msg, cmsgp); + if (cmsgp != NULL) { + return ((char *)cmsgp - (char *)msg.msg_control); + } else { + return (0); + } +#endif /* ifdef CMSG_SPACE */ +} + +/* + * Make a fd non-blocking. + */ +static isc_result_t +make_nonblock(int fd) { + int ret; + int flags; + char strbuf[ISC_STRERRORSIZE]; +#ifdef USE_FIONBIO_IOCTL + int on = 1; + + ret = ioctl(fd, FIONBIO, (char *)&on); +#else /* ifdef USE_FIONBIO_IOCTL */ + flags = fcntl(fd, F_GETFL, 0); + flags |= PORT_NONBLOCK; + ret = fcntl(fd, F_SETFL, flags); +#endif /* ifdef USE_FIONBIO_IOCTL */ + + if (ret == -1) { + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, +#ifdef USE_FIONBIO_IOCTL + "ioctl(%d, FIONBIO, &on): %s", fd, +#else /* ifdef USE_FIONBIO_IOCTL */ + "fcntl(%d, F_SETFL, %d): %s", fd, flags, +#endif /* ifdef USE_FIONBIO_IOCTL */ + strbuf); + + return (ISC_R_UNEXPECTED); + } + + return (ISC_R_SUCCESS); +} + +static bool +cmsgsend(int s, int level, int type, struct addrinfo *res) { + char strbuf[ISC_STRERRORSIZE]; + struct sockaddr_storage ss; + socklen_t len = sizeof(ss); + struct msghdr msg; + union { + struct cmsghdr h; + unsigned char b[256]; + } control; + struct cmsghdr *cmsgp; + int dscp = (46 << 2); /* Expedited forwarding. */ + struct iovec iovec; + char buf[1] = { 0 }; + isc_result_t result; + + if (bind(s, res->ai_addr, res->ai_addrlen) < 0) { + strerror_r(errno, strbuf, sizeof(strbuf)); + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_SOCKET, ISC_LOG_DEBUG(10), + "bind: %s", strbuf); + return (false); + } + + if (getsockname(s, (struct sockaddr *)&ss, &len) < 0) { + strerror_r(errno, strbuf, sizeof(strbuf)); + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_SOCKET, ISC_LOG_DEBUG(10), + "getsockname: %s", strbuf); + return (false); + } + + iovec.iov_base = buf; + iovec.iov_len = sizeof(buf); + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = (struct sockaddr *)&ss; + msg.msg_namelen = len; + msg.msg_iov = &iovec; + msg.msg_iovlen = 1; + msg.msg_control = (void *)&control; + msg.msg_controllen = 0; + msg.msg_flags = 0; + + cmsgp = msg.msg_control; + + switch (type) { +#ifdef IP_TOS + case IP_TOS: + memset(cmsgp, 0, cmsg_space(sizeof(char))); + cmsgp->cmsg_level = level; + cmsgp->cmsg_type = type; + cmsgp->cmsg_len = cmsg_len(sizeof(char)); + *(unsigned char *)CMSG_DATA(cmsgp) = dscp; + msg.msg_controllen += cmsg_space(sizeof(char)); + break; +#endif /* ifdef IP_TOS */ +#ifdef IPV6_TCLASS + case IPV6_TCLASS: + memset(cmsgp, 0, cmsg_space(sizeof(dscp))); + cmsgp->cmsg_level = level; + cmsgp->cmsg_type = type; + cmsgp->cmsg_len = cmsg_len(sizeof(dscp)); + memmove(CMSG_DATA(cmsgp), &dscp, sizeof(dscp)); + msg.msg_controllen += cmsg_space(sizeof(dscp)); + break; +#endif /* ifdef IPV6_TCLASS */ + default: + UNREACHABLE(); + } + + if (sendmsg(s, &msg, 0) < 0) { + int debug = ISC_LOG_DEBUG(10); + const char *typestr; + switch (errno) { +#ifdef ENOPROTOOPT + case ENOPROTOOPT: +#endif /* ifdef ENOPROTOOPT */ +#ifdef EOPNOTSUPP + case EOPNOTSUPP: +#endif /* ifdef EOPNOTSUPP */ + case EINVAL: + case EPERM: + break; + default: + debug = ISC_LOG_NOTICE; + } + strerror_r(errno, strbuf, sizeof(strbuf)); + if (debug != ISC_LOG_NOTICE) { + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_SOCKET, ISC_LOG_DEBUG(10), + "sendmsg: %s", strbuf); + } else { + typestr = (type == IP_TOS) ? "IP_TOS" : "IPV6_TCLASS"; + UNEXPECTED_ERROR(__FILE__, __LINE__, + "probing " + "sendmsg() with %s=%02x failed: %s", + typestr, dscp, strbuf); + } + return (false); + } + + /* + * Make sure the message actually got sent. + */ + result = make_nonblock(s); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + iovec.iov_base = buf; + iovec.iov_len = sizeof(buf); + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = (struct sockaddr *)&ss; + msg.msg_namelen = sizeof(ss); + msg.msg_iov = &iovec; + msg.msg_iovlen = 1; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; + + if (recvmsg(s, &msg, 0) < 0) { + return (false); + } + + return (true); +} +#endif /* if ISC_CMSG_IP_TOS || defined(IPV6_TCLASS) */ + +static void +try_dscp_v4(void) { +#ifdef IP_TOS + char strbuf[ISC_STRERRORSIZE]; + struct addrinfo hints, *res0; + int s, dscp = 0, n; +#ifdef IP_RECVTOS + int on = 1; +#endif /* IP_RECVTOS */ + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = IPPROTO_UDP; +#ifdef AI_NUMERICHOST + hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST; +#else /* ifdef AI_NUMERICHOST */ + hints.ai_flags = AI_PASSIVE; +#endif /* ifdef AI_NUMERICHOST */ + + n = getaddrinfo("127.0.0.1", NULL, &hints, &res0); + if (n != 0 || res0 == NULL) { + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_SOCKET, ISC_LOG_DEBUG(10), + "getaddrinfo(127.0.0.1): %s", gai_strerror(n)); + return; + } + + s = socket(res0->ai_family, res0->ai_socktype, res0->ai_protocol); + + if (s == -1) { + strerror_r(errno, strbuf, sizeof(strbuf)); + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_SOCKET, ISC_LOG_DEBUG(10), + "socket: %s", strbuf); + freeaddrinfo(res0); + return; + } + + if (setsockopt(s, IPPROTO_IP, IP_TOS, &dscp, sizeof(dscp)) == 0) { + dscp_result |= ISC_NET_DSCPSETV4; + } + +#ifdef IP_RECVTOS + on = 1; + if (setsockopt(s, IPPROTO_IP, IP_RECVTOS, &on, sizeof(on)) == 0) { + dscp_result |= ISC_NET_DSCPRECVV4; + } +#endif /* IP_RECVTOS */ + +#if ISC_CMSG_IP_TOS + if (cmsgsend(s, IPPROTO_IP, IP_TOS, res0)) { + dscp_result |= ISC_NET_DSCPPKTV4; + } +#endif /* ISC_CMSG_IP_TOS */ + + freeaddrinfo(res0); + close(s); + +#endif /* IP_TOS */ +} + +static void +try_dscp_v6(void) { +#ifdef IPV6_TCLASS + char strbuf[ISC_STRERRORSIZE]; + struct addrinfo hints, *res0; + int s, dscp = 0, n; +#if defined(IPV6_RECVTCLASS) + int on = 1; +#endif /* IPV6_RECVTCLASS */ + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = IPPROTO_UDP; +#ifdef AI_NUMERICHOST + hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST; +#else /* ifdef AI_NUMERICHOST */ + hints.ai_flags = AI_PASSIVE; +#endif /* ifdef AI_NUMERICHOST */ + + n = getaddrinfo("::1", NULL, &hints, &res0); + if (n != 0 || res0 == NULL) { + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_SOCKET, ISC_LOG_DEBUG(10), + "getaddrinfo(::1): %s", gai_strerror(n)); + return; + } + + s = socket(res0->ai_family, res0->ai_socktype, res0->ai_protocol); + if (s == -1) { + strerror_r(errno, strbuf, sizeof(strbuf)); + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_SOCKET, ISC_LOG_DEBUG(10), + "socket: %s", strbuf); + freeaddrinfo(res0); + return; + } + if (setsockopt(s, IPPROTO_IPV6, IPV6_TCLASS, &dscp, sizeof(dscp)) == 0) + { + dscp_result |= ISC_NET_DSCPSETV6; + } + +#ifdef IPV6_RECVTCLASS + on = 1; + if (setsockopt(s, IPPROTO_IPV6, IPV6_RECVTCLASS, &on, sizeof(on)) == 0) + { + dscp_result |= ISC_NET_DSCPRECVV6; + } +#endif /* IPV6_RECVTCLASS */ + + if (cmsgsend(s, IPPROTO_IPV6, IPV6_TCLASS, res0)) { + dscp_result |= ISC_NET_DSCPPKTV6; + } + + freeaddrinfo(res0); + close(s); + +#endif /* IPV6_TCLASS */ +} + +static void +try_dscp(void) { + try_dscp_v4(); + try_dscp_v6(); +} + +static void +initialize_dscp(void) { + RUNTIME_CHECK(isc_once_do(&once_dscp, try_dscp) == ISC_R_SUCCESS); +} + +unsigned int +isc_net_probedscp(void) { + initialize_dscp(); + return (dscp_result); +} + +#if defined(USE_SYSCTL_PORTRANGE) +#if defined(HAVE_SYSCTLBYNAME) +static isc_result_t +getudpportrange_sysctl(int af, in_port_t *low, in_port_t *high) { + int port_low, port_high; + size_t portlen; + const char *sysctlname_lowport, *sysctlname_hiport; + + if (af == AF_INET) { + sysctlname_lowport = SYSCTL_V4PORTRANGE_LOW; + sysctlname_hiport = SYSCTL_V4PORTRANGE_HIGH; + } else { + sysctlname_lowport = SYSCTL_V6PORTRANGE_LOW; + sysctlname_hiport = SYSCTL_V6PORTRANGE_HIGH; + } + portlen = sizeof(port_low); + if (sysctlbyname(sysctlname_lowport, &port_low, &portlen, NULL, 0) < 0) + { + return (ISC_R_FAILURE); + } + portlen = sizeof(port_high); + if (sysctlbyname(sysctlname_hiport, &port_high, &portlen, NULL, 0) < 0) + { + return (ISC_R_FAILURE); + } + if ((port_low & ~0xffff) != 0 || (port_high & ~0xffff) != 0) { + return (ISC_R_RANGE); + } + + *low = (in_port_t)port_low; + *high = (in_port_t)port_high; + + return (ISC_R_SUCCESS); +} +#else /* !HAVE_SYSCTLBYNAME */ +static isc_result_t +getudpportrange_sysctl(int af, in_port_t *low, in_port_t *high) { + int mib_lo4[4] = SYSCTL_V4PORTRANGE_LOW; + int mib_hi4[4] = SYSCTL_V4PORTRANGE_HIGH; + int mib_lo6[4] = SYSCTL_V6PORTRANGE_LOW; + int mib_hi6[4] = SYSCTL_V6PORTRANGE_HIGH; + int *mib_lo, *mib_hi, miblen; + int port_low, port_high; + size_t portlen; + + if (af == AF_INET) { + mib_lo = mib_lo4; + mib_hi = mib_hi4; + miblen = sizeof(mib_lo4) / sizeof(mib_lo4[0]); + } else { + mib_lo = mib_lo6; + mib_hi = mib_hi6; + miblen = sizeof(mib_lo6) / sizeof(mib_lo6[0]); + } + + portlen = sizeof(port_low); + if (sysctl(mib_lo, miblen, &port_low, &portlen, NULL, 0) < 0) { + return (ISC_R_FAILURE); + } + + portlen = sizeof(port_high); + if (sysctl(mib_hi, miblen, &port_high, &portlen, NULL, 0) < 0) { + return (ISC_R_FAILURE); + } + + if ((port_low & ~0xffff) != 0 || (port_high & ~0xffff) != 0) { + return (ISC_R_RANGE); + } + + *low = (in_port_t)port_low; + *high = (in_port_t)port_high; + + return (ISC_R_SUCCESS); +} +#endif /* HAVE_SYSCTLBYNAME */ +#endif /* USE_SYSCTL_PORTRANGE */ + +isc_result_t +isc_net_getudpportrange(int af, in_port_t *low, in_port_t *high) { + int result = ISC_R_FAILURE; +#if !defined(USE_SYSCTL_PORTRANGE) && defined(__linux) + FILE *fp; +#endif /* if !defined(USE_SYSCTL_PORTRANGE) && defined(__linux) */ + + REQUIRE(low != NULL && high != NULL); + +#if defined(USE_SYSCTL_PORTRANGE) + result = getudpportrange_sysctl(af, low, high); +#elif defined(__linux) + + UNUSED(af); + + /* + * Linux local ports are address family agnostic. + */ + fp = fopen("/proc/sys/net/ipv4/ip_local_port_range", "r"); + if (fp != NULL) { + int n; + unsigned int l, h; + + n = fscanf(fp, "%u %u", &l, &h); + if (n == 2 && (l & ~0xffff) == 0 && (h & ~0xffff) == 0) { + *low = l; + *high = h; + result = ISC_R_SUCCESS; + } + fclose(fp); + } +#else /* if defined(USE_SYSCTL_PORTRANGE) */ + UNUSED(af); +#endif /* if defined(USE_SYSCTL_PORTRANGE) */ + + if (result != ISC_R_SUCCESS) { + *low = ISC_NET_PORTRANGELOW; + *high = ISC_NET_PORTRANGEHIGH; + } + + return (ISC_R_SUCCESS); /* we currently never fail in this function */ +} + +void +isc_net_disableipv4(void) { + initialize(); + if (ipv4_result == ISC_R_SUCCESS) { + ipv4_result = ISC_R_DISABLED; + } +} + +void +isc_net_disableipv6(void) { + initialize(); + if (ipv6_result == ISC_R_SUCCESS) { + ipv6_result = ISC_R_DISABLED; + } +} + +void +isc_net_enableipv4(void) { + initialize(); + if (ipv4_result == ISC_R_DISABLED) { + ipv4_result = ISC_R_SUCCESS; + } +} + +void +isc_net_enableipv6(void) { + initialize(); + if (ipv6_result == ISC_R_DISABLED) { + ipv6_result = ISC_R_SUCCESS; + } +} diff --git a/lib/isc/unix/os.c b/lib/isc/unix/os.c new file mode 100644 index 0000000..5577587 --- /dev/null +++ b/lib/isc/unix/os.c @@ -0,0 +1,68 @@ +/* + * 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. + */ + +#include <isc/os.h> + +#ifdef HAVE_SYSCONF + +#include <unistd.h> + +static long +sysconf_ncpus(void) { +#if defined(_SC_NPROCESSORS_ONLN) + return (sysconf((_SC_NPROCESSORS_ONLN))); +#elif defined(_SC_NPROC_ONLN) + return (sysconf((_SC_NPROC_ONLN))); +#else /* if defined(_SC_NPROCESSORS_ONLN) */ + return (0); +#endif /* if defined(_SC_NPROCESSORS_ONLN) */ +} +#endif /* HAVE_SYSCONF */ + +#if defined(HAVE_SYS_SYSCTL_H) && defined(HAVE_SYSCTLBYNAME) +#include <sys/param.h> /* for NetBSD */ +#include <sys/sysctl.h> +#include <sys/types.h> /* for FreeBSD */ + +static int +sysctl_ncpus(void) { + int ncpu, result; + size_t len; + + len = sizeof(ncpu); + result = sysctlbyname("hw.ncpu", &ncpu, &len, 0, 0); + if (result != -1) { + return (ncpu); + } + return (0); +} +#endif /* if defined(HAVE_SYS_SYSCTL_H) && defined(HAVE_SYSCTLBYNAME) */ + +unsigned int +isc_os_ncpus(void) { + long ncpus = 0; + +#if defined(HAVE_SYSCONF) + ncpus = sysconf_ncpus(); +#endif /* if defined(HAVE_SYSCONF) */ +#if defined(HAVE_SYS_SYSCTL_H) && defined(HAVE_SYSCTLBYNAME) + if (ncpus <= 0) { + ncpus = sysctl_ncpus(); + } +#endif /* if defined(HAVE_SYS_SYSCTL_H) && defined(HAVE_SYSCTLBYNAME) */ + if (ncpus <= 0) { + ncpus = 1; + } + + return ((unsigned int)ncpus); +} diff --git a/lib/isc/unix/pk11_api.c b/lib/isc/unix/pk11_api.c new file mode 100644 index 0000000..babd1a9 --- /dev/null +++ b/lib/isc/unix/pk11_api.c @@ -0,0 +1,708 @@ +/* + * 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. + */ + +/*! \file */ + +#include <dlfcn.h> +#include <string.h> + +#include <isc/log.h> +#include <isc/mem.h> +#include <isc/once.h> +#include <isc/print.h> +#include <isc/stdio.h> +#include <isc/thread.h> +#include <isc/util.h> + +#include <pkcs11/pkcs11.h> + +#define KEEP_PKCS11_NAMES +#include <pk11/internal.h> +#include <pk11/pk11.h> + +static void *hPK11 = NULL; +static char loaderrmsg[1024]; + +CK_RV +pkcs_C_Initialize(CK_VOID_PTR pReserved) { + CK_C_Initialize sym; + + if (hPK11 != NULL) { + return (CKR_CRYPTOKI_ALREADY_INITIALIZED); + } + + hPK11 = dlopen(pk11_get_lib_name(), RTLD_NOW); + + if (hPK11 == NULL) { + snprintf(loaderrmsg, sizeof(loaderrmsg), + "dlopen(\"%s\") failed: %s\n", pk11_get_lib_name(), + dlerror()); + return (CKR_LIBRARY_LOAD_FAILED); + } + sym = (CK_C_Initialize)dlsym(hPK11, "C_Initialize"); + if (sym == NULL) { + return (CKR_FUNCTION_NOT_SUPPORTED); + } + return ((*sym)(pReserved)); +} + +char * +pk11_get_load_error_message(void) { + return (loaderrmsg); +} + +CK_RV +pkcs_C_Finalize(CK_VOID_PTR pReserved) { + CK_C_Finalize sym; + CK_RV rv; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_LOAD_FAILED); + } + sym = (CK_C_Finalize)dlsym(hPK11, "C_Finalize"); + if (sym == NULL) { + return (CKR_FUNCTION_NOT_SUPPORTED); + } + rv = (*sym)(pReserved); + if ((rv == CKR_OK) && (dlclose(hPK11) != 0)) { + return (CKR_LIBRARY_LOAD_FAILED); + } + hPK11 = NULL; + return (rv); +} + +CK_RV +pkcs_C_GetSlotList(CK_BBOOL tokenPresent, CK_SLOT_ID_PTR pSlotList, + CK_ULONG_PTR pulCount) { + static CK_C_GetSlotList sym = NULL; + static void *pPK11 = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_LOAD_FAILED); + } + if ((sym == NULL) || (hPK11 != pPK11)) { + pPK11 = hPK11; + sym = (CK_C_GetSlotList)dlsym(hPK11, "C_GetSlotList"); + } + if (sym == NULL) { + return (CKR_FUNCTION_NOT_SUPPORTED); + } + return ((*sym)(tokenPresent, pSlotList, pulCount)); +} + +CK_RV +pkcs_C_GetTokenInfo(CK_SLOT_ID slotID, CK_TOKEN_INFO_PTR pInfo) { + static CK_C_GetTokenInfo sym = NULL; + static void *pPK11 = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_LOAD_FAILED); + } + if ((sym == NULL) || (hPK11 != pPK11)) { + pPK11 = hPK11; + sym = (CK_C_GetTokenInfo)dlsym(hPK11, "C_GetTokenInfo"); + } + if (sym == NULL) { + return (CKR_FUNCTION_NOT_SUPPORTED); + } + return ((*sym)(slotID, pInfo)); +} + +CK_RV +pkcs_C_GetMechanismInfo(CK_SLOT_ID slotID, CK_MECHANISM_TYPE type, + CK_MECHANISM_INFO_PTR pInfo) { + static CK_C_GetMechanismInfo sym = NULL; + static void *pPK11 = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_LOAD_FAILED); + } + if ((sym == NULL) || (hPK11 != pPK11)) { + pPK11 = hPK11; + sym = (CK_C_GetMechanismInfo)dlsym(hPK11, "C_GetMechanismInfo"); + } + if (sym == NULL) { + return (CKR_FUNCTION_NOT_SUPPORTED); + } + return ((*sym)(slotID, type, pInfo)); +} + +CK_RV +pkcs_C_OpenSession(CK_SLOT_ID slotID, CK_FLAGS flags, CK_VOID_PTR pApplication, + CK_RV (*Notify)(CK_SESSION_HANDLE hSession, + CK_NOTIFICATION event, + CK_VOID_PTR pApplication), + CK_SESSION_HANDLE_PTR phSession) { + static CK_C_OpenSession sym = NULL; + static void *pPK11 = NULL; + + if (hPK11 == NULL) { + hPK11 = dlopen(pk11_get_lib_name(), RTLD_NOW); + } + if (hPK11 == NULL) { + snprintf(loaderrmsg, sizeof(loaderrmsg), + "dlopen(\"%s\") failed: %s\n", pk11_get_lib_name(), + dlerror()); + return (CKR_LIBRARY_LOAD_FAILED); + } + if ((sym == NULL) || (hPK11 != pPK11)) { + pPK11 = hPK11; + sym = (CK_C_OpenSession)dlsym(hPK11, "C_OpenSession"); + } + if (sym == NULL) { + return (CKR_FUNCTION_NOT_SUPPORTED); + } + return ((*sym)(slotID, flags, pApplication, Notify, phSession)); +} + +CK_RV +pkcs_C_CloseSession(CK_SESSION_HANDLE hSession) { + static CK_C_CloseSession sym = NULL; + static void *pPK11 = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_LOAD_FAILED); + } + if ((sym == NULL) || (hPK11 != pPK11)) { + pPK11 = hPK11; + sym = (CK_C_CloseSession)dlsym(hPK11, "C_CloseSession"); + } + if (sym == NULL) { + return (CKR_FUNCTION_NOT_SUPPORTED); + } + return ((*sym)(hSession)); +} + +CK_RV +pkcs_C_Login(CK_SESSION_HANDLE hSession, CK_USER_TYPE userType, + CK_CHAR_PTR pPin, CK_ULONG usPinLen) { + static CK_C_Login sym = NULL; + static void *pPK11 = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_LOAD_FAILED); + } + if ((sym == NULL) || (hPK11 != pPK11)) { + pPK11 = hPK11; + sym = (CK_C_Login)dlsym(hPK11, "C_Login"); + } + if (sym == NULL) { + return (CKR_FUNCTION_NOT_SUPPORTED); + } + return ((*sym)(hSession, userType, pPin, usPinLen)); +} + +CK_RV +pkcs_C_Logout(CK_SESSION_HANDLE hSession) { + static CK_C_Logout sym = NULL; + static void *pPK11 = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_LOAD_FAILED); + } + if ((sym == NULL) || (hPK11 != pPK11)) { + pPK11 = hPK11; + sym = (CK_C_Logout)dlsym(hPK11, "C_Logout"); + } + if (sym == NULL) { + return (CKR_FUNCTION_NOT_SUPPORTED); + } + return ((*sym)(hSession)); +} + +CK_RV +pkcs_C_CreateObject(CK_SESSION_HANDLE hSession, CK_ATTRIBUTE_PTR pTemplate, + CK_ULONG usCount, CK_OBJECT_HANDLE_PTR phObject) { + static CK_C_CreateObject sym = NULL; + static void *pPK11 = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_LOAD_FAILED); + } + if ((sym == NULL) || (hPK11 != pPK11)) { + pPK11 = hPK11; + sym = (CK_C_CreateObject)dlsym(hPK11, "C_CreateObject"); + } + if (sym == NULL) { + return (CKR_FUNCTION_NOT_SUPPORTED); + } + return ((*sym)(hSession, pTemplate, usCount, phObject)); +} + +CK_RV +pkcs_C_DestroyObject(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject) { + static CK_C_DestroyObject sym = NULL; + static void *pPK11 = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_LOAD_FAILED); + } + if ((sym == NULL) || (hPK11 != pPK11)) { + pPK11 = hPK11; + sym = (CK_C_DestroyObject)dlsym(hPK11, "C_DestroyObject"); + } + if (sym == NULL) { + return (CKR_FUNCTION_NOT_SUPPORTED); + } + return ((*sym)(hSession, hObject)); +} + +CK_RV +pkcs_C_GetAttributeValue(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject, + CK_ATTRIBUTE_PTR pTemplate, CK_ULONG usCount) { + static CK_C_GetAttributeValue sym = NULL; + static void *pPK11 = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_LOAD_FAILED); + } + if ((sym == NULL) || (hPK11 != pPK11)) { + pPK11 = hPK11; + sym = (CK_C_GetAttributeValue)dlsym(hPK11, "C_" + "GetAttributeValue"); + } + if (sym == NULL) { + return (CKR_FUNCTION_NOT_SUPPORTED); + } + return ((*sym)(hSession, hObject, pTemplate, usCount)); +} + +CK_RV +pkcs_C_SetAttributeValue(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject, + CK_ATTRIBUTE_PTR pTemplate, CK_ULONG usCount) { + static CK_C_SetAttributeValue sym = NULL; + static void *pPK11 = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_LOAD_FAILED); + } + if ((sym == NULL) || (hPK11 != pPK11)) { + pPK11 = hPK11; + sym = (CK_C_SetAttributeValue)dlsym(hPK11, "C_" + "SetAttributeValue"); + } + if (sym == NULL) { + return (CKR_FUNCTION_NOT_SUPPORTED); + } + return ((*sym)(hSession, hObject, pTemplate, usCount)); +} + +CK_RV +pkcs_C_FindObjectsInit(CK_SESSION_HANDLE hSession, CK_ATTRIBUTE_PTR pTemplate, + CK_ULONG usCount) { + static CK_C_FindObjectsInit sym = NULL; + static void *pPK11 = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_LOAD_FAILED); + } + if ((sym == NULL) || (hPK11 != pPK11)) { + pPK11 = hPK11; + sym = (CK_C_FindObjectsInit)dlsym(hPK11, "C_FindObjectsInit"); + } + if (sym == NULL) { + return (CKR_FUNCTION_NOT_SUPPORTED); + } + return ((*sym)(hSession, pTemplate, usCount)); +} + +CK_RV +pkcs_C_FindObjects(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE_PTR phObject, + CK_ULONG usMaxObjectCount, CK_ULONG_PTR pusObjectCount) { + static CK_C_FindObjects sym = NULL; + static void *pPK11 = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_LOAD_FAILED); + } + if ((sym == NULL) || (hPK11 != pPK11)) { + pPK11 = hPK11; + sym = (CK_C_FindObjects)dlsym(hPK11, "C_FindObjects"); + } + if (sym == NULL) { + return (CKR_FUNCTION_NOT_SUPPORTED); + } + return ((*sym)(hSession, phObject, usMaxObjectCount, pusObjectCount)); +} + +CK_RV +pkcs_C_FindObjectsFinal(CK_SESSION_HANDLE hSession) { + static CK_C_FindObjectsFinal sym = NULL; + static void *pPK11 = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_LOAD_FAILED); + } + if ((sym == NULL) || (hPK11 != pPK11)) { + pPK11 = hPK11; + sym = (CK_C_FindObjectsFinal)dlsym(hPK11, "C_FindObjectsFinal"); + } + if (sym == NULL) { + return (CKR_FUNCTION_NOT_SUPPORTED); + } + return ((*sym)(hSession)); +} + +CK_RV +pkcs_C_EncryptInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, + CK_OBJECT_HANDLE hKey) { + static CK_C_EncryptInit sym = NULL; + static void *pPK11 = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_LOAD_FAILED); + } + if ((sym == NULL) || (hPK11 != pPK11)) { + pPK11 = hPK11; + sym = (CK_C_EncryptInit)dlsym(hPK11, "C_EncryptInit"); + } + if (sym == NULL) { + return (CKR_FUNCTION_NOT_SUPPORTED); + } + return ((*sym)(hSession, pMechanism, hKey)); +} + +CK_RV +pkcs_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, + CK_ULONG ulDataLen, CK_BYTE_PTR pEncryptedData, + CK_ULONG_PTR pulEncryptedDataLen) { + static CK_C_Encrypt sym = NULL; + static void *pPK11 = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_LOAD_FAILED); + } + if ((sym == NULL) || (hPK11 != pPK11)) { + pPK11 = hPK11; + sym = (CK_C_Encrypt)dlsym(hPK11, "C_Encrypt"); + } + if (sym == NULL) { + return (CKR_FUNCTION_NOT_SUPPORTED); + } + return ((*sym)(hSession, pData, ulDataLen, pEncryptedData, + pulEncryptedDataLen)); +} + +CK_RV +pkcs_C_DigestInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism) { + static CK_C_DigestInit sym = NULL; + static void *pPK11 = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_LOAD_FAILED); + } + if ((sym == NULL) || (hPK11 != pPK11)) { + pPK11 = hPK11; + sym = (CK_C_DigestInit)dlsym(hPK11, "C_DigestInit"); + } + if (sym == NULL) { + return (CKR_FUNCTION_NOT_SUPPORTED); + } + return ((*sym)(hSession, pMechanism)); +} + +CK_RV +pkcs_C_DigestUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, + CK_ULONG ulPartLen) { + static CK_C_DigestUpdate sym = NULL; + static void *pPK11 = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_LOAD_FAILED); + } + if ((sym == NULL) || (hPK11 != pPK11)) { + pPK11 = hPK11; + sym = (CK_C_DigestUpdate)dlsym(hPK11, "C_DigestUpdate"); + } + if (sym == NULL) { + return (CKR_FUNCTION_NOT_SUPPORTED); + } + return ((*sym)(hSession, pPart, ulPartLen)); +} + +CK_RV +pkcs_C_DigestFinal(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pDigest, + CK_ULONG_PTR pulDigestLen) { + static CK_C_DigestFinal sym = NULL; + static void *pPK11 = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_LOAD_FAILED); + } + if ((sym == NULL) || (hPK11 != pPK11)) { + pPK11 = hPK11; + sym = (CK_C_DigestFinal)dlsym(hPK11, "C_DigestFinal"); + } + if (sym == NULL) { + return (CKR_FUNCTION_NOT_SUPPORTED); + } + return ((*sym)(hSession, pDigest, pulDigestLen)); +} + +CK_RV +pkcs_C_SignInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, + CK_OBJECT_HANDLE hKey) { + static CK_C_SignInit sym = NULL; + static void *pPK11 = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_LOAD_FAILED); + } + if ((sym == NULL) || (hPK11 != pPK11)) { + pPK11 = hPK11; + sym = (CK_C_SignInit)dlsym(hPK11, "C_SignInit"); + } + if (sym == NULL) { + return (CKR_FUNCTION_NOT_SUPPORTED); + } + return ((*sym)(hSession, pMechanism, hKey)); +} + +CK_RV +pkcs_C_Sign(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, CK_ULONG ulDataLen, + CK_BYTE_PTR pSignature, CK_ULONG_PTR pulSignatureLen) { + static CK_C_Sign sym = NULL; + static void *pPK11 = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_LOAD_FAILED); + } + if ((sym == NULL) || (hPK11 != pPK11)) { + pPK11 = hPK11; + sym = (CK_C_Sign)dlsym(hPK11, "C_Sign"); + } + if (sym == NULL) { + return (CKR_FUNCTION_NOT_SUPPORTED); + } + return ((*sym)(hSession, pData, ulDataLen, pSignature, + pulSignatureLen)); +} + +CK_RV +pkcs_C_SignUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, + CK_ULONG ulPartLen) { + static CK_C_SignUpdate sym = NULL; + static void *pPK11 = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_LOAD_FAILED); + } + if ((sym == NULL) || (hPK11 != pPK11)) { + pPK11 = hPK11; + sym = (CK_C_SignUpdate)dlsym(hPK11, "C_SignUpdate"); + } + if (sym == NULL) { + return (CKR_FUNCTION_NOT_SUPPORTED); + } + return ((*sym)(hSession, pPart, ulPartLen)); +} + +CK_RV +pkcs_C_SignFinal(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pSignature, + CK_ULONG_PTR pulSignatureLen) { + static CK_C_SignFinal sym = NULL; + static void *pPK11 = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_LOAD_FAILED); + } + if ((sym == NULL) || (hPK11 != pPK11)) { + pPK11 = hPK11; + sym = (CK_C_SignFinal)dlsym(hPK11, "C_SignFinal"); + } + if (sym == NULL) { + return (CKR_FUNCTION_NOT_SUPPORTED); + } + return ((*sym)(hSession, pSignature, pulSignatureLen)); +} + +CK_RV +pkcs_C_VerifyInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, + CK_OBJECT_HANDLE hKey) { + static CK_C_VerifyInit sym = NULL; + static void *pPK11 = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_LOAD_FAILED); + } + if ((sym == NULL) || (hPK11 != pPK11)) { + pPK11 = hPK11; + sym = (CK_C_VerifyInit)dlsym(hPK11, "C_VerifyInit"); + } + if (sym == NULL) { + return (CKR_FUNCTION_NOT_SUPPORTED); + } + return ((*sym)(hSession, pMechanism, hKey)); +} + +CK_RV +pkcs_C_Verify(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, CK_ULONG ulDataLen, + CK_BYTE_PTR pSignature, CK_ULONG ulSignatureLen) { + static CK_C_Verify sym = NULL; + static void *pPK11 = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_LOAD_FAILED); + } + if ((sym == NULL) || (hPK11 != pPK11)) { + pPK11 = hPK11; + sym = (CK_C_Verify)dlsym(hPK11, "C_Verify"); + } + if (sym == NULL) { + return (CKR_FUNCTION_NOT_SUPPORTED); + } + return ((*sym)(hSession, pData, ulDataLen, pSignature, ulSignatureLen)); +} + +CK_RV +pkcs_C_VerifyUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, + CK_ULONG ulPartLen) { + static CK_C_VerifyUpdate sym = NULL; + static void *pPK11 = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_LOAD_FAILED); + } + if ((sym == NULL) || (hPK11 != pPK11)) { + pPK11 = hPK11; + sym = (CK_C_VerifyUpdate)dlsym(hPK11, "C_VerifyUpdate"); + } + if (sym == NULL) { + return (CKR_FUNCTION_NOT_SUPPORTED); + } + return ((*sym)(hSession, pPart, ulPartLen)); +} + +CK_RV +pkcs_C_VerifyFinal(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pSignature, + CK_ULONG ulSignatureLen) { + static CK_C_VerifyFinal sym = NULL; + static void *pPK11 = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_LOAD_FAILED); + } + if ((sym == NULL) || (hPK11 != pPK11)) { + pPK11 = hPK11; + sym = (CK_C_VerifyFinal)dlsym(hPK11, "C_VerifyFinal"); + } + if (sym == NULL) { + return (CKR_FUNCTION_NOT_SUPPORTED); + } + return ((*sym)(hSession, pSignature, ulSignatureLen)); +} + +CK_RV +pkcs_C_GenerateKey(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, + CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount, + CK_OBJECT_HANDLE_PTR phKey) { + static CK_C_GenerateKey sym = NULL; + static void *pPK11 = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_LOAD_FAILED); + } + if ((sym == NULL) || (hPK11 != pPK11)) { + pPK11 = hPK11; + sym = (CK_C_GenerateKey)dlsym(hPK11, "C_GenerateKey"); + } + if (sym == NULL) { + return (CKR_FUNCTION_NOT_SUPPORTED); + } + return ((*sym)(hSession, pMechanism, pTemplate, ulCount, phKey)); +} + +CK_RV +pkcs_C_GenerateKeyPair(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, + CK_ATTRIBUTE_PTR pPublicKeyTemplate, + CK_ULONG usPublicKeyAttributeCount, + CK_ATTRIBUTE_PTR pPrivateKeyTemplate, + CK_ULONG usPrivateKeyAttributeCount, + CK_OBJECT_HANDLE_PTR phPrivateKey, + CK_OBJECT_HANDLE_PTR phPublicKey) { + static CK_C_GenerateKeyPair sym = NULL; + static void *pPK11 = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_LOAD_FAILED); + } + if ((sym == NULL) || (hPK11 != pPK11)) { + pPK11 = hPK11; + sym = (CK_C_GenerateKeyPair)dlsym(hPK11, "C_GenerateKeyPair"); + } + if (sym == NULL) { + return (CKR_FUNCTION_NOT_SUPPORTED); + } + return ((*sym)(hSession, pMechanism, pPublicKeyTemplate, + usPublicKeyAttributeCount, pPrivateKeyTemplate, + usPrivateKeyAttributeCount, phPrivateKey, phPublicKey)); +} + +CK_RV +pkcs_C_DeriveKey(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, + CK_OBJECT_HANDLE hBaseKey, CK_ATTRIBUTE_PTR pTemplate, + CK_ULONG ulAttributeCount, CK_OBJECT_HANDLE_PTR phKey) { + static CK_C_DeriveKey sym = NULL; + static void *pPK11 = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_LOAD_FAILED); + } + if ((sym == NULL) || (hPK11 != pPK11)) { + pPK11 = hPK11; + sym = (CK_C_DeriveKey)dlsym(hPK11, "C_DeriveKey"); + } + if (sym == NULL) { + return (CKR_FUNCTION_NOT_SUPPORTED); + } + return ((*sym)(hSession, pMechanism, hBaseKey, pTemplate, + ulAttributeCount, phKey)); +} + +CK_RV +pkcs_C_SeedRandom(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pSeed, + CK_ULONG ulSeedLen) { + static CK_C_SeedRandom sym = NULL; + static void *pPK11 = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_LOAD_FAILED); + } + if ((sym == NULL) || (hPK11 != pPK11)) { + pPK11 = hPK11; + sym = (CK_C_SeedRandom)dlsym(hPK11, "C_SeedRandom"); + } + if (sym == NULL) { + return (CKR_FUNCTION_NOT_SUPPORTED); + } + return ((*sym)(hSession, pSeed, ulSeedLen)); +} + +CK_RV +pkcs_C_GenerateRandom(CK_SESSION_HANDLE hSession, CK_BYTE_PTR RandomData, + CK_ULONG ulRandomLen) { + static CK_C_GenerateRandom sym = NULL; + static void *pPK11 = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_LOAD_FAILED); + } + if ((sym == NULL) || (hPK11 != pPK11)) { + pPK11 = hPK11; + sym = (CK_C_GenerateRandom)dlsym(hPK11, "C_GenerateRandom"); + } + if (sym == NULL) { + return (CKR_FUNCTION_NOT_SUPPORTED); + } + return ((*sym)(hSession, RandomData, ulRandomLen)); +} diff --git a/lib/isc/unix/resource.c b/lib/isc/unix/resource.c new file mode 100644 index 0000000..6020b39 --- /dev/null +++ b/lib/isc/unix/resource.c @@ -0,0 +1,219 @@ +/* + * 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. + */ + +#include <inttypes.h> +#include <stdbool.h> +#include <sys/resource.h> +#include <sys/time.h> /* Required on some systems for <sys/resource.h>. */ +#include <sys/types.h> + +#include <isc/platform.h> +#include <isc/resource.h> +#include <isc/result.h> +#include <isc/util.h> + +#ifdef __linux__ +#include <linux/fs.h> /* To get the large NR_OPEN. */ +#endif /* ifdef __linux__ */ + +#include "errno2result.h" + +static isc_result_t +resource2rlim(isc_resource_t resource, int *rlim_resource) { + isc_result_t result = ISC_R_SUCCESS; + + switch (resource) { + case isc_resource_coresize: + *rlim_resource = RLIMIT_CORE; + break; + case isc_resource_cputime: + *rlim_resource = RLIMIT_CPU; + break; + case isc_resource_datasize: + *rlim_resource = RLIMIT_DATA; + break; + case isc_resource_filesize: + *rlim_resource = RLIMIT_FSIZE; + break; + case isc_resource_lockedmemory: +#ifdef RLIMIT_MEMLOCK + *rlim_resource = RLIMIT_MEMLOCK; +#else /* ifdef RLIMIT_MEMLOCK */ + result = ISC_R_NOTIMPLEMENTED; +#endif /* ifdef RLIMIT_MEMLOCK */ + break; + case isc_resource_openfiles: +#ifdef RLIMIT_NOFILE + *rlim_resource = RLIMIT_NOFILE; +#else /* ifdef RLIMIT_NOFILE */ + result = ISC_R_NOTIMPLEMENTED; +#endif /* ifdef RLIMIT_NOFILE */ + break; + case isc_resource_processes: +#ifdef RLIMIT_NPROC + *rlim_resource = RLIMIT_NPROC; +#else /* ifdef RLIMIT_NPROC */ + result = ISC_R_NOTIMPLEMENTED; +#endif /* ifdef RLIMIT_NPROC */ + break; + case isc_resource_residentsize: +#ifdef RLIMIT_RSS + *rlim_resource = RLIMIT_RSS; +#else /* ifdef RLIMIT_RSS */ + result = ISC_R_NOTIMPLEMENTED; +#endif /* ifdef RLIMIT_RSS */ + break; + case isc_resource_stacksize: + *rlim_resource = RLIMIT_STACK; + break; + default: + /* + * This test is not very robust if isc_resource_t + * changes, but generates a clear assertion message. + */ + REQUIRE(resource >= isc_resource_coresize && + resource <= isc_resource_stacksize); + + result = ISC_R_RANGE; + break; + } + + return (result); +} + +isc_result_t +isc_resource_setlimit(isc_resource_t resource, isc_resourcevalue_t value) { + struct rlimit rl; + rlim_t rlim_value; + int unixresult; + int unixresource; + isc_result_t result; + + result = resource2rlim(resource, &unixresource); + if (result != ISC_R_SUCCESS) { + return (result); + } + + if (value == ISC_RESOURCE_UNLIMITED) { + rlim_value = RLIM_INFINITY; + } else { + /* + * isc_resourcevalue_t was chosen as an unsigned 64 bit + * integer so that it could contain the maximum range of + * reasonable values. Unfortunately, this exceeds the typical + * range on Unix systems. Ensure the range of + * rlim_t is not overflowed. + */ + isc_resourcevalue_t rlim_max; + bool rlim_t_is_signed = (((double)(rlim_t)-1) < 0); + + if (rlim_t_is_signed) { + rlim_max = ~((rlim_t)1 << (sizeof(rlim_t) * 8 - 1)); + } else { + rlim_max = (rlim_t)-1; + } + + if (value > rlim_max) { + value = rlim_max; + } + + rlim_value = value; + } + + rl.rlim_cur = rl.rlim_max = rlim_value; + unixresult = setrlimit(unixresource, &rl); + + if (unixresult == 0) { + return (ISC_R_SUCCESS); + } + +#if defined(OPEN_MAX) && defined(__APPLE__) + /* + * The Darwin kernel doesn't accept RLIM_INFINITY for rlim_cur; the + * maximum possible value is OPEN_MAX. BIND8 used to use + * sysconf(_SC_OPEN_MAX) for such a case, but this value is much + * smaller than OPEN_MAX and is not really effective. + */ + if (resource == isc_resource_openfiles && rlim_value == RLIM_INFINITY) { + rl.rlim_cur = OPEN_MAX; + unixresult = setrlimit(unixresource, &rl); + if (unixresult == 0) { + return (ISC_R_SUCCESS); + } + } +#elif defined(__linux__) +#ifndef NR_OPEN +#define NR_OPEN (1024 * 1024) +#endif /* ifndef NR_OPEN */ + + /* + * Some Linux kernels don't accept RLIM_INFINIT; the maximum + * possible value is the NR_OPEN defined in linux/fs.h. + */ + if (resource == isc_resource_openfiles && rlim_value == RLIM_INFINITY) { + rl.rlim_cur = rl.rlim_max = NR_OPEN; + unixresult = setrlimit(unixresource, &rl); + if (unixresult == 0) { + return (ISC_R_SUCCESS); + } + } +#endif /* if defined(OPEN_MAX) && defined(__APPLE__) */ + if (resource == isc_resource_openfiles && rlim_value == RLIM_INFINITY) { + if (getrlimit(unixresource, &rl) == 0) { + rl.rlim_cur = rl.rlim_max; + unixresult = setrlimit(unixresource, &rl); + if (unixresult == 0) { + return (ISC_R_SUCCESS); + } + } + } + return (isc__errno2result(errno)); +} + +isc_result_t +isc_resource_getlimit(isc_resource_t resource, isc_resourcevalue_t *value) { + int unixresource; + struct rlimit rl; + isc_result_t result; + + result = resource2rlim(resource, &unixresource); + if (result != ISC_R_SUCCESS) { + return (result); + } + + if (getrlimit(unixresource, &rl) != 0) { + return (isc__errno2result(errno)); + } + + *value = rl.rlim_max; + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_resource_getcurlimit(isc_resource_t resource, isc_resourcevalue_t *value) { + int unixresource; + struct rlimit rl; + isc_result_t result; + + result = resource2rlim(resource, &unixresource); + if (result != ISC_R_SUCCESS) { + return (result); + } + + if (getrlimit(unixresource, &rl) != 0) { + return (isc__errno2result(errno)); + } + + *value = rl.rlim_cur; + return (ISC_R_SUCCESS); +} diff --git a/lib/isc/unix/socket.c b/lib/isc/unix/socket.c new file mode 100644 index 0000000..d6578ea --- /dev/null +++ b/lib/isc/unix/socket.c @@ -0,0 +1,5521 @@ +/* + * 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. + */ + +/*! \file */ + +#include <inttypes.h> +#include <stdbool.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#if defined(HAVE_SYS_SYSCTL_H) && !defined(__linux__) +#include <sys/sysctl.h> +#endif /* if defined(HAVE_SYS_SYSCTL_H) && !defined(__linux__) */ +#include <sys/time.h> +#include <sys/uio.h> + +#if defined(HAVE_LINUX_NETLINK_H) && defined(HAVE_LINUX_RTNETLINK_H) +#include <linux/netlink.h> +#include <linux/rtnetlink.h> +#endif /* if defined(HAVE_LINUX_NETLINK_H) && defined(HAVE_LINUX_RTNETLINK_H) \ + */ + +#include <errno.h> +#include <fcntl.h> +#include <stddef.h> +#include <stdlib.h> +#include <unistd.h> + +#include <isc/app.h> +#include <isc/buffer.h> +#include <isc/condition.h> +#include <isc/formatcheck.h> +#include <isc/list.h> +#include <isc/log.h> +#include <isc/mem.h> +#include <isc/mutex.h> +#include <isc/net.h> +#include <isc/once.h> +#include <isc/platform.h> +#include <isc/print.h> +#include <isc/refcount.h> +#include <isc/region.h> +#include <isc/resource.h> +#include <isc/socket.h> +#include <isc/stats.h> +#include <isc/strerr.h> +#include <isc/string.h> +#include <isc/task.h> +#include <isc/thread.h> +#include <isc/util.h> + +#ifdef ISC_PLATFORM_HAVESYSUNH +#include <sys/un.h> +#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */ +#ifdef HAVE_KQUEUE +#include <sys/event.h> +#endif /* ifdef HAVE_KQUEUE */ +#ifdef HAVE_EPOLL_CREATE1 +#include <sys/epoll.h> +#endif /* ifdef HAVE_EPOLL_CREATE1 */ +#if defined(HAVE_SYS_DEVPOLL_H) +#include <sys/devpoll.h> +#elif defined(HAVE_DEVPOLL_H) +#include <devpoll.h> +#endif /* if defined(HAVE_SYS_DEVPOLL_H) */ + +#include <netinet/tcp.h> + +#include "errno2result.h" + +#ifdef ENABLE_TCP_FASTOPEN +#include <netinet/tcp.h> +#endif /* ifdef ENABLE_TCP_FASTOPEN */ + +#ifdef HAVE_JSON_C +#include <json_object.h> +#endif /* HAVE_JSON_C */ + +#ifdef HAVE_LIBXML2 +#include <libxml/xmlwriter.h> +#define ISC_XMLCHAR (const xmlChar *) +#endif /* HAVE_LIBXML2 */ + +/*% + * Choose the most preferable multiplex method. + */ +#if defined(HAVE_KQUEUE) +#define USE_KQUEUE +#elif defined(HAVE_EPOLL_CREATE1) +#define USE_EPOLL +#elif defined(HAVE_SYS_DEVPOLL_H) || defined(HAVE_DEVPOLL_H) +#define USE_DEVPOLL +typedef struct { + unsigned int want_read : 1, want_write : 1; +} pollinfo_t; +#else /* if defined(HAVE_KQUEUE) */ +#define USE_SELECT +#endif /* HAVE_KQUEUE */ + +/* + * Set by the -T dscp option on the command line. If set to a value + * other than -1, we check to make sure DSCP values match it, and + * assert if not. + */ +int isc_dscp_check_value = -1; + +/*% + * Maximum number of allowable open sockets. This is also the maximum + * allowable socket file descriptor. + * + * Care should be taken before modifying this value for select(): + * The API standard doesn't ensure select() accept more than (the system default + * of) FD_SETSIZE descriptors, and the default size should in fact be fine in + * the vast majority of cases. This constant should therefore be increased only + * when absolutely necessary and possible, i.e., the server is exhausting all + * available file descriptors (up to FD_SETSIZE) and the select() function + * and FD_xxx macros support larger values than FD_SETSIZE (which may not + * always by true, but we keep using some of them to ensure as much + * portability as possible). Note also that overall server performance + * may be rather worsened with a larger value of this constant due to + * inherent scalability problems of select(). + * + * As a special note, this value shouldn't have to be touched if + * this is a build for an authoritative only DNS server. + */ +#ifndef ISC_SOCKET_MAXSOCKETS +#if defined(USE_KQUEUE) || defined(USE_EPOLL) || defined(USE_DEVPOLL) +#ifdef TUNE_LARGE +#define ISC_SOCKET_MAXSOCKETS 21000 +#else /* ifdef TUNE_LARGE */ +#define ISC_SOCKET_MAXSOCKETS 4096 +#endif /* TUNE_LARGE */ +#elif defined(USE_SELECT) +#define ISC_SOCKET_MAXSOCKETS FD_SETSIZE +#endif /* USE_KQUEUE... */ +#endif /* ISC_SOCKET_MAXSOCKETS */ + +#ifdef USE_SELECT +/*% + * Mac OS X needs a special definition to support larger values in select(). + * We always define this because a larger value can be specified run-time. + */ +#ifdef __APPLE__ +#define _DARWIN_UNLIMITED_SELECT +#endif /* __APPLE__ */ +#endif /* USE_SELECT */ + +#ifdef ISC_SOCKET_USE_POLLWATCH +/*% + * If this macro is defined, enable workaround for a Solaris /dev/poll kernel + * bug: DP_POLL ioctl could keep sleeping even if socket I/O is possible for + * some of the specified FD. The idea is based on the observation that it's + * likely for a busy server to keep receiving packets. It specifically works + * as follows: the socket watcher is first initialized with the state of + * "poll_idle". While it's in the idle state it keeps sleeping until a socket + * event occurs. When it wakes up for a socket I/O event, it moves to the + * poll_active state, and sets the poll timeout to a short period + * (ISC_SOCKET_POLLWATCH_TIMEOUT msec). If timeout occurs in this state, the + * watcher goes to the poll_checking state with the same timeout period. + * In this state, the watcher tries to detect whether this is a break + * during intermittent events or the kernel bug is triggered. If the next + * polling reports an event within the short period, the previous timeout is + * likely to be a kernel bug, and so the watcher goes back to the active state. + * Otherwise, it moves to the idle state again. + * + * It's not clear whether this is a thread-related bug, but since we've only + * seen this with threads, this workaround is used only when enabling threads. + */ + +typedef enum { poll_idle, poll_active, poll_checking } pollstate_t; + +#ifndef ISC_SOCKET_POLLWATCH_TIMEOUT +#define ISC_SOCKET_POLLWATCH_TIMEOUT 10 +#endif /* ISC_SOCKET_POLLWATCH_TIMEOUT */ +#endif /* ISC_SOCKET_USE_POLLWATCH */ + +/*% + * Per-FD lock buckets, we shuffle them around a bit as FDs come in herds. + */ +#define FDLOCK_BITS 10 +#define FDLOCK_COUNT (1 << FDLOCK_BITS) +#define FDLOCK_ID(fd) \ + (((fd) % (FDLOCK_COUNT) >> (FDLOCK_BITS / 2)) | \ + (((fd) << (FDLOCK_BITS / 2)) % (FDLOCK_COUNT))) + +/*% + * Maximum number of events communicated with the kernel. There should normally + * be no need for having a large number. + */ +#if defined(USE_KQUEUE) || defined(USE_EPOLL) || defined(USE_DEVPOLL) +#ifndef ISC_SOCKET_MAXEVENTS +#ifdef TUNE_LARGE +#define ISC_SOCKET_MAXEVENTS 2048 +#else /* ifdef TUNE_LARGE */ +#define ISC_SOCKET_MAXEVENTS 64 +#endif /* TUNE_LARGE */ +#endif /* ifndef ISC_SOCKET_MAXEVENTS */ +#endif /* if defined(USE_KQUEUE) || defined(USE_EPOLL) || defined(USE_DEVPOLL) \ + * */ + +/*% + * Some systems define the socket length argument as an int, some as size_t, + * some as socklen_t. This is here so it can be easily changed if needed. + */ +#ifndef socklen_t +#define socklen_t unsigned int +#endif /* ifndef socklen_t */ + +/*% + * Define what the possible "soft" errors can be. These are non-fatal returns + * of various network related functions, like recv() and so on. + * + * For some reason, BSDI (and perhaps others) will sometimes return <0 + * from recv() but will have errno==0. This is broken, but we have to + * work around it here. + */ +#define SOFT_ERROR(e) \ + ((e) == EAGAIN || (e) == EWOULDBLOCK || (e) == ENOBUFS || \ + (e) == EINTR || (e) == 0) + +#define DLVL(x) ISC_LOGCATEGORY_GENERAL, ISC_LOGMODULE_SOCKET, ISC_LOG_DEBUG(x) + +/*!< + * DLVL(90) -- Function entry/exit and other tracing. + * DLVL(70) -- Socket "correctness" -- including returning of events, etc. + * DLVL(60) -- Socket data send/receive + * DLVL(50) -- Event tracing, including receiving/sending completion events. + * DLVL(20) -- Socket creation/destruction. + */ +#define TRACE_LEVEL 90 +#define CORRECTNESS_LEVEL 70 +#define IOEVENT_LEVEL 60 +#define EVENT_LEVEL 50 +#define CREATION_LEVEL 20 + +#define TRACE DLVL(TRACE_LEVEL) +#define CORRECTNESS DLVL(CORRECTNESS_LEVEL) +#define IOEVENT DLVL(IOEVENT_LEVEL) +#define EVENT DLVL(EVENT_LEVEL) +#define CREATION DLVL(CREATION_LEVEL) + +typedef isc_event_t intev_t; + +#define SOCKET_MAGIC ISC_MAGIC('I', 'O', 'i', 'o') +#define VALID_SOCKET(s) ISC_MAGIC_VALID(s, SOCKET_MAGIC) + +/*! + * IPv6 control information. If the socket is an IPv6 socket we want + * to collect the destination address and interface so the client can + * set them on outgoing packets. + */ +#ifndef USE_CMSG +#define USE_CMSG 1 +#endif /* ifndef USE_CMSG */ + +/*% + * NetBSD and FreeBSD can timestamp packets. XXXMLG Should we have + * a setsockopt() like interface to request timestamps, and if the OS + * doesn't do it for us, call gettimeofday() on every UDP receive? + */ +#ifdef SO_TIMESTAMP +#ifndef USE_CMSG +#define USE_CMSG 1 +#endif /* ifndef USE_CMSG */ +#endif /* ifdef SO_TIMESTAMP */ + +#if defined(SO_RCVBUF) && defined(ISC_RECV_BUFFER_SIZE) +#define SET_RCVBUF +#endif + +#if defined(SO_SNDBUF) && defined(ISC_SEND_BUFFER_SIZE) +#define SET_SNDBUF +#endif + +/*% + * Instead of calculating the cmsgbuf lengths every time we take + * a rule of thumb approach - sizes are taken from x86_64 linux, + * multiplied by 2, everything should fit. Those sizes are not + * large enough to cause any concern. + */ +#if defined(USE_CMSG) +#define CMSG_SP_IN6PKT 40 +#else /* if defined(USE_CMSG) */ +#define CMSG_SP_IN6PKT 0 +#endif /* if defined(USE_CMSG) */ + +#if defined(USE_CMSG) && defined(SO_TIMESTAMP) +#define CMSG_SP_TIMESTAMP 32 +#else /* if defined(USE_CMSG) && defined(SO_TIMESTAMP) */ +#define CMSG_SP_TIMESTAMP 0 +#endif /* if defined(USE_CMSG) && defined(SO_TIMESTAMP) */ + +#if defined(USE_CMSG) && (defined(IPV6_TCLASS) || defined(IP_TOS)) +#define CMSG_SP_TCTOS 24 +#else /* if defined(USE_CMSG) && (defined(IPV6_TCLASS) || defined(IP_TOS)) */ +#define CMSG_SP_TCTOS 0 +#endif /* if defined(USE_CMSG) && (defined(IPV6_TCLASS) || defined(IP_TOS)) */ + +#define CMSG_SP_INT 24 + +/* Align cmsg buffers to be safe on SPARC etc. */ +#define RECVCMSGBUFLEN \ + ISC_ALIGN(2 * (CMSG_SP_IN6PKT + CMSG_SP_TIMESTAMP + CMSG_SP_TCTOS) + \ + 1, \ + sizeof(void *)) +#define SENDCMSGBUFLEN \ + ISC_ALIGN(2 * (CMSG_SP_IN6PKT + CMSG_SP_INT + CMSG_SP_TCTOS) + 1, \ + sizeof(void *)) + +/*% + * The number of times a send operation is repeated if the result is EINTR. + */ +#define NRETRIES 10 + +typedef struct isc__socketthread isc__socketthread_t; + +#define NEWCONNSOCK(ev) ((ev)->newsocket) + +struct isc_socket { + /* Not locked. */ + unsigned int magic; + isc_socketmgr_t *manager; + isc_mutex_t lock; + isc_sockettype_t type; + const isc_statscounter_t *statsindex; + isc_refcount_t references; + + /* Locked by socket lock. */ + ISC_LINK(isc_socket_t) link; + int fd; + int pf; + int threadid; + char name[16]; + void *tag; + + ISC_LIST(isc_socketevent_t) send_list; + ISC_LIST(isc_socketevent_t) recv_list; + ISC_LIST(isc_socket_newconnev_t) accept_list; + ISC_LIST(isc_socket_connev_t) connect_list; + + isc_sockaddr_t peer_address; /* remote address */ + + unsigned int listener : 1, /* listener socket */ + connected : 1, connecting : 1, /* connect pending + * */ + bound : 1, /* bound to local addr */ + dupped : 1, active : 1, /* currently active */ + pktdscp : 1; /* per packet dscp */ + +#ifdef ISC_PLATFORM_RECVOVERFLOW + unsigned char overflow; /* used for MSG_TRUNC fake */ +#endif /* ifdef ISC_PLATFORM_RECVOVERFLOW */ + + unsigned int dscp; +}; + +#define SOCKET_MANAGER_MAGIC ISC_MAGIC('I', 'O', 'm', 'g') +#define VALID_MANAGER(m) ISC_MAGIC_VALID(m, SOCKET_MANAGER_MAGIC) + +struct isc_socketmgr { + /* Not locked. */ + unsigned int magic; + isc_mem_t *mctx; + isc_mutex_t lock; + isc_stats_t *stats; + int nthreads; + isc__socketthread_t *threads; + unsigned int maxsocks; + /* Locked by manager lock. */ + ISC_LIST(isc_socket_t) socklist; + int reserved; /* unlocked */ + isc_condition_t shutdown_ok; + size_t maxudp; +}; + +struct isc__socketthread { + isc_socketmgr_t *manager; + int threadid; + isc_thread_t thread; + int pipe_fds[2]; + isc_mutex_t *fdlock; + /* Locked by fdlock. */ + isc_socket_t **fds; + int *fdstate; +#ifdef USE_KQUEUE + int kqueue_fd; + int nevents; + struct kevent *events; +#endif /* USE_KQUEUE */ +#ifdef USE_EPOLL + int epoll_fd; + int nevents; + struct epoll_event *events; + uint32_t *epoll_events; +#endif /* USE_EPOLL */ +#ifdef USE_DEVPOLL + int devpoll_fd; + isc_resourcevalue_t open_max; + unsigned int calls; + int nevents; + struct pollfd *events; + pollinfo_t *fdpollinfo; +#endif /* USE_DEVPOLL */ +#ifdef USE_SELECT + int fd_bufsize; + fd_set *read_fds; + fd_set *read_fds_copy; + fd_set *write_fds; + fd_set *write_fds_copy; + int maxfd; +#endif /* USE_SELECT */ +}; + +#define CLOSED 0 /* this one must be zero */ +#define MANAGED 1 +#define CLOSE_PENDING 2 + +/* + * send() and recv() iovec counts + */ +#define MAXSCATTERGATHER_SEND (ISC_SOCKET_MAXSCATTERGATHER) +#ifdef ISC_PLATFORM_RECVOVERFLOW +#define MAXSCATTERGATHER_RECV (ISC_SOCKET_MAXSCATTERGATHER + 1) +#else /* ifdef ISC_PLATFORM_RECVOVERFLOW */ +#define MAXSCATTERGATHER_RECV (ISC_SOCKET_MAXSCATTERGATHER) +#endif /* ifdef ISC_PLATFORM_RECVOVERFLOW */ + +static isc_result_t +socket_create(isc_socketmgr_t *manager0, int pf, isc_sockettype_t type, + isc_socket_t **socketp, isc_socket_t *dup_socket); +static void +send_recvdone_event(isc_socket_t *, isc_socketevent_t **); +static void +send_senddone_event(isc_socket_t *, isc_socketevent_t **); +static void +send_connectdone_event(isc_socket_t *, isc_socket_connev_t **); +static void +free_socket(isc_socket_t **); +static isc_result_t +allocate_socket(isc_socketmgr_t *, isc_sockettype_t, isc_socket_t **); +static void +destroy(isc_socket_t **); +static void +internal_accept(isc_socket_t *); +static void +internal_connect(isc_socket_t *); +static void +internal_recv(isc_socket_t *); +static void +internal_send(isc_socket_t *); +static void +process_cmsg(isc_socket_t *, struct msghdr *, isc_socketevent_t *); +static void +build_msghdr_send(isc_socket_t *, char *, isc_socketevent_t *, struct msghdr *, + struct iovec *, size_t *); +static void +build_msghdr_recv(isc_socket_t *, char *, isc_socketevent_t *, struct msghdr *, + struct iovec *, size_t *); +static bool +process_ctlfd(isc__socketthread_t *thread); +static void +setdscp(isc_socket_t *sock, isc_dscp_t dscp); + +#define SELECT_POKE_SHUTDOWN (-1) +#define SELECT_POKE_NOTHING (-2) +#define SELECT_POKE_READ (-3) +#define SELECT_POKE_ACCEPT (-3) /*%< Same as _READ */ +#define SELECT_POKE_WRITE (-4) +#define SELECT_POKE_CONNECT (-4) /*%< Same as _WRITE */ +#define SELECT_POKE_CLOSE (-5) + +/*% + * Shortcut index arrays to get access to statistics counters. + */ +enum { + STATID_OPEN = 0, + STATID_OPENFAIL = 1, + STATID_CLOSE = 2, + STATID_BINDFAIL = 3, + STATID_CONNECTFAIL = 4, + STATID_CONNECT = 5, + STATID_ACCEPTFAIL = 6, + STATID_ACCEPT = 7, + STATID_SENDFAIL = 8, + STATID_RECVFAIL = 9, + STATID_ACTIVE = 10 +}; +static const isc_statscounter_t udp4statsindex[] = { + isc_sockstatscounter_udp4open, + isc_sockstatscounter_udp4openfail, + isc_sockstatscounter_udp4close, + isc_sockstatscounter_udp4bindfail, + isc_sockstatscounter_udp4connectfail, + isc_sockstatscounter_udp4connect, + -1, + -1, + isc_sockstatscounter_udp4sendfail, + isc_sockstatscounter_udp4recvfail, + isc_sockstatscounter_udp4active +}; +static const isc_statscounter_t udp6statsindex[] = { + isc_sockstatscounter_udp6open, + isc_sockstatscounter_udp6openfail, + isc_sockstatscounter_udp6close, + isc_sockstatscounter_udp6bindfail, + isc_sockstatscounter_udp6connectfail, + isc_sockstatscounter_udp6connect, + -1, + -1, + isc_sockstatscounter_udp6sendfail, + isc_sockstatscounter_udp6recvfail, + isc_sockstatscounter_udp6active +}; +static const isc_statscounter_t tcp4statsindex[] = { + isc_sockstatscounter_tcp4open, isc_sockstatscounter_tcp4openfail, + isc_sockstatscounter_tcp4close, isc_sockstatscounter_tcp4bindfail, + isc_sockstatscounter_tcp4connectfail, isc_sockstatscounter_tcp4connect, + isc_sockstatscounter_tcp4acceptfail, isc_sockstatscounter_tcp4accept, + isc_sockstatscounter_tcp4sendfail, isc_sockstatscounter_tcp4recvfail, + isc_sockstatscounter_tcp4active +}; +static const isc_statscounter_t tcp6statsindex[] = { + isc_sockstatscounter_tcp6open, isc_sockstatscounter_tcp6openfail, + isc_sockstatscounter_tcp6close, isc_sockstatscounter_tcp6bindfail, + isc_sockstatscounter_tcp6connectfail, isc_sockstatscounter_tcp6connect, + isc_sockstatscounter_tcp6acceptfail, isc_sockstatscounter_tcp6accept, + isc_sockstatscounter_tcp6sendfail, isc_sockstatscounter_tcp6recvfail, + isc_sockstatscounter_tcp6active +}; +static const isc_statscounter_t unixstatsindex[] = { + isc_sockstatscounter_unixopen, isc_sockstatscounter_unixopenfail, + isc_sockstatscounter_unixclose, isc_sockstatscounter_unixbindfail, + isc_sockstatscounter_unixconnectfail, isc_sockstatscounter_unixconnect, + isc_sockstatscounter_unixacceptfail, isc_sockstatscounter_unixaccept, + isc_sockstatscounter_unixsendfail, isc_sockstatscounter_unixrecvfail, + isc_sockstatscounter_unixactive +}; +static const isc_statscounter_t rawstatsindex[] = { + isc_sockstatscounter_rawopen, + isc_sockstatscounter_rawopenfail, + isc_sockstatscounter_rawclose, + -1, + -1, + -1, + -1, + -1, + -1, + isc_sockstatscounter_rawrecvfail, + isc_sockstatscounter_rawactive +}; + +static int +gen_threadid(isc_socket_t *sock); + +static int +gen_threadid(isc_socket_t *sock) { + return (sock->fd % sock->manager->nthreads); +} + +static void +manager_log(isc_socketmgr_t *sockmgr, isc_logcategory_t *category, + isc_logmodule_t *module, int level, const char *fmt, ...) + ISC_FORMAT_PRINTF(5, 6); +static void +manager_log(isc_socketmgr_t *sockmgr, isc_logcategory_t *category, + isc_logmodule_t *module, int level, const char *fmt, ...) { + char msgbuf[2048]; + va_list ap; + + if (!isc_log_wouldlog(isc_lctx, level)) { + return; + } + + va_start(ap, fmt); + vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap); + va_end(ap); + + isc_log_write(isc_lctx, category, module, level, "sockmgr %p: %s", + sockmgr, msgbuf); +} + +static void +thread_log(isc__socketthread_t *thread, isc_logcategory_t *category, + isc_logmodule_t *module, int level, const char *fmt, ...) + ISC_FORMAT_PRINTF(5, 6); +static void +thread_log(isc__socketthread_t *thread, isc_logcategory_t *category, + isc_logmodule_t *module, int level, const char *fmt, ...) { + char msgbuf[2048]; + va_list ap; + + if (!isc_log_wouldlog(isc_lctx, level)) { + return; + } + + va_start(ap, fmt); + vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap); + va_end(ap); + + isc_log_write(isc_lctx, category, module, level, + "sockmgr %p thread %d: %s", thread->manager, + thread->threadid, msgbuf); +} + +static void +socket_log(isc_socket_t *sock, const isc_sockaddr_t *address, + isc_logcategory_t *category, isc_logmodule_t *module, int level, + const char *fmt, ...) ISC_FORMAT_PRINTF(6, 7); +static void +socket_log(isc_socket_t *sock, const isc_sockaddr_t *address, + isc_logcategory_t *category, isc_logmodule_t *module, int level, + const char *fmt, ...) { + char msgbuf[2048]; + char peerbuf[ISC_SOCKADDR_FORMATSIZE]; + va_list ap; + + if (!isc_log_wouldlog(isc_lctx, level)) { + return; + } + + va_start(ap, fmt); + vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap); + va_end(ap); + + if (address == NULL) { + isc_log_write(isc_lctx, category, module, level, + "socket %p: %s", sock, msgbuf); + } else { + isc_sockaddr_format(address, peerbuf, sizeof(peerbuf)); + isc_log_write(isc_lctx, category, module, level, + "socket %p %s: %s", sock, peerbuf, msgbuf); + } +} + +/*% + * Increment socket-related statistics counters. + */ +static void +inc_stats(isc_stats_t *stats, isc_statscounter_t counterid) { + REQUIRE(counterid != -1); + + if (stats != NULL) { + isc_stats_increment(stats, counterid); + } +} + +/*% + * Decrement socket-related statistics counters. + */ +static void +dec_stats(isc_stats_t *stats, isc_statscounter_t counterid) { + REQUIRE(counterid != -1); + + if (stats != NULL) { + isc_stats_decrement(stats, counterid); + } +} + +static isc_result_t +watch_fd(isc__socketthread_t *thread, int fd, int msg) { + isc_result_t result = ISC_R_SUCCESS; + +#ifdef USE_KQUEUE + struct kevent evchange; + + memset(&evchange, 0, sizeof(evchange)); + if (msg == SELECT_POKE_READ) { + evchange.filter = EVFILT_READ; + } else { + evchange.filter = EVFILT_WRITE; + } + evchange.flags = EV_ADD; + evchange.ident = fd; + if (kevent(thread->kqueue_fd, &evchange, 1, NULL, 0, NULL) != 0) { + result = isc__errno2result(errno); + } + + return (result); +#elif defined(USE_EPOLL) + struct epoll_event event; + uint32_t oldevents; + int ret; + int op; + + oldevents = thread->epoll_events[fd]; + if (msg == SELECT_POKE_READ) { + thread->epoll_events[fd] |= EPOLLIN; + } else { + thread->epoll_events[fd] |= EPOLLOUT; + } + + event.events = thread->epoll_events[fd]; + memset(&event.data, 0, sizeof(event.data)); + event.data.fd = fd; + + op = (oldevents == 0U) ? EPOLL_CTL_ADD : EPOLL_CTL_MOD; + if (thread->fds[fd] != NULL) { + LOCK(&thread->fds[fd]->lock); + } + ret = epoll_ctl(thread->epoll_fd, op, fd, &event); + if (thread->fds[fd] != NULL) { + UNLOCK(&thread->fds[fd]->lock); + } + if (ret == -1) { + if (errno == EEXIST) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "epoll_ctl(ADD/MOD) returned " + "EEXIST for fd %d", + fd); + } + result = isc__errno2result(errno); + } + + return (result); +#elif defined(USE_DEVPOLL) + struct pollfd pfd; + + memset(&pfd, 0, sizeof(pfd)); + if (msg == SELECT_POKE_READ) { + pfd.events = POLLIN; + } else { + pfd.events = POLLOUT; + } + pfd.fd = fd; + pfd.revents = 0; + if (write(thread->devpoll_fd, &pfd, sizeof(pfd)) == -1) { + result = isc__errno2result(errno); + } else { + if (msg == SELECT_POKE_READ) { + thread->fdpollinfo[fd].want_read = 1; + } else { + thread->fdpollinfo[fd].want_write = 1; + } + } + + return (result); +#elif defined(USE_SELECT) + LOCK(&thread->manager->lock); + if (msg == SELECT_POKE_READ) { + FD_SET(fd, thread->read_fds); + } + if (msg == SELECT_POKE_WRITE) { + FD_SET(fd, thread->write_fds); + } + UNLOCK(&thread->manager->lock); + + return (result); +#endif /* ifdef USE_KQUEUE */ +} + +static isc_result_t +unwatch_fd(isc__socketthread_t *thread, int fd, int msg) { + isc_result_t result = ISC_R_SUCCESS; + +#ifdef USE_KQUEUE + struct kevent evchange; + + memset(&evchange, 0, sizeof(evchange)); + if (msg == SELECT_POKE_READ) { + evchange.filter = EVFILT_READ; + } else { + evchange.filter = EVFILT_WRITE; + } + evchange.flags = EV_DELETE; + evchange.ident = fd; + if (kevent(thread->kqueue_fd, &evchange, 1, NULL, 0, NULL) != 0) { + result = isc__errno2result(errno); + } + + return (result); +#elif defined(USE_EPOLL) + struct epoll_event event; + int ret; + int op; + + if (msg == SELECT_POKE_READ) { + thread->epoll_events[fd] &= ~(EPOLLIN); + } else { + thread->epoll_events[fd] &= ~(EPOLLOUT); + } + + event.events = thread->epoll_events[fd]; + memset(&event.data, 0, sizeof(event.data)); + event.data.fd = fd; + + op = (event.events == 0U) ? EPOLL_CTL_DEL : EPOLL_CTL_MOD; + ret = epoll_ctl(thread->epoll_fd, op, fd, &event); + if (ret == -1 && errno != ENOENT) { + char strbuf[ISC_STRERRORSIZE]; + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, "epoll_ctl(DEL), %d: %s", + fd, strbuf); + result = ISC_R_UNEXPECTED; + } + return (result); +#elif defined(USE_DEVPOLL) + struct pollfd pfds[2]; + size_t writelen = sizeof(pfds[0]); + + memset(pfds, 0, sizeof(pfds)); + pfds[0].events = POLLREMOVE; + pfds[0].fd = fd; + + /* + * Canceling read or write polling via /dev/poll is tricky. Since it + * only provides a way of canceling per FD, we may need to re-poll the + * socket for the other operation. + */ + if (msg == SELECT_POKE_READ && thread->fdpollinfo[fd].want_write == 1) { + pfds[1].events = POLLOUT; + pfds[1].fd = fd; + writelen += sizeof(pfds[1]); + } + if (msg == SELECT_POKE_WRITE && thread->fdpollinfo[fd].want_read == 1) { + pfds[1].events = POLLIN; + pfds[1].fd = fd; + writelen += sizeof(pfds[1]); + } + + if (write(thread->devpoll_fd, pfds, writelen) == -1) { + result = isc__errno2result(errno); + } else { + if (msg == SELECT_POKE_READ) { + thread->fdpollinfo[fd].want_read = 0; + } else { + thread->fdpollinfo[fd].want_write = 0; + } + } + + return (result); +#elif defined(USE_SELECT) + LOCK(&thread->manager->lock); + if (msg == SELECT_POKE_READ) { + FD_CLR(fd, thread->read_fds); + } else if (msg == SELECT_POKE_WRITE) { + FD_CLR(fd, thread->write_fds); + } + UNLOCK(&thread->manager->lock); + + return (result); +#endif /* ifdef USE_KQUEUE */ +} + +/* + * A poke message was received, perform a proper watch/unwatch + * on a fd provided + */ +static void +wakeup_socket(isc__socketthread_t *thread, int fd, int msg) { + isc_result_t result; + int lockid = FDLOCK_ID(fd); + + /* + * This is a wakeup on a socket. If the socket is not in the + * process of being closed, start watching it for either reads + * or writes. + */ + + INSIST(fd >= 0 && fd < (int)thread->manager->maxsocks); + + if (msg == SELECT_POKE_CLOSE) { + LOCK(&thread->fdlock[lockid]); + INSIST(thread->fdstate[fd] == CLOSE_PENDING); + thread->fdstate[fd] = CLOSED; + (void)unwatch_fd(thread, fd, SELECT_POKE_READ); + (void)unwatch_fd(thread, fd, SELECT_POKE_WRITE); + (void)close(fd); + UNLOCK(&thread->fdlock[lockid]); + return; + } + + LOCK(&thread->fdlock[lockid]); + if (thread->fdstate[fd] == CLOSE_PENDING) { + /* + * We accept (and ignore) any error from unwatch_fd() as we are + * closing the socket, hoping it doesn't leave dangling state in + * the kernel. + * Note that unwatch_fd() must be called after releasing the + * fdlock; otherwise it could cause deadlock due to a lock order + * reversal. + */ + (void)unwatch_fd(thread, fd, SELECT_POKE_READ); + (void)unwatch_fd(thread, fd, SELECT_POKE_WRITE); + UNLOCK(&thread->fdlock[lockid]); + return; + } + if (thread->fdstate[fd] != MANAGED) { + UNLOCK(&thread->fdlock[lockid]); + return; + } + + /* + * Set requested bit. + */ + result = watch_fd(thread, fd, msg); + if (result != ISC_R_SUCCESS) { + /* + * XXXJT: what should we do? Ignoring the failure of watching + * a socket will make the application dysfunctional, but there + * seems to be no reasonable recovery process. + */ + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_SOCKET, ISC_LOG_ERROR, + "failed to start watching FD (%d): %s", fd, + isc_result_totext(result)); + } + UNLOCK(&thread->fdlock[lockid]); +} + +/* + * Poke the select loop when there is something for us to do. + * The write is required (by POSIX) to complete. That is, we + * will not get partial writes. + */ +static void +select_poke(isc_socketmgr_t *mgr, int threadid, int fd, int msg) { + int cc; + int buf[2]; + char strbuf[ISC_STRERRORSIZE]; + + buf[0] = fd; + buf[1] = msg; + + do { + cc = write(mgr->threads[threadid].pipe_fds[1], buf, + sizeof(buf)); +#ifdef ENOSR + /* + * Treat ENOSR as EAGAIN but loop slowly as it is + * unlikely to clear fast. + */ + if (cc < 0 && errno == ENOSR) { + sleep(1); + errno = EAGAIN; + } +#endif /* ifdef ENOSR */ + } while (cc < 0 && SOFT_ERROR(errno)); + + if (cc < 0) { + strerror_r(errno, strbuf, sizeof(strbuf)); + FATAL_ERROR(__FILE__, __LINE__, + "write() failed during watcher poke: %s", strbuf); + } + + INSIST(cc == sizeof(buf)); +} + +/* + * Read a message on the internal fd. + */ +static void +select_readmsg(isc__socketthread_t *thread, int *fd, int *msg) { + int buf[2]; + int cc; + char strbuf[ISC_STRERRORSIZE]; + + cc = read(thread->pipe_fds[0], buf, sizeof(buf)); + if (cc < 0) { + *msg = SELECT_POKE_NOTHING; + *fd = -1; /* Silence compiler. */ + if (SOFT_ERROR(errno)) { + return; + } + + strerror_r(errno, strbuf, sizeof(strbuf)); + FATAL_ERROR(__FILE__, __LINE__, + "read() failed during watcher poke: %s", strbuf); + } + INSIST(cc == sizeof(buf)); + + *fd = buf[0]; + *msg = buf[1]; +} + +/* + * Make a fd non-blocking. + */ +static isc_result_t +make_nonblock(int fd) { + int ret; + char strbuf[ISC_STRERRORSIZE]; +#ifdef USE_FIONBIO_IOCTL + int on = 1; +#else /* ifdef USE_FIONBIO_IOCTL */ + int flags; +#endif /* ifdef USE_FIONBIO_IOCTL */ + +#ifdef USE_FIONBIO_IOCTL + ret = ioctl(fd, FIONBIO, (char *)&on); +#else /* ifdef USE_FIONBIO_IOCTL */ + flags = fcntl(fd, F_GETFL, 0); + flags |= PORT_NONBLOCK; + ret = fcntl(fd, F_SETFL, flags); +#endif /* ifdef USE_FIONBIO_IOCTL */ + + if (ret == -1) { + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, +#ifdef USE_FIONBIO_IOCTL + "ioctl(%d, FIONBIO, &on): %s", fd, +#else /* ifdef USE_FIONBIO_IOCTL */ + "fcntl(%d, F_SETFL, %d): %s", fd, flags, +#endif /* ifdef USE_FIONBIO_IOCTL */ + strbuf); + + return (ISC_R_UNEXPECTED); + } + + return (ISC_R_SUCCESS); +} + +#ifdef USE_CMSG +/* + * Not all OSes support advanced CMSG macros: CMSG_LEN and CMSG_SPACE. + * In order to ensure as much portability as possible, we provide wrapper + * functions of these macros. + * Note that cmsg_space() could run slow on OSes that do not have + * CMSG_SPACE. + */ +static socklen_t +cmsg_len(socklen_t len) { +#ifdef CMSG_LEN + return (CMSG_LEN(len)); +#else /* ifdef CMSG_LEN */ + socklen_t hdrlen; + + /* + * Cast NULL so that any pointer arithmetic performed by CMSG_DATA + * is correct. + */ + hdrlen = (socklen_t)CMSG_DATA(((struct cmsghdr *)NULL)); + return (hdrlen + len); +#endif /* ifdef CMSG_LEN */ +} + +static socklen_t +cmsg_space(socklen_t len) { +#ifdef CMSG_SPACE + return (CMSG_SPACE(len)); +#else /* ifdef CMSG_SPACE */ + struct msghdr msg; + struct cmsghdr *cmsgp; + /* + * XXX: The buffer length is an ad-hoc value, but should be enough + * in a practical sense. + */ + char dummybuf[sizeof(struct cmsghdr) + 1024]; + + memset(&msg, 0, sizeof(msg)); + msg.msg_control = dummybuf; + msg.msg_controllen = sizeof(dummybuf); + + cmsgp = (struct cmsghdr *)dummybuf; + cmsgp->cmsg_len = cmsg_len(len); + + cmsgp = CMSG_NXTHDR(&msg, cmsgp); + if (cmsgp != NULL) { + return ((char *)cmsgp - (char *)msg.msg_control); + } else { + return (0); + } +#endif /* ifdef CMSG_SPACE */ +} +#endif /* USE_CMSG */ + +/* + * Process control messages received on a socket. + */ +static void +process_cmsg(isc_socket_t *sock, struct msghdr *msg, isc_socketevent_t *dev) { +#ifdef USE_CMSG + struct cmsghdr *cmsgp; + struct in6_pktinfo *pktinfop; +#ifdef SO_TIMESTAMP + void *timevalp; +#endif /* ifdef SO_TIMESTAMP */ +#endif /* ifdef USE_CMSG */ + + /* + * sock is used only when ISC_NET_BSD44MSGHDR and USE_CMSG are defined. + * msg and dev are used only when ISC_NET_BSD44MSGHDR is defined. + * They are all here, outside of the CPP tests, because it is + * more consistent with the usual ISC coding style. + */ + UNUSED(sock); + UNUSED(msg); + UNUSED(dev); + +#ifdef MSG_TRUNC + if ((msg->msg_flags & MSG_TRUNC) != 0) { + dev->attributes |= ISC_SOCKEVENTATTR_TRUNC; + } +#endif /* ifdef MSG_TRUNC */ + +#ifdef MSG_CTRUNC + if ((msg->msg_flags & MSG_CTRUNC) != 0) { + dev->attributes |= ISC_SOCKEVENTATTR_CTRUNC; + } +#endif /* ifdef MSG_CTRUNC */ + +#ifndef USE_CMSG + return; +#else /* ifndef USE_CMSG */ + if (msg->msg_controllen == 0U || msg->msg_control == NULL) { + return; + } + +#ifdef SO_TIMESTAMP + timevalp = NULL; +#endif /* ifdef SO_TIMESTAMP */ + pktinfop = NULL; + + cmsgp = CMSG_FIRSTHDR(msg); + while (cmsgp != NULL) { + socket_log(sock, NULL, TRACE, "processing cmsg %p", cmsgp); + + if (cmsgp->cmsg_level == IPPROTO_IPV6 && + cmsgp->cmsg_type == IPV6_PKTINFO) + { + pktinfop = (struct in6_pktinfo *)CMSG_DATA(cmsgp); + memmove(&dev->pktinfo, pktinfop, + sizeof(struct in6_pktinfo)); + dev->attributes |= ISC_SOCKEVENTATTR_PKTINFO; + socket_log(sock, NULL, TRACE, + "interface received on ifindex %u", + dev->pktinfo.ipi6_ifindex); + if (IN6_IS_ADDR_MULTICAST(&pktinfop->ipi6_addr)) { + dev->attributes |= ISC_SOCKEVENTATTR_MULTICAST; + } + goto next; + } + +#ifdef SO_TIMESTAMP + if (cmsgp->cmsg_level == SOL_SOCKET && + cmsgp->cmsg_type == SCM_TIMESTAMP) + { + struct timeval tv; + timevalp = CMSG_DATA(cmsgp); + memmove(&tv, timevalp, sizeof(tv)); + dev->timestamp.seconds = tv.tv_sec; + dev->timestamp.nanoseconds = tv.tv_usec * 1000; + dev->attributes |= ISC_SOCKEVENTATTR_TIMESTAMP; + goto next; + } +#endif /* ifdef SO_TIMESTAMP */ + +#ifdef IPV6_TCLASS + if (cmsgp->cmsg_level == IPPROTO_IPV6 && + cmsgp->cmsg_type == IPV6_TCLASS) + { + dev->dscp = *(int *)CMSG_DATA(cmsgp); + dev->dscp >>= 2; + dev->attributes |= ISC_SOCKEVENTATTR_DSCP; + goto next; + } +#endif /* ifdef IPV6_TCLASS */ + +#ifdef IP_TOS + if (cmsgp->cmsg_level == IPPROTO_IP && + (cmsgp->cmsg_type == IP_TOS +#ifdef IP_RECVTOS + || cmsgp->cmsg_type == IP_RECVTOS +#endif /* ifdef IP_RECVTOS */ + )) + { + dev->dscp = (int)*(unsigned char *)CMSG_DATA(cmsgp); + dev->dscp >>= 2; + dev->attributes |= ISC_SOCKEVENTATTR_DSCP; + goto next; + } +#endif /* ifdef IP_TOS */ + next: + cmsgp = CMSG_NXTHDR(msg, cmsgp); + } +#endif /* USE_CMSG */ +} + +/* + * Construct an iov array and attach it to the msghdr passed in. This is + * the SEND constructor, which will use the used region of the buffer + * (if using a buffer list) or will use the internal region (if a single + * buffer I/O is requested). + * + * Nothing can be NULL, and the done event must list at least one buffer + * on the buffer linked list for this function to be meaningful. + * + * If write_countp != NULL, *write_countp will hold the number of bytes + * this transaction can send. + */ +static void +build_msghdr_send(isc_socket_t *sock, char *cmsgbuf, isc_socketevent_t *dev, + struct msghdr *msg, struct iovec *iov, size_t *write_countp) { + unsigned int iovcount; + size_t write_count; + struct cmsghdr *cmsgp; + + memset(msg, 0, sizeof(*msg)); + + if (!sock->connected) { + msg->msg_name = (void *)&dev->address.type.sa; + msg->msg_namelen = dev->address.length; + } else { + msg->msg_name = NULL; + msg->msg_namelen = 0; + } + + write_count = dev->region.length - dev->n; + iov[0].iov_base = (void *)(dev->region.base + dev->n); + iov[0].iov_len = write_count; + iovcount = 1; + + msg->msg_iov = iov; + msg->msg_iovlen = iovcount; + msg->msg_control = NULL; + msg->msg_controllen = 0; + msg->msg_flags = 0; +#if defined(USE_CMSG) + + if ((sock->type == isc_sockettype_udp) && + ((dev->attributes & ISC_SOCKEVENTATTR_PKTINFO) != 0)) + { + struct in6_pktinfo *pktinfop; + + socket_log(sock, NULL, TRACE, "sendto pktinfo data, ifindex %u", + dev->pktinfo.ipi6_ifindex); + + msg->msg_control = (void *)cmsgbuf; + msg->msg_controllen = cmsg_space(sizeof(struct in6_pktinfo)); + INSIST(msg->msg_controllen <= SENDCMSGBUFLEN); + + cmsgp = (struct cmsghdr *)cmsgbuf; + cmsgp->cmsg_level = IPPROTO_IPV6; + cmsgp->cmsg_type = IPV6_PKTINFO; + cmsgp->cmsg_len = cmsg_len(sizeof(struct in6_pktinfo)); + pktinfop = (struct in6_pktinfo *)CMSG_DATA(cmsgp); + memmove(pktinfop, &dev->pktinfo, sizeof(struct in6_pktinfo)); + } + +#if defined(IPV6_USE_MIN_MTU) + if ((sock->type == isc_sockettype_udp) && (sock->pf == AF_INET6) && + ((dev->attributes & ISC_SOCKEVENTATTR_USEMINMTU) != 0)) + { + int use_min_mtu = 1; /* -1, 0, 1 */ + + cmsgp = (struct cmsghdr *)(cmsgbuf + msg->msg_controllen); + msg->msg_control = (void *)cmsgbuf; + msg->msg_controllen += cmsg_space(sizeof(use_min_mtu)); + INSIST(msg->msg_controllen <= SENDCMSGBUFLEN); + + cmsgp->cmsg_level = IPPROTO_IPV6; + cmsgp->cmsg_type = IPV6_USE_MIN_MTU; + cmsgp->cmsg_len = cmsg_len(sizeof(use_min_mtu)); + memmove(CMSG_DATA(cmsgp), &use_min_mtu, sizeof(use_min_mtu)); + } +#endif /* if defined(IPV6_USE_MIN_MTU) */ + + if (isc_dscp_check_value > -1) { + if (sock->type == isc_sockettype_udp) { + INSIST((int)dev->dscp == isc_dscp_check_value); + } else if (sock->type == isc_sockettype_tcp) { + INSIST((int)sock->dscp == isc_dscp_check_value); + } + } + +#if defined(IP_TOS) || (defined(IPPROTO_IPV6) && defined(IPV6_TCLASS)) + if ((sock->type == isc_sockettype_udp) && + ((dev->attributes & ISC_SOCKEVENTATTR_DSCP) != 0)) + { + int dscp = (dev->dscp << 2) & 0xff; + + INSIST(dev->dscp < 0x40); + +#ifdef IP_TOS + if (sock->pf == AF_INET && sock->pktdscp) { + cmsgp = (struct cmsghdr *)(cmsgbuf + + msg->msg_controllen); + msg->msg_control = (void *)cmsgbuf; + msg->msg_controllen += cmsg_space(sizeof(dscp)); + INSIST(msg->msg_controllen <= SENDCMSGBUFLEN); + + cmsgp->cmsg_level = IPPROTO_IP; + cmsgp->cmsg_type = IP_TOS; + cmsgp->cmsg_len = cmsg_len(sizeof(char)); + *(unsigned char *)CMSG_DATA(cmsgp) = dscp; + } else if (sock->pf == AF_INET && sock->dscp != dev->dscp) { + if (setsockopt(sock->fd, IPPROTO_IP, IP_TOS, + (void *)&dscp, sizeof(int)) < 0) + { + char strbuf[ISC_STRERRORSIZE]; + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "setsockopt(%d, IP_TOS, %.02x)" + " failed: %s", + sock->fd, dscp >> 2, strbuf); + } else { + sock->dscp = dscp; + } + } +#endif /* ifdef IP_TOS */ +#if defined(IPPROTO_IPV6) && defined(IPV6_TCLASS) + if (sock->pf == AF_INET6 && sock->pktdscp) { + cmsgp = (struct cmsghdr *)(cmsgbuf + + msg->msg_controllen); + msg->msg_control = (void *)cmsgbuf; + msg->msg_controllen += cmsg_space(sizeof(dscp)); + INSIST(msg->msg_controllen <= SENDCMSGBUFLEN); + + cmsgp->cmsg_level = IPPROTO_IPV6; + cmsgp->cmsg_type = IPV6_TCLASS; + cmsgp->cmsg_len = cmsg_len(sizeof(dscp)); + memmove(CMSG_DATA(cmsgp), &dscp, sizeof(dscp)); + } else if (sock->pf == AF_INET6 && sock->dscp != dev->dscp) { + if (setsockopt(sock->fd, IPPROTO_IPV6, IPV6_TCLASS, + (void *)&dscp, sizeof(int)) < 0) + { + char strbuf[ISC_STRERRORSIZE]; + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "setsockopt(%d, IPV6_TCLASS, " + "%.02x) failed: %s", + sock->fd, dscp >> 2, strbuf); + } else { + sock->dscp = dscp; + } + } +#endif /* if defined(IPPROTO_IPV6) && defined(IPV6_TCLASS) */ + if (msg->msg_controllen != 0 && + msg->msg_controllen < SENDCMSGBUFLEN) + { + memset(cmsgbuf + msg->msg_controllen, 0, + SENDCMSGBUFLEN - msg->msg_controllen); + } + } +#endif /* if defined(IP_TOS) || (defined(IPPROTO_IPV6) && \ + * defined(IPV6_TCLASS)) \ + * */ +#endif /* USE_CMSG */ + + if (write_countp != NULL) { + *write_countp = write_count; + } +} + +/* + * Construct an iov array and attach it to the msghdr passed in. This is + * the RECV constructor, which will use the available region of the buffer + * (if using a buffer list) or will use the internal region (if a single + * buffer I/O is requested). + * + * Nothing can be NULL, and the done event must list at least one buffer + * on the buffer linked list for this function to be meaningful. + * + * If read_countp != NULL, *read_countp will hold the number of bytes + * this transaction can receive. + */ +static void +build_msghdr_recv(isc_socket_t *sock, char *cmsgbuf, isc_socketevent_t *dev, + struct msghdr *msg, struct iovec *iov, size_t *read_countp) { + unsigned int iovcount; + size_t read_count; + + memset(msg, 0, sizeof(struct msghdr)); + + if (sock->type == isc_sockettype_udp) { + memset(&dev->address, 0, sizeof(dev->address)); + msg->msg_name = (void *)&dev->address.type.sa; + msg->msg_namelen = sizeof(dev->address.type); + } else { /* TCP */ + msg->msg_name = NULL; + msg->msg_namelen = 0; + dev->address = sock->peer_address; + } + + read_count = dev->region.length - dev->n; + iov[0].iov_base = (void *)(dev->region.base + dev->n); + iov[0].iov_len = read_count; + iovcount = 1; + + /* + * If needed, set up to receive that one extra byte. + */ +#ifdef ISC_PLATFORM_RECVOVERFLOW + if (sock->type == isc_sockettype_udp) { + INSIST(iovcount < MAXSCATTERGATHER_RECV); + iov[iovcount].iov_base = (void *)(&sock->overflow); + iov[iovcount].iov_len = 1; + iovcount++; + } +#endif /* ifdef ISC_PLATFORM_RECVOVERFLOW */ + + msg->msg_iov = iov; + msg->msg_iovlen = iovcount; + +#if defined(USE_CMSG) + msg->msg_control = cmsgbuf; + msg->msg_controllen = RECVCMSGBUFLEN; +#else /* if defined(USE_CMSG) */ + msg->msg_control = NULL; + msg->msg_controllen = 0; +#endif /* USE_CMSG */ + msg->msg_flags = 0; + + if (read_countp != NULL) { + *read_countp = read_count; + } +} + +static void +set_dev_address(const isc_sockaddr_t *address, isc_socket_t *sock, + isc_socketevent_t *dev) { + if (sock->type == isc_sockettype_udp) { + if (address != NULL) { + dev->address = *address; + } else { + dev->address = sock->peer_address; + } + } else if (sock->type == isc_sockettype_tcp) { + INSIST(address == NULL); + dev->address = sock->peer_address; + } +} + +static void +destroy_socketevent(isc_event_t *event) { + isc_socketevent_t *ev = (isc_socketevent_t *)event; + + (ev->destroy)(event); +} + +static isc_socketevent_t * +allocate_socketevent(isc_mem_t *mctx, void *sender, isc_eventtype_t eventtype, + isc_taskaction_t action, void *arg) { + isc_socketevent_t *ev; + + ev = (isc_socketevent_t *)isc_event_allocate(mctx, sender, eventtype, + action, arg, sizeof(*ev)); + + ev->result = ISC_R_UNSET; + ISC_LINK_INIT(ev, ev_link); + ev->region.base = NULL; + ev->n = 0; + ev->offset = 0; + ev->attributes = 0; + ev->destroy = ev->ev_destroy; + ev->ev_destroy = destroy_socketevent; + ev->dscp = 0; + + return (ev); +} + +#if defined(ISC_SOCKET_DEBUG) +static void +dump_msg(struct msghdr *msg) { + unsigned int i; + + printf("MSGHDR %p\n", msg); + printf("\tname %p, namelen %ld\n", msg->msg_name, + (long)msg->msg_namelen); + printf("\tiov %p, iovlen %ld\n", msg->msg_iov, (long)msg->msg_iovlen); + for (i = 0; i < (unsigned int)msg->msg_iovlen; i++) { + printf("\t\t%u\tbase %p, len %ld\n", i, + msg->msg_iov[i].iov_base, (long)msg->msg_iov[i].iov_len); + } + printf("\tcontrol %p, controllen %ld\n", msg->msg_control, + (long)msg->msg_controllen); +} +#endif /* if defined(ISC_SOCKET_DEBUG) */ + +#define DOIO_SUCCESS 0 /* i/o ok, event sent */ +#define DOIO_SOFT 1 /* i/o ok, soft error, no event sent */ +#define DOIO_HARD 2 /* i/o error, event sent */ +#define DOIO_EOF 3 /* EOF, no event sent */ + +static int +doio_recv(isc_socket_t *sock, isc_socketevent_t *dev) { + int cc; + struct iovec iov[MAXSCATTERGATHER_RECV]; + size_t read_count; + struct msghdr msghdr; + int recv_errno; + char strbuf[ISC_STRERRORSIZE]; + char cmsgbuf[RECVCMSGBUFLEN] = { 0 }; + + build_msghdr_recv(sock, cmsgbuf, dev, &msghdr, iov, &read_count); + +#if defined(ISC_SOCKET_DEBUG) + dump_msg(&msghdr); +#endif /* if defined(ISC_SOCKET_DEBUG) */ + + cc = recvmsg(sock->fd, &msghdr, 0); + recv_errno = errno; + +#if defined(ISC_SOCKET_DEBUG) + dump_msg(&msghdr); +#endif /* if defined(ISC_SOCKET_DEBUG) */ + + if (cc < 0) { + if (SOFT_ERROR(recv_errno)) { + return (DOIO_SOFT); + } + + if (isc_log_wouldlog(isc_lctx, IOEVENT_LEVEL)) { + strerror_r(recv_errno, strbuf, sizeof(strbuf)); + socket_log(sock, NULL, IOEVENT, + "doio_recv: recvmsg(%d) %d bytes, err %d/%s", + sock->fd, cc, recv_errno, strbuf); + } + +#define SOFT_OR_HARD(_system, _isc) \ + if (recv_errno == _system) { \ + if (sock->connected) { \ + dev->result = _isc; \ + inc_stats(sock->manager->stats, \ + sock->statsindex[STATID_RECVFAIL]); \ + return (DOIO_HARD); \ + } \ + return (DOIO_SOFT); \ + } +#define ALWAYS_HARD(_system, _isc) \ + if (recv_errno == _system) { \ + dev->result = _isc; \ + inc_stats(sock->manager->stats, \ + sock->statsindex[STATID_RECVFAIL]); \ + return (DOIO_HARD); \ + } + + SOFT_OR_HARD(ECONNREFUSED, ISC_R_CONNREFUSED); + SOFT_OR_HARD(ENETUNREACH, ISC_R_NETUNREACH); + SOFT_OR_HARD(EHOSTUNREACH, ISC_R_HOSTUNREACH); + SOFT_OR_HARD(EHOSTDOWN, ISC_R_HOSTDOWN); + SOFT_OR_HARD(ENOBUFS, ISC_R_NORESOURCES); + /* + * Older operating systems may still return EPROTO in some + * situations, for example when receiving ICMP/ICMPv6 errors. + * A real life scenario is when ICMPv6 returns code 5 or 6. + * These codes are introduced in RFC 4443 from March 2006, + * and the document obsoletes RFC 1885. But unfortunately not + * all operating systems have caught up with the new standard + * (in 2020) and thus a generic protocol error is returned. + */ + SOFT_OR_HARD(EPROTO, ISC_R_HOSTUNREACH); + /* Should never get this one but it was seen. */ +#ifdef ENOPROTOOPT + SOFT_OR_HARD(ENOPROTOOPT, ISC_R_HOSTUNREACH); +#endif /* ifdef ENOPROTOOPT */ + SOFT_OR_HARD(EINVAL, ISC_R_HOSTUNREACH); + +#undef SOFT_OR_HARD +#undef ALWAYS_HARD + + dev->result = isc__errno2result(recv_errno); + inc_stats(sock->manager->stats, + sock->statsindex[STATID_RECVFAIL]); + return (DOIO_HARD); + } + + /* + * On TCP and UNIX sockets, zero length reads indicate EOF, + * while on UDP sockets, zero length reads are perfectly valid, + * although strange. + */ + switch (sock->type) { + case isc_sockettype_tcp: + case isc_sockettype_unix: + if (cc == 0) { + return (DOIO_EOF); + } + break; + case isc_sockettype_udp: + case isc_sockettype_raw: + break; + default: + UNREACHABLE(); + } + + if (sock->type == isc_sockettype_udp) { + dev->address.length = msghdr.msg_namelen; + if (isc_sockaddr_getport(&dev->address) == 0) { + if (isc_log_wouldlog(isc_lctx, IOEVENT_LEVEL)) { + socket_log(sock, &dev->address, IOEVENT, + "dropping source port zero packet"); + } + return (DOIO_SOFT); + } + /* + * Simulate a firewall blocking UDP responses bigger than + * 'maxudp' bytes. + */ + if (sock->manager->maxudp != 0 && + cc > (int)sock->manager->maxudp) + { + return (DOIO_SOFT); + } + } + + socket_log(sock, &dev->address, IOEVENT, "packet received correctly"); + + /* + * Overflow bit detection. If we received MORE bytes than we should, + * this indicates an overflow situation. Set the flag in the + * dev entry and adjust how much we read by one. + */ +#ifdef ISC_PLATFORM_RECVOVERFLOW + if ((sock->type == isc_sockettype_udp) && ((size_t)cc > read_count)) { + dev->attributes |= ISC_SOCKEVENTATTR_TRUNC; + cc--; + } +#endif /* ifdef ISC_PLATFORM_RECVOVERFLOW */ + + /* + * If there are control messages attached, run through them and pull + * out the interesting bits. + */ + process_cmsg(sock, &msghdr, dev); + + /* + * update the buffers (if any) and the i/o count + */ + dev->n += cc; + + /* + * If we read less than we expected, update counters, + * and let the upper layer poke the descriptor. + */ + if (((size_t)cc != read_count) && (dev->n < dev->minimum)) { + return (DOIO_SOFT); + } + + /* + * Full reads are posted, or partials if partials are ok. + */ + dev->result = ISC_R_SUCCESS; + return (DOIO_SUCCESS); +} + +/* + * Returns: + * DOIO_SUCCESS The operation succeeded. dev->result contains + * ISC_R_SUCCESS. + * + * DOIO_HARD A hard or unexpected I/O error was encountered. + * dev->result contains the appropriate error. + * + * DOIO_SOFT A soft I/O error was encountered. No senddone + * event was sent. The operation should be retried. + * + * No other return values are possible. + */ +static int +doio_send(isc_socket_t *sock, isc_socketevent_t *dev) { + int cc; + struct iovec iov[MAXSCATTERGATHER_SEND]; + size_t write_count; + struct msghdr msghdr; + char addrbuf[ISC_SOCKADDR_FORMATSIZE]; + int attempts = 0; + int send_errno; + char strbuf[ISC_STRERRORSIZE]; + char cmsgbuf[SENDCMSGBUFLEN] = { 0 }; + + build_msghdr_send(sock, cmsgbuf, dev, &msghdr, iov, &write_count); + +resend: + if (sock->type == isc_sockettype_udp && sock->manager->maxudp != 0 && + write_count > sock->manager->maxudp) + { + cc = write_count; + } else { + cc = sendmsg(sock->fd, &msghdr, 0); + } + send_errno = errno; + + /* + * Check for error or block condition. + */ + if (cc < 0) { + if (send_errno == EINTR && ++attempts < NRETRIES) { + goto resend; + } + + if (SOFT_ERROR(send_errno)) { + if (errno == EWOULDBLOCK || errno == EAGAIN) { + dev->result = ISC_R_WOULDBLOCK; + } + return (DOIO_SOFT); + } + +#define SOFT_OR_HARD(_system, _isc) \ + if (send_errno == _system) { \ + if (sock->connected) { \ + dev->result = _isc; \ + inc_stats(sock->manager->stats, \ + sock->statsindex[STATID_SENDFAIL]); \ + return (DOIO_HARD); \ + } \ + return (DOIO_SOFT); \ + } +#define ALWAYS_HARD(_system, _isc) \ + if (send_errno == _system) { \ + dev->result = _isc; \ + inc_stats(sock->manager->stats, \ + sock->statsindex[STATID_SENDFAIL]); \ + return (DOIO_HARD); \ + } + + SOFT_OR_HARD(ECONNREFUSED, ISC_R_CONNREFUSED); + ALWAYS_HARD(EACCES, ISC_R_NOPERM); + ALWAYS_HARD(EAFNOSUPPORT, ISC_R_ADDRNOTAVAIL); + ALWAYS_HARD(EADDRNOTAVAIL, ISC_R_ADDRNOTAVAIL); + ALWAYS_HARD(EHOSTUNREACH, ISC_R_HOSTUNREACH); +#ifdef EHOSTDOWN + ALWAYS_HARD(EHOSTDOWN, ISC_R_HOSTUNREACH); +#endif /* ifdef EHOSTDOWN */ + ALWAYS_HARD(ENETUNREACH, ISC_R_NETUNREACH); + SOFT_OR_HARD(ENOBUFS, ISC_R_NORESOURCES); + ALWAYS_HARD(EPERM, ISC_R_HOSTUNREACH); + ALWAYS_HARD(EPIPE, ISC_R_NOTCONNECTED); + ALWAYS_HARD(ECONNRESET, ISC_R_CONNECTIONRESET); + +#undef SOFT_OR_HARD +#undef ALWAYS_HARD + + /* + * The other error types depend on whether or not the + * socket is UDP or TCP. If it is UDP, some errors + * that we expect to be fatal under TCP are merely + * annoying, and are really soft errors. + * + * However, these soft errors are still returned as + * a status. + */ + isc_sockaddr_format(&dev->address, addrbuf, sizeof(addrbuf)); + strerror_r(send_errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, "internal_send: %s: %s", + addrbuf, strbuf); + dev->result = isc__errno2result(send_errno); + inc_stats(sock->manager->stats, + sock->statsindex[STATID_SENDFAIL]); + return (DOIO_HARD); + } + + if (cc == 0) { + inc_stats(sock->manager->stats, + sock->statsindex[STATID_SENDFAIL]); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "doio_send: send() returned 0"); + } + + /* + * If we write less than we expected, update counters, poke. + */ + dev->n += cc; + if ((size_t)cc != write_count) { + return (DOIO_SOFT); + } + + /* + * Exactly what we wanted to write. We're done with this + * entry. Post its completion event. + */ + dev->result = ISC_R_SUCCESS; + return (DOIO_SUCCESS); +} + +/* + * Kill. + * + * Caller must ensure that the socket is not locked and no external + * references exist. + */ +static void +socketclose(isc__socketthread_t *thread, isc_socket_t *sock, int fd) { + int lockid = FDLOCK_ID(fd); + /* + * No one has this socket open, so the watcher doesn't have to be + * poked, and the socket doesn't have to be locked. + */ + LOCK(&thread->fdlock[lockid]); + thread->fds[fd] = NULL; + thread->fdstate[fd] = CLOSE_PENDING; + UNLOCK(&thread->fdlock[lockid]); + select_poke(thread->manager, thread->threadid, fd, SELECT_POKE_CLOSE); + + inc_stats(thread->manager->stats, sock->statsindex[STATID_CLOSE]); + + LOCK(&sock->lock); + if (sock->active == 1) { + dec_stats(thread->manager->stats, + sock->statsindex[STATID_ACTIVE]); + sock->active = 0; + } + UNLOCK(&sock->lock); + + /* + * update manager->maxfd here (XXX: this should be implemented more + * efficiently) + */ +#ifdef USE_SELECT + LOCK(&thread->manager->lock); + if (thread->maxfd == fd) { + int i; + + thread->maxfd = 0; + for (i = fd - 1; i >= 0; i--) { + lockid = FDLOCK_ID(i); + + LOCK(&thread->fdlock[lockid]); + if (thread->fdstate[i] == MANAGED) { + thread->maxfd = i; + UNLOCK(&thread->fdlock[lockid]); + break; + } + UNLOCK(&thread->fdlock[lockid]); + } + if (thread->maxfd < thread->pipe_fds[0]) { + thread->maxfd = thread->pipe_fds[0]; + } + } + + UNLOCK(&thread->manager->lock); +#endif /* USE_SELECT */ +} + +static void +destroy(isc_socket_t **sockp) { + int fd = 0; + isc_socket_t *sock = *sockp; + isc_socketmgr_t *manager = sock->manager; + isc__socketthread_t *thread = NULL; + + socket_log(sock, NULL, CREATION, "destroying"); + + isc_refcount_destroy(&sock->references); + + LOCK(&sock->lock); + INSIST(ISC_LIST_EMPTY(sock->connect_list)); + INSIST(ISC_LIST_EMPTY(sock->accept_list)); + INSIST(ISC_LIST_EMPTY(sock->recv_list)); + INSIST(ISC_LIST_EMPTY(sock->send_list)); + INSIST(sock->fd >= -1 && sock->fd < (int)manager->maxsocks); + + if (sock->fd >= 0) { + fd = sock->fd; + thread = &manager->threads[sock->threadid]; + sock->fd = -1; + sock->threadid = -1; + } + UNLOCK(&sock->lock); + + if (fd > 0) { + socketclose(thread, sock, fd); + } + + LOCK(&manager->lock); + + ISC_LIST_UNLINK(manager->socklist, sock, link); + + if (ISC_LIST_EMPTY(manager->socklist)) { + SIGNAL(&manager->shutdown_ok); + } + + /* can't unlock manager as its memory context is still used */ + free_socket(sockp); + + UNLOCK(&manager->lock); +} + +static isc_result_t +allocate_socket(isc_socketmgr_t *manager, isc_sockettype_t type, + isc_socket_t **socketp) { + isc_socket_t *sock; + + sock = isc_mem_get(manager->mctx, sizeof(*sock)); + + sock->magic = 0; + isc_refcount_init(&sock->references, 0); + + sock->manager = manager; + sock->type = type; + sock->fd = -1; + sock->threadid = -1; + sock->dscp = 0; /* TOS/TCLASS is zero until set. */ + sock->dupped = 0; + sock->statsindex = NULL; + sock->active = 0; + + ISC_LINK_INIT(sock, link); + + memset(sock->name, 0, sizeof(sock->name)); + sock->tag = NULL; + + /* + * Set up list of readers and writers to be initially empty. + */ + ISC_LIST_INIT(sock->recv_list); + ISC_LIST_INIT(sock->send_list); + ISC_LIST_INIT(sock->accept_list); + ISC_LIST_INIT(sock->connect_list); + + sock->listener = 0; + sock->connected = 0; + sock->connecting = 0; + sock->bound = 0; + sock->pktdscp = 0; + + /* + * Initialize the lock. + */ + isc_mutex_init(&sock->lock); + + sock->magic = SOCKET_MAGIC; + *socketp = sock; + + return (ISC_R_SUCCESS); +} + +/* + * This event requires that the various lists be empty, that the reference + * count be 1, and that the magic number is valid. The other socket bits, + * like the lock, must be initialized as well. The fd associated must be + * marked as closed, by setting it to -1 on close, or this routine will + * also close the socket. + */ +static void +free_socket(isc_socket_t **socketp) { + isc_socket_t *sock = *socketp; + *socketp = NULL; + + INSIST(VALID_SOCKET(sock)); + isc_refcount_destroy(&sock->references); + LOCK(&sock->lock); + INSIST(!sock->connecting); + INSIST(ISC_LIST_EMPTY(sock->recv_list)); + INSIST(ISC_LIST_EMPTY(sock->send_list)); + INSIST(ISC_LIST_EMPTY(sock->accept_list)); + INSIST(ISC_LIST_EMPTY(sock->connect_list)); + INSIST(!ISC_LINK_LINKED(sock, link)); + UNLOCK(&sock->lock); + + sock->magic = 0; + + isc_mutex_destroy(&sock->lock); + + isc_mem_put(sock->manager->mctx, sock, sizeof(*sock)); +} + +#if defined(SET_RCVBUF) +static isc_once_t rcvbuf_once = ISC_ONCE_INIT; +static int rcvbuf = ISC_RECV_BUFFER_SIZE; + +static void +set_rcvbuf(void) { + int fd; + int max = rcvbuf, min; + socklen_t len; + + fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (fd == -1) { + switch (errno) { + case EPROTONOSUPPORT: + case EPFNOSUPPORT: + case EAFNOSUPPORT: + /* + * Linux 2.2 (and maybe others) return EINVAL instead of + * EAFNOSUPPORT. + */ + case EINVAL: + fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); + break; + } + } + if (fd == -1) { + return; + } + + len = sizeof(min); + if (getsockopt(fd, SOL_SOCKET, SO_RCVBUF, (void *)&min, &len) == 0 && + min < rcvbuf) + { + again: + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (void *)&rcvbuf, + sizeof(rcvbuf)) == -1) + { + if (errno == ENOBUFS && rcvbuf > min) { + max = rcvbuf - 1; + rcvbuf = (rcvbuf + min) / 2; + goto again; + } else { + rcvbuf = min; + goto cleanup; + } + } else { + min = rcvbuf; + } + if (min != max) { + rcvbuf = max; + goto again; + } + } +cleanup: + close(fd); +} +#endif /* ifdef SO_RCVBUF */ + +#if defined(SET_SNDBUF) +static isc_once_t sndbuf_once = ISC_ONCE_INIT; +static int sndbuf = ISC_SEND_BUFFER_SIZE; + +static void +set_sndbuf(void) { + int fd; + int max = sndbuf, min; + socklen_t len; + + fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (fd == -1) { + switch (errno) { + case EPROTONOSUPPORT: + case EPFNOSUPPORT: + case EAFNOSUPPORT: + /* + * Linux 2.2 (and maybe others) return EINVAL instead of + * EAFNOSUPPORT. + */ + case EINVAL: + fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); + break; + } + } + if (fd == -1) { + return; + } + + len = sizeof(min); + if (getsockopt(fd, SOL_SOCKET, SO_SNDBUF, (void *)&min, &len) == 0 && + min < sndbuf) + { + again: + if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (void *)&sndbuf, + sizeof(sndbuf)) == -1) + { + if (errno == ENOBUFS && sndbuf > min) { + max = sndbuf - 1; + sndbuf = (sndbuf + min) / 2; + goto again; + } else { + sndbuf = min; + goto cleanup; + } + } else { + min = sndbuf; + } + if (min != max) { + sndbuf = max; + goto again; + } + } +cleanup: + close(fd); +} +#endif /* ifdef SO_SNDBUF */ + +static void +use_min_mtu(isc_socket_t *sock) { +#if !defined(IPV6_USE_MIN_MTU) && !defined(IPV6_MTU) + UNUSED(sock); +#endif /* if !defined(IPV6_USE_MIN_MTU) && !defined(IPV6_MTU) */ +#ifdef IPV6_USE_MIN_MTU + /* use minimum MTU */ + if (sock->pf == AF_INET6) { + int on = 1; + (void)setsockopt(sock->fd, IPPROTO_IPV6, IPV6_USE_MIN_MTU, + (void *)&on, sizeof(on)); + } +#endif /* ifdef IPV6_USE_MIN_MTU */ +#if defined(IPV6_MTU) + /* + * Use minimum MTU on IPv6 sockets. + */ + if (sock->pf == AF_INET6) { + int mtu = 1280; + (void)setsockopt(sock->fd, IPPROTO_IPV6, IPV6_MTU, &mtu, + sizeof(mtu)); + } +#endif /* if defined(IPV6_MTU) */ +} + +static void +set_tcp_maxseg(isc_socket_t *sock, int size) { +#ifdef TCP_MAXSEG + if (sock->type == isc_sockettype_tcp) { + (void)setsockopt(sock->fd, IPPROTO_TCP, TCP_MAXSEG, + (void *)&size, sizeof(size)); + } +#endif /* ifdef TCP_MAXSEG */ +} + +static void +set_ip_disable_pmtud(isc_socket_t *sock) { + /* + * Disable Path MTU Discover on IP packets + */ + if (sock->pf == AF_INET6) { +#if defined(IPV6_DONTFRAG) + (void)setsockopt(sock->fd, IPPROTO_IPV6, IPV6_DONTFRAG, + &(int){ 0 }, sizeof(int)); +#endif +#if defined(IPV6_MTU_DISCOVER) && defined(IP_PMTUDISC_OMIT) + (void)setsockopt(sock->fd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, + &(int){ IP_PMTUDISC_OMIT }, sizeof(int)); +#endif + } else if (sock->pf == AF_INET) { +#if defined(IP_DONTFRAG) + (void)setsockopt(sock->fd, IPPROTO_IP, IP_DONTFRAG, &(int){ 0 }, + sizeof(int)); +#endif +#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_OMIT) + (void)setsockopt(sock->fd, IPPROTO_IP, IP_MTU_DISCOVER, + &(int){ IP_PMTUDISC_OMIT }, sizeof(int)); +#endif + } +} + +static isc_result_t +opensocket(isc_socketmgr_t *manager, isc_socket_t *sock, + isc_socket_t *dup_socket) { + isc_result_t result; + char strbuf[ISC_STRERRORSIZE]; + const char *err = "socket"; + int tries = 0; +#if defined(USE_CMSG) || defined(SO_NOSIGPIPE) + int on = 1; +#endif /* if defined(USE_CMSG) || defined(SO_NOSIGPIPE) */ +#if defined(SET_RCVBUF) || defined(SET_SNDBUF) + socklen_t optlen; + int size = 0; +#endif + +again: + if (dup_socket == NULL) { + switch (sock->type) { + case isc_sockettype_udp: + sock->fd = socket(sock->pf, SOCK_DGRAM, IPPROTO_UDP); + break; + case isc_sockettype_tcp: + sock->fd = socket(sock->pf, SOCK_STREAM, IPPROTO_TCP); + break; + case isc_sockettype_unix: + sock->fd = socket(sock->pf, SOCK_STREAM, 0); + break; + case isc_sockettype_raw: + errno = EPFNOSUPPORT; + /* + * PF_ROUTE is a alias for PF_NETLINK on linux. + */ +#if defined(PF_ROUTE) + if (sock->fd == -1 && sock->pf == PF_ROUTE) { +#ifdef NETLINK_ROUTE + sock->fd = socket(sock->pf, SOCK_RAW, + NETLINK_ROUTE); +#else /* ifdef NETLINK_ROUTE */ + sock->fd = socket(sock->pf, SOCK_RAW, 0); +#endif /* ifdef NETLINK_ROUTE */ + if (sock->fd != -1) { +#ifdef NETLINK_ROUTE + struct sockaddr_nl sa; + int n; + + /* + * Do an implicit bind. + */ + memset(&sa, 0, sizeof(sa)); + sa.nl_family = AF_NETLINK; + sa.nl_groups = RTMGRP_IPV4_IFADDR | + RTMGRP_IPV6_IFADDR; + n = bind(sock->fd, + (struct sockaddr *)&sa, + sizeof(sa)); + if (n < 0) { + close(sock->fd); + sock->fd = -1; + } +#endif /* ifdef NETLINK_ROUTE */ + sock->bound = 1; + } + } +#endif /* if defined(PF_ROUTE) */ + break; + } + } else { + sock->fd = dup(dup_socket->fd); + sock->dupped = 1; + sock->bound = dup_socket->bound; + } + if (sock->fd == -1 && errno == EINTR && tries++ < 42) { + goto again; + } + +#ifdef F_DUPFD + /* + * Leave a space for stdio and TCP to work in. + */ + if (manager->reserved != 0 && sock->type == isc_sockettype_udp && + sock->fd >= 0 && sock->fd < manager->reserved) + { + int newfd, tmp; + newfd = fcntl(sock->fd, F_DUPFD, manager->reserved); + tmp = errno; + (void)close(sock->fd); + errno = tmp; + sock->fd = newfd; + err = "isc_socket_create: fcntl/reserved"; + } else if (sock->fd >= 0 && sock->fd < 20) { + int newfd, tmp; + newfd = fcntl(sock->fd, F_DUPFD, 20); + tmp = errno; + (void)close(sock->fd); + errno = tmp; + sock->fd = newfd; + err = "isc_socket_create: fcntl"; + } +#endif /* ifdef F_DUPFD */ + + if (sock->fd >= (int)manager->maxsocks) { + (void)close(sock->fd); + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_SOCKET, ISC_LOG_ERROR, + "socket: file descriptor exceeds limit (%d/%u)", + sock->fd, manager->maxsocks); + inc_stats(manager->stats, sock->statsindex[STATID_OPENFAIL]); + return (ISC_R_NORESOURCES); + } + + if (sock->fd < 0) { + switch (errno) { + case EMFILE: + case ENFILE: + strerror_r(errno, strbuf, sizeof(strbuf)); + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_SOCKET, ISC_LOG_ERROR, + "%s: %s", err, strbuf); + FALLTHROUGH; + case ENOBUFS: + inc_stats(manager->stats, + sock->statsindex[STATID_OPENFAIL]); + return (ISC_R_NORESOURCES); + + case EPROTONOSUPPORT: + case EPFNOSUPPORT: + case EAFNOSUPPORT: + /* + * Linux 2.2 (and maybe others) return EINVAL instead of + * EAFNOSUPPORT. + */ + case EINVAL: + inc_stats(manager->stats, + sock->statsindex[STATID_OPENFAIL]); + return (ISC_R_FAMILYNOSUPPORT); + + default: + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, "%s() failed: %s", + err, strbuf); + inc_stats(manager->stats, + sock->statsindex[STATID_OPENFAIL]); + return (ISC_R_UNEXPECTED); + } + } + + if (dup_socket != NULL) { + goto setup_done; + } + + result = make_nonblock(sock->fd); + if (result != ISC_R_SUCCESS) { + (void)close(sock->fd); + inc_stats(manager->stats, sock->statsindex[STATID_OPENFAIL]); + return (result); + } + +#ifdef SO_NOSIGPIPE + if (setsockopt(sock->fd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&on, + sizeof(on)) < 0) + { + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "setsockopt(%d, SO_NOSIGPIPE) failed: %s", + sock->fd, strbuf); + /* Press on... */ + } +#endif /* ifdef SO_NOSIGPIPE */ + + /* + * Use minimum mtu if possible. + */ + if (sock->type == isc_sockettype_tcp && sock->pf == AF_INET6) { + use_min_mtu(sock); + set_tcp_maxseg(sock, 1280 - 20 - 40); /* 1280 - TCP - IPV6 */ + } + +#if defined(USE_CMSG) || defined(SET_RCVBUF) || defined(SET_SNDBUF) + if (sock->type == isc_sockettype_udp) { +#if defined(USE_CMSG) +#if defined(SO_TIMESTAMP) + if (setsockopt(sock->fd, SOL_SOCKET, SO_TIMESTAMP, (void *)&on, + sizeof(on)) < 0 && + errno != ENOPROTOOPT) + { + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "setsockopt(%d, SO_TIMESTAMP) failed: " + "%s", + sock->fd, strbuf); + /* Press on... */ + } +#endif /* SO_TIMESTAMP */ + +#ifdef IPV6_RECVPKTINFO + /* RFC 3542 */ + if ((sock->pf == AF_INET6) && + (setsockopt(sock->fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, + (void *)&on, sizeof(on)) < 0)) + { + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "setsockopt(%d, IPV6_RECVPKTINFO) " + "failed: %s", + sock->fd, strbuf); + } +#else /* ifdef IPV6_RECVPKTINFO */ + /* RFC 2292 */ + if ((sock->pf == AF_INET6) && + (setsockopt(sock->fd, IPPROTO_IPV6, IPV6_PKTINFO, + (void *)&on, sizeof(on)) < 0)) + { + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "setsockopt(%d, IPV6_PKTINFO) failed: " + "%s", + sock->fd, strbuf); + } +#endif /* IPV6_RECVPKTINFO */ +#endif /* defined(USE_CMSG) */ + +#if defined(SET_RCVBUF) + optlen = sizeof(size); + if (getsockopt(sock->fd, SOL_SOCKET, SO_RCVBUF, (void *)&size, + &optlen) == 0 && + size < rcvbuf) + { + RUNTIME_CHECK(isc_once_do(&rcvbuf_once, set_rcvbuf) == + ISC_R_SUCCESS); + if (setsockopt(sock->fd, SOL_SOCKET, SO_RCVBUF, + (void *)&rcvbuf, sizeof(rcvbuf)) == -1) + { + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "setsockopt(%d, SO_RCVBUF, " + "%d) failed: %s", + sock->fd, rcvbuf, strbuf); + } + } +#endif /* if defined(SET_RCVBUF) */ + +#if defined(SET_SNDBUF) + optlen = sizeof(size); + if (getsockopt(sock->fd, SOL_SOCKET, SO_SNDBUF, (void *)&size, + &optlen) == 0 && + size < sndbuf) + { + RUNTIME_CHECK(isc_once_do(&sndbuf_once, set_sndbuf) == + ISC_R_SUCCESS); + if (setsockopt(sock->fd, SOL_SOCKET, SO_SNDBUF, + (void *)&sndbuf, sizeof(sndbuf)) == -1) + { + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "setsockopt(%d, SO_SNDBUF, " + "%d) failed: %s", + sock->fd, sndbuf, strbuf); + } + } +#endif /* if defined(SO_SNDBUF) */ + } +#ifdef IPV6_RECVTCLASS + if ((sock->pf == AF_INET6) && + (setsockopt(sock->fd, IPPROTO_IPV6, IPV6_RECVTCLASS, (void *)&on, + sizeof(on)) < 0)) + { + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "setsockopt(%d, IPV6_RECVTCLASS) " + "failed: %s", + sock->fd, strbuf); + } +#endif /* ifdef IPV6_RECVTCLASS */ +#ifdef IP_RECVTOS + if ((sock->pf == AF_INET) && + (setsockopt(sock->fd, IPPROTO_IP, IP_RECVTOS, (void *)&on, + sizeof(on)) < 0)) + { + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "setsockopt(%d, IP_RECVTOS) " + "failed: %s", + sock->fd, strbuf); + } +#endif /* ifdef IP_RECVTOS */ +#endif /* defined(USE_CMSG) || defined(SET_RCVBUF) || defined(SET_SNDBUF) */ + + set_ip_disable_pmtud(sock); + +setup_done: + inc_stats(manager->stats, sock->statsindex[STATID_OPEN]); + if (sock->active == 0) { + inc_stats(manager->stats, sock->statsindex[STATID_ACTIVE]); + sock->active = 1; + } + + return (ISC_R_SUCCESS); +} + +/* + * Create a 'type' socket or duplicate an existing socket, managed + * by 'manager'. Events will be posted to 'task' and when dispatched + * 'action' will be called with 'arg' as the arg value. The new + * socket is returned in 'socketp'. + */ +static isc_result_t +socket_create(isc_socketmgr_t *manager, int pf, isc_sockettype_t type, + isc_socket_t **socketp, isc_socket_t *dup_socket) { + isc_socket_t *sock = NULL; + isc__socketthread_t *thread; + isc_result_t result; + int lockid; + + REQUIRE(VALID_MANAGER(manager)); + REQUIRE(socketp != NULL && *socketp == NULL); + + result = allocate_socket(manager, type, &sock); + if (result != ISC_R_SUCCESS) { + return (result); + } + + switch (sock->type) { + case isc_sockettype_udp: + sock->statsindex = (pf == AF_INET) ? udp4statsindex + : udp6statsindex; +#define DCSPPKT(pf) ((pf == AF_INET) ? ISC_NET_DSCPPKTV4 : ISC_NET_DSCPPKTV6) + sock->pktdscp = (isc_net_probedscp() & DCSPPKT(pf)) != 0; + break; + case isc_sockettype_tcp: + sock->statsindex = (pf == AF_INET) ? tcp4statsindex + : tcp6statsindex; + break; + case isc_sockettype_unix: + sock->statsindex = unixstatsindex; + break; + case isc_sockettype_raw: + sock->statsindex = rawstatsindex; + break; + default: + UNREACHABLE(); + } + + sock->pf = pf; + + result = opensocket(manager, sock, dup_socket); + if (result != ISC_R_SUCCESS) { + free_socket(&sock); + return (result); + } + + if (sock->fd == -1) { + abort(); + } + sock->threadid = gen_threadid(sock); + isc_refcount_increment0(&sock->references); + thread = &manager->threads[sock->threadid]; + *socketp = sock; + + /* + * Note we don't have to lock the socket like we normally would because + * there are no external references to it yet. + */ + + lockid = FDLOCK_ID(sock->fd); + LOCK(&thread->fdlock[lockid]); + thread->fds[sock->fd] = sock; + thread->fdstate[sock->fd] = MANAGED; +#if defined(USE_EPOLL) + thread->epoll_events[sock->fd] = 0; +#endif /* if defined(USE_EPOLL) */ +#ifdef USE_DEVPOLL + INSIST(thread->fdpollinfo[sock->fd].want_read == 0 && + thread->fdpollinfo[sock->fd].want_write == 0); +#endif /* ifdef USE_DEVPOLL */ + UNLOCK(&thread->fdlock[lockid]); + + LOCK(&manager->lock); + ISC_LIST_APPEND(manager->socklist, sock, link); +#ifdef USE_SELECT + if (thread->maxfd < sock->fd) { + thread->maxfd = sock->fd; + } +#endif /* ifdef USE_SELECT */ + UNLOCK(&manager->lock); + + socket_log(sock, NULL, CREATION, + dup_socket != NULL ? "dupped" : "created"); + + return (ISC_R_SUCCESS); +} + +/*% + * Create a new 'type' socket managed by 'manager'. Events + * will be posted to 'task' and when dispatched 'action' will be + * called with 'arg' as the arg value. The new socket is returned + * in 'socketp'. + */ +isc_result_t +isc_socket_create(isc_socketmgr_t *manager0, int pf, isc_sockettype_t type, + isc_socket_t **socketp) { + return (socket_create(manager0, pf, type, socketp, NULL)); +} + +/*% + * Duplicate an existing socket. The new socket is returned + * in 'socketp'. + */ +isc_result_t +isc_socket_dup(isc_socket_t *sock, isc_socket_t **socketp) { + REQUIRE(VALID_SOCKET(sock)); + REQUIRE(socketp != NULL && *socketp == NULL); + + return (socket_create(sock->manager, sock->pf, sock->type, socketp, + sock)); +} + +isc_result_t +isc_socket_open(isc_socket_t *sock) { + isc_result_t result; + isc__socketthread_t *thread; + + REQUIRE(VALID_SOCKET(sock)); + + LOCK(&sock->lock); + + REQUIRE(isc_refcount_current(&sock->references) >= 1); + REQUIRE(sock->fd == -1); + REQUIRE(sock->threadid == -1); + + result = opensocket(sock->manager, sock, NULL); + + UNLOCK(&sock->lock); + + if (result != ISC_R_SUCCESS) { + sock->fd = -1; + } else { + sock->threadid = gen_threadid(sock); + thread = &sock->manager->threads[sock->threadid]; + int lockid = FDLOCK_ID(sock->fd); + + LOCK(&thread->fdlock[lockid]); + thread->fds[sock->fd] = sock; + thread->fdstate[sock->fd] = MANAGED; +#if defined(USE_EPOLL) + thread->epoll_events[sock->fd] = 0; +#endif /* if defined(USE_EPOLL) */ +#ifdef USE_DEVPOLL + INSIST(thread->fdpollinfo[sock->fd].want_read == 0 && + thread->fdpollinfo[sock->fd].want_write == 0); +#endif /* ifdef USE_DEVPOLL */ + UNLOCK(&thread->fdlock[lockid]); + +#ifdef USE_SELECT + LOCK(&sock->manager->lock); + if (thread->maxfd < sock->fd) { + thread->maxfd = sock->fd; + } + UNLOCK(&sock->manager->lock); +#endif /* ifdef USE_SELECT */ + } + + return (result); +} + +/* + * Attach to a socket. Caller must explicitly detach when it is done. + */ +void +isc_socket_attach(isc_socket_t *sock, isc_socket_t **socketp) { + REQUIRE(VALID_SOCKET(sock)); + REQUIRE(socketp != NULL && *socketp == NULL); + + int old_refs = isc_refcount_increment(&sock->references); + REQUIRE(old_refs > 0); + + *socketp = sock; +} + +/* + * Dereference a socket. If this is the last reference to it, clean things + * up by destroying the socket. + */ +void +isc_socket_detach(isc_socket_t **socketp) { + isc_socket_t *sock; + + REQUIRE(socketp != NULL); + sock = *socketp; + REQUIRE(VALID_SOCKET(sock)); + if (isc_refcount_decrement(&sock->references) == 1) { + destroy(&sock); + } + + *socketp = NULL; +} + +isc_result_t +isc_socket_close(isc_socket_t *sock) { + int fd; + isc_socketmgr_t *manager; + isc__socketthread_t *thread; + fflush(stdout); + REQUIRE(VALID_SOCKET(sock)); + + LOCK(&sock->lock); + + REQUIRE(sock->fd >= 0 && sock->fd < (int)sock->manager->maxsocks); + + INSIST(!sock->connecting); + INSIST(ISC_LIST_EMPTY(sock->recv_list)); + INSIST(ISC_LIST_EMPTY(sock->send_list)); + INSIST(ISC_LIST_EMPTY(sock->accept_list)); + INSIST(ISC_LIST_EMPTY(sock->connect_list)); + + manager = sock->manager; + thread = &manager->threads[sock->threadid]; + fd = sock->fd; + sock->fd = -1; + sock->threadid = -1; + + sock->dupped = 0; + memset(sock->name, 0, sizeof(sock->name)); + sock->tag = NULL; + sock->listener = 0; + sock->connected = 0; + sock->connecting = 0; + sock->bound = 0; + isc_sockaddr_any(&sock->peer_address); + + UNLOCK(&sock->lock); + + socketclose(thread, sock, fd); + + return (ISC_R_SUCCESS); +} + +/* + * Dequeue an item off the given socket's read queue, set the result code + * in the done event to the one provided, and send it to the task it was + * destined for. + * + * If the event to be sent is on a list, remove it before sending. If + * asked to, send and detach from the socket as well. + * + * Caller must have the socket locked if the event is attached to the socket. + */ +static void +send_recvdone_event(isc_socket_t *sock, isc_socketevent_t **dev) { + isc_task_t *task; + + task = (*dev)->ev_sender; + + (*dev)->ev_sender = sock; + + if (ISC_LINK_LINKED(*dev, ev_link)) { + ISC_LIST_DEQUEUE(sock->recv_list, *dev, ev_link); + } + + if (((*dev)->attributes & ISC_SOCKEVENTATTR_ATTACHED) != 0) { + isc_task_sendtoanddetach(&task, (isc_event_t **)dev, + sock->threadid); + } else { + isc_task_sendto(task, (isc_event_t **)dev, sock->threadid); + } +} + +/* + * See comments for send_recvdone_event() above. + * + * Caller must have the socket locked if the event is attached to the socket. + */ +static void +send_senddone_event(isc_socket_t *sock, isc_socketevent_t **dev) { + isc_task_t *task; + + INSIST(dev != NULL && *dev != NULL); + + task = (*dev)->ev_sender; + (*dev)->ev_sender = sock; + + if (ISC_LINK_LINKED(*dev, ev_link)) { + ISC_LIST_DEQUEUE(sock->send_list, *dev, ev_link); + } + + if (((*dev)->attributes & ISC_SOCKEVENTATTR_ATTACHED) != 0) { + isc_task_sendtoanddetach(&task, (isc_event_t **)dev, + sock->threadid); + } else { + isc_task_sendto(task, (isc_event_t **)dev, sock->threadid); + } +} + +/* + * See comments for send_recvdone_event() above. + * + * Caller must have the socket locked if the event is attached to the socket. + */ +static void +send_connectdone_event(isc_socket_t *sock, isc_socket_connev_t **dev) { + isc_task_t *task; + + INSIST(dev != NULL && *dev != NULL); + + task = (*dev)->ev_sender; + (*dev)->ev_sender = sock; + + if (ISC_LINK_LINKED(*dev, ev_link)) { + ISC_LIST_DEQUEUE(sock->connect_list, *dev, ev_link); + } + + isc_task_sendtoanddetach(&task, (isc_event_t **)dev, sock->threadid); +} + +/* + * Call accept() on a socket, to get the new file descriptor. The listen + * socket is used as a prototype to create a new isc_socket_t. The new + * socket has one outstanding reference. The task receiving the event + * will be detached from just after the event is delivered. + * + * On entry to this function, the event delivered is the internal + * readable event, and the first item on the accept_list should be + * the done event we want to send. If the list is empty, this is a no-op, + * so just unlock and return. + */ +static void +internal_accept(isc_socket_t *sock) { + isc_socketmgr_t *manager; + isc__socketthread_t *thread, *nthread; + isc_socket_newconnev_t *dev; + isc_task_t *task; + socklen_t addrlen; + int fd; + isc_result_t result = ISC_R_SUCCESS; + char strbuf[ISC_STRERRORSIZE]; + const char *err = "accept"; + + INSIST(VALID_SOCKET(sock)); + REQUIRE(sock->fd >= 0); + + socket_log(sock, NULL, TRACE, "internal_accept called, locked socket"); + + manager = sock->manager; + INSIST(VALID_MANAGER(manager)); + thread = &manager->threads[sock->threadid]; + + INSIST(sock->listener); + + /* + * Get the first item off the accept list. + * If it is empty, unlock the socket and return. + */ + dev = ISC_LIST_HEAD(sock->accept_list); + if (dev == NULL) { + unwatch_fd(thread, sock->fd, SELECT_POKE_ACCEPT); + UNLOCK(&sock->lock); + return; + } + + /* + * Try to accept the new connection. If the accept fails with + * EAGAIN or EINTR, simply poke the watcher to watch this socket + * again. Also ignore ECONNRESET, which has been reported to + * be spuriously returned on Linux 2.2.19 although it is not + * a documented error for accept(). ECONNABORTED has been + * reported for Solaris 8. The rest are thrown in not because + * we have seen them but because they are ignored by other + * daemons such as BIND 8 and Apache. + */ + + addrlen = sizeof(NEWCONNSOCK(dev)->peer_address.type); + memset(&NEWCONNSOCK(dev)->peer_address.type, 0, addrlen); + fd = accept(sock->fd, &NEWCONNSOCK(dev)->peer_address.type.sa, + (void *)&addrlen); + +#ifdef F_DUPFD + /* + * Leave a space for stdio to work in. + */ + if (fd >= 0 && fd < 20) { + int newfd, tmp; + newfd = fcntl(fd, F_DUPFD, 20); + tmp = errno; + (void)close(fd); + errno = tmp; + fd = newfd; + err = "accept/fcntl"; + } +#endif /* ifdef F_DUPFD */ + + if (fd < 0) { + if (SOFT_ERROR(errno)) { + goto soft_error; + } + switch (errno) { + case ENFILE: + case EMFILE: + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_SOCKET, ISC_LOG_ERROR, + "%s: too many open file descriptors", + err); + goto soft_error; + + case ENOBUFS: + case ENOMEM: + case ECONNRESET: + case ECONNABORTED: + case EHOSTUNREACH: + case EHOSTDOWN: + case ENETUNREACH: + case ENETDOWN: + case ECONNREFUSED: +#ifdef EPROTO + case EPROTO: +#endif /* ifdef EPROTO */ +#ifdef ENONET + case ENONET: +#endif /* ifdef ENONET */ + goto soft_error; + default: + break; + } + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "internal_accept: %s() failed: %s", err, + strbuf); + fd = -1; + result = ISC_R_UNEXPECTED; + } else { + if (addrlen == 0U) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "internal_accept(): " + "accept() failed to return " + "remote address"); + + (void)close(fd); + goto soft_error; + } else if (NEWCONNSOCK(dev)->peer_address.type.sa.sa_family != + sock->pf) + { + UNEXPECTED_ERROR( + __FILE__, __LINE__, + "internal_accept(): " + "accept() returned peer address " + "family %u (expected %u)", + NEWCONNSOCK(dev)->peer_address.type.sa.sa_family, + sock->pf); + (void)close(fd); + goto soft_error; + } else if (fd >= (int)manager->maxsocks) { + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_SOCKET, ISC_LOG_ERROR, + "accept: file descriptor exceeds limit " + "(%d/%u)", + fd, manager->maxsocks); + (void)close(fd); + goto soft_error; + } + } + + if (fd != -1) { + NEWCONNSOCK(dev)->peer_address.length = addrlen; + NEWCONNSOCK(dev)->pf = sock->pf; + } + + /* + * Pull off the done event. + */ + ISC_LIST_UNLINK(sock->accept_list, dev, ev_link); + + /* + * Poke watcher if there are more pending accepts. + */ + if (ISC_LIST_EMPTY(sock->accept_list)) { + unwatch_fd(thread, sock->fd, SELECT_POKE_ACCEPT); + } + + if (fd != -1) { + result = make_nonblock(fd); + if (result != ISC_R_SUCCESS) { + (void)close(fd); + fd = -1; + } + } + + /* + * We need to unlock sock->lock now to be able to lock manager->lock + * without risking a deadlock with xmlstats. + */ + UNLOCK(&sock->lock); + + /* + * -1 means the new socket didn't happen. + */ + if (fd != -1) { + int lockid = FDLOCK_ID(fd); + + NEWCONNSOCK(dev)->fd = fd; + NEWCONNSOCK(dev)->threadid = gen_threadid(NEWCONNSOCK(dev)); + NEWCONNSOCK(dev)->bound = 1; + NEWCONNSOCK(dev)->connected = 1; + nthread = &manager->threads[NEWCONNSOCK(dev)->threadid]; + + /* + * We already hold a lock on one fdlock in accepting thread, + * we need to make sure that we don't double lock. + */ + bool same_bucket = (sock->threadid == + NEWCONNSOCK(dev)->threadid) && + (FDLOCK_ID(sock->fd) == lockid); + + /* + * Use minimum mtu if possible. + */ + use_min_mtu(NEWCONNSOCK(dev)); + set_tcp_maxseg(NEWCONNSOCK(dev), 1280 - 20 - 40); + + /* + * Ensure DSCP settings are inherited across accept. + */ + setdscp(NEWCONNSOCK(dev), sock->dscp); + + /* + * Save away the remote address + */ + dev->address = NEWCONNSOCK(dev)->peer_address; + + if (NEWCONNSOCK(dev)->active == 0) { + inc_stats(manager->stats, + NEWCONNSOCK(dev)->statsindex[STATID_ACTIVE]); + NEWCONNSOCK(dev)->active = 1; + } + + if (!same_bucket) { + LOCK(&nthread->fdlock[lockid]); + } + nthread->fds[fd] = NEWCONNSOCK(dev); + nthread->fdstate[fd] = MANAGED; +#if defined(USE_EPOLL) + nthread->epoll_events[fd] = 0; +#endif /* if defined(USE_EPOLL) */ + if (!same_bucket) { + UNLOCK(&nthread->fdlock[lockid]); + } + + LOCK(&manager->lock); + +#ifdef USE_SELECT + if (nthread->maxfd < fd) { + nthread->maxfd = fd; + } +#endif /* ifdef USE_SELECT */ + + socket_log(sock, &NEWCONNSOCK(dev)->peer_address, CREATION, + "accepted connection, new socket %p", + dev->newsocket); + + ISC_LIST_APPEND(manager->socklist, NEWCONNSOCK(dev), link); + + UNLOCK(&manager->lock); + + inc_stats(manager->stats, sock->statsindex[STATID_ACCEPT]); + } else { + inc_stats(manager->stats, sock->statsindex[STATID_ACCEPTFAIL]); + isc_refcount_decrementz(&NEWCONNSOCK(dev)->references); + free_socket((isc_socket_t **)&dev->newsocket); + } + + /* + * Fill in the done event details and send it off. + */ + dev->result = result; + task = dev->ev_sender; + dev->ev_sender = sock; + + isc_task_sendtoanddetach(&task, ISC_EVENT_PTR(&dev), sock->threadid); + return; + +soft_error: + watch_fd(thread, sock->fd, SELECT_POKE_ACCEPT); + UNLOCK(&sock->lock); + + inc_stats(manager->stats, sock->statsindex[STATID_ACCEPTFAIL]); + return; +} + +static void +internal_recv(isc_socket_t *sock) { + isc_socketevent_t *dev; + + INSIST(VALID_SOCKET(sock)); + REQUIRE(sock->fd >= 0); + + dev = ISC_LIST_HEAD(sock->recv_list); + if (dev == NULL) { + goto finish; + } + + socket_log(sock, NULL, IOEVENT, "internal_recv: event %p -> task %p", + dev, dev->ev_sender); + + /* + * Try to do as much I/O as possible on this socket. There are no + * limits here, currently. + */ + while (dev != NULL) { + switch (doio_recv(sock, dev)) { + case DOIO_SOFT: + goto finish; + + case DOIO_EOF: + /* + * read of 0 means the remote end was closed. + * Run through the event queue and dispatch all + * the events with an EOF result code. + */ + do { + dev->result = ISC_R_EOF; + send_recvdone_event(sock, &dev); + dev = ISC_LIST_HEAD(sock->recv_list); + } while (dev != NULL); + goto finish; + + case DOIO_SUCCESS: + case DOIO_HARD: + send_recvdone_event(sock, &dev); + break; + } + + dev = ISC_LIST_HEAD(sock->recv_list); + } + +finish: + if (ISC_LIST_EMPTY(sock->recv_list)) { + unwatch_fd(&sock->manager->threads[sock->threadid], sock->fd, + SELECT_POKE_READ); + } +} + +static void +internal_send(isc_socket_t *sock) { + isc_socketevent_t *dev; + + INSIST(VALID_SOCKET(sock)); + REQUIRE(sock->fd >= 0); + + dev = ISC_LIST_HEAD(sock->send_list); + if (dev == NULL) { + goto finish; + } + socket_log(sock, NULL, EVENT, "internal_send: event %p -> task %p", dev, + dev->ev_sender); + + /* + * Try to do as much I/O as possible on this socket. There are no + * limits here, currently. + */ + while (dev != NULL) { + switch (doio_send(sock, dev)) { + case DOIO_SOFT: + goto finish; + + case DOIO_HARD: + case DOIO_SUCCESS: + send_senddone_event(sock, &dev); + break; + } + + dev = ISC_LIST_HEAD(sock->send_list); + } + +finish: + if (ISC_LIST_EMPTY(sock->send_list)) { + unwatch_fd(&sock->manager->threads[sock->threadid], sock->fd, + SELECT_POKE_WRITE); + } +} + +/* + * Process read/writes on each fd here. Avoid locking + * and unlocking twice if both reads and writes are possible. + */ +static void +process_fd(isc__socketthread_t *thread, int fd, bool readable, bool writeable) { + isc_socket_t *sock; + int lockid = FDLOCK_ID(fd); + + /* + * If the socket is going to be closed, don't do more I/O. + */ + LOCK(&thread->fdlock[lockid]); + if (thread->fdstate[fd] == CLOSE_PENDING) { + UNLOCK(&thread->fdlock[lockid]); + + (void)unwatch_fd(thread, fd, SELECT_POKE_READ); + (void)unwatch_fd(thread, fd, SELECT_POKE_WRITE); + return; + } + + sock = thread->fds[fd]; + if (sock == NULL) { + UNLOCK(&thread->fdlock[lockid]); + return; + } + + LOCK(&sock->lock); + + if (sock->fd < 0) { + /* + * Sock is being closed - the final external reference + * is gone but it was not yet removed from event loop + * and fdstate[]/fds[] as destroy() is waiting on + * thread->fdlock[lockid] or sock->lock that we're holding. + * Just release the locks and bail. + */ + UNLOCK(&sock->lock); + UNLOCK(&thread->fdlock[lockid]); + return; + } + + REQUIRE(readable || writeable); + if (writeable) { + if (sock->connecting) { + internal_connect(sock); + } else { + internal_send(sock); + } + } + + if (readable) { + if (sock->listener) { + internal_accept(sock); /* unlocks sock */ + } else { + internal_recv(sock); + UNLOCK(&sock->lock); + } + } else { + UNLOCK(&sock->lock); + } + + UNLOCK(&thread->fdlock[lockid]); + + /* + * Socket destruction might be pending, it will resume + * after releasing fdlock and sock->lock. + */ +} + +/* + * process_fds is different for different event loops + * it takes the events from event loops and for each FD + * launches process_fd + */ +#ifdef USE_KQUEUE +static bool +process_fds(isc__socketthread_t *thread, struct kevent *events, int nevents) { + int i; + bool readable, writable; + bool done = false; + bool have_ctlevent = false; + if (nevents == thread->nevents) { + /* + * This is not an error, but something unexpected. If this + * happens, it may indicate the need for increasing + * ISC_SOCKET_MAXEVENTS. + */ + thread_log(thread, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_SOCKET, ISC_LOG_INFO, + "maximum number of FD events (%d) received", + nevents); + } + + for (i = 0; i < nevents; i++) { + REQUIRE(events[i].ident < thread->manager->maxsocks); + if (events[i].ident == (uintptr_t)thread->pipe_fds[0]) { + have_ctlevent = true; + continue; + } + readable = (events[i].filter == EVFILT_READ); + writable = (events[i].filter == EVFILT_WRITE); + process_fd(thread, events[i].ident, readable, writable); + } + + if (have_ctlevent) { + done = process_ctlfd(thread); + } + + return (done); +} +#elif defined(USE_EPOLL) +static bool +process_fds(isc__socketthread_t *thread, struct epoll_event *events, + int nevents) { + int i; + bool done = false; + bool have_ctlevent = false; + + if (nevents == thread->nevents) { + thread_log(thread, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_SOCKET, ISC_LOG_INFO, + "maximum number of FD events (%d) received", + nevents); + } + + for (i = 0; i < nevents; i++) { + REQUIRE(events[i].data.fd < (int)thread->manager->maxsocks); + if (events[i].data.fd == thread->pipe_fds[0]) { + have_ctlevent = true; + continue; + } + if ((events[i].events & EPOLLERR) != 0 || + (events[i].events & EPOLLHUP) != 0) + { + /* + * epoll does not set IN/OUT bits on an erroneous + * condition, so we need to try both anyway. This is a + * bit inefficient, but should be okay for such rare + * events. Note also that the read or write attempt + * won't block because we use non-blocking sockets. + */ + int fd = events[i].data.fd; + events[i].events |= thread->epoll_events[fd]; + } + process_fd(thread, events[i].data.fd, + (events[i].events & EPOLLIN) != 0, + (events[i].events & EPOLLOUT) != 0); + } + + if (have_ctlevent) { + done = process_ctlfd(thread); + } + + return (done); +} +#elif defined(USE_DEVPOLL) +static bool +process_fds(isc__socketthread_t *thread, struct pollfd *events, int nevents) { + int i; + bool done = false; + bool have_ctlevent = false; + + if (nevents == thread->nevents) { + thread_log(thread, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_SOCKET, ISC_LOG_INFO, + "maximum number of FD events (%d) received", + nevents); + } + + for (i = 0; i < nevents; i++) { + REQUIRE(events[i].fd < (int)thread->manager->maxsocks); + if (events[i].fd == thread->pipe_fds[0]) { + have_ctlevent = true; + continue; + } + process_fd(thread, events[i].fd, + (events[i].events & POLLIN) != 0, + (events[i].events & POLLOUT) != 0); + } + + if (have_ctlevent) { + done = process_ctlfd(thread); + } + + return (done); +} +#elif defined(USE_SELECT) +static void +process_fds(isc__socketthread_t *thread, int maxfd, fd_set *readfds, + fd_set *writefds) { + int i; + + REQUIRE(maxfd <= (int)thread->manager->maxsocks); + + for (i = 0; i < maxfd; i++) { + if (i == thread->pipe_fds[0] || i == thread->pipe_fds[1]) { + continue; + } + process_fd(thread, i, FD_ISSET(i, readfds), + FD_ISSET(i, writefds)); + } +} +#endif /* ifdef USE_KQUEUE */ + +static bool +process_ctlfd(isc__socketthread_t *thread) { + int msg, fd; + + for (;;) { + select_readmsg(thread, &fd, &msg); + + thread_log(thread, IOEVENT, + "watcher got message %d for socket %d", msg, fd); + + /* + * Nothing to read? + */ + if (msg == SELECT_POKE_NOTHING) { + break; + } + + /* + * Handle shutdown message. We really should + * jump out of this loop right away, but + * it doesn't matter if we have to do a little + * more work first. + */ + if (msg == SELECT_POKE_SHUTDOWN) { + return (true); + } + + /* + * This is a wakeup on a socket. Look + * at the event queue for both read and write, + * and decide if we need to watch on it now + * or not. + */ + wakeup_socket(thread, fd, msg); + } + + return (false); +} + +/* + * This is the thread that will loop forever, always in a select or poll + * call. + * + * When select returns something to do, do whatever's necessary and post + * an event to the task that was requesting the action. + */ +static isc_threadresult_t +netthread(void *uap) { + isc__socketthread_t *thread = uap; + isc_socketmgr_t *manager = thread->manager; + (void)manager; + bool done; + int cc; +#ifdef USE_KQUEUE + const char *fnname = "kevent()"; +#elif defined(USE_EPOLL) + const char *fnname = "epoll_wait()"; +#elif defined(USE_DEVPOLL) + isc_result_t result; + const char *fnname = "ioctl(DP_POLL)"; + struct dvpoll dvp; + int pass; +#if defined(ISC_SOCKET_USE_POLLWATCH) + pollstate_t pollstate = poll_idle; +#endif /* if defined(ISC_SOCKET_USE_POLLWATCH) */ +#elif defined(USE_SELECT) + const char *fnname = "select()"; + int maxfd; + int ctlfd; +#endif /* ifdef USE_KQUEUE */ + char strbuf[ISC_STRERRORSIZE]; + +#if defined(USE_SELECT) + /* + * Get the control fd here. This will never change. + */ + ctlfd = thread->pipe_fds[0]; +#endif /* if defined(USE_SELECT) */ + done = false; + while (!done) { + do { +#ifdef USE_KQUEUE + cc = kevent(thread->kqueue_fd, NULL, 0, thread->events, + thread->nevents, NULL); +#elif defined(USE_EPOLL) + cc = epoll_wait(thread->epoll_fd, thread->events, + thread->nevents, -1); +#elif defined(USE_DEVPOLL) + /* + * Re-probe every thousand calls. + */ + if (thread->calls++ > 1000U) { + result = isc_resource_getcurlimit( + isc_resource_openfiles, + &thread->open_max); + if (result != ISC_R_SUCCESS) { + thread->open_max = 64; + } + thread->calls = 0; + } + for (pass = 0; pass < 2; pass++) { + dvp.dp_fds = thread->events; + dvp.dp_nfds = thread->nevents; + if (dvp.dp_nfds >= thread->open_max) { + dvp.dp_nfds = thread->open_max - 1; + } +#ifndef ISC_SOCKET_USE_POLLWATCH + dvp.dp_timeout = -1; +#else /* ifndef ISC_SOCKET_USE_POLLWATCH */ + if (pollstate == poll_idle) { + dvp.dp_timeout = -1; + } else { + dvp.dp_timeout = + ISC_SOCKET_POLLWATCH_TIMEOUT; + } +#endif /* ISC_SOCKET_USE_POLLWATCH */ + cc = ioctl(thread->devpoll_fd, DP_POLL, &dvp); + if (cc == -1 && errno == EINVAL) { + /* + * {OPEN_MAX} may have dropped. Look + * up the current value and try again. + */ + result = isc_resource_getcurlimit( + isc_resource_openfiles, + &thread->open_max); + if (result != ISC_R_SUCCESS) { + thread->open_max = 64; + } + } else { + break; + } + } +#elif defined(USE_SELECT) + /* + * We will have only one thread anyway, we can lock + * manager lock and don't care + */ + LOCK(&manager->lock); + memmove(thread->read_fds_copy, thread->read_fds, + thread->fd_bufsize); + memmove(thread->write_fds_copy, thread->write_fds, + thread->fd_bufsize); + maxfd = thread->maxfd + 1; + UNLOCK(&manager->lock); + + cc = select(maxfd, thread->read_fds_copy, + thread->write_fds_copy, NULL, NULL); +#endif /* USE_KQUEUE */ + + if (cc < 0 && !SOFT_ERROR(errno)) { + strerror_r(errno, strbuf, sizeof(strbuf)); + FATAL_ERROR(__FILE__, __LINE__, "%s failed: %s", + fnname, strbuf); + } + +#if defined(USE_DEVPOLL) && defined(ISC_SOCKET_USE_POLLWATCH) + if (cc == 0) { + if (pollstate == poll_active) { + pollstate = poll_checking; + } else if (pollstate == poll_checking) { + pollstate = poll_idle; + } + } else if (cc > 0) { + if (pollstate == poll_checking) { + /* + * XXX: We'd like to use a more + * verbose log level as it's actually an + * unexpected event, but the kernel bug + * reportedly happens pretty frequently + * (and it can also be a false positive) + * so it would be just too noisy. + */ + thread_log(thread, + ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_SOCKET, + ISC_LOG_DEBUG(1), + "unexpected POLL timeout"); + } + pollstate = poll_active; + } +#endif /* if defined(USE_DEVPOLL) && defined(ISC_SOCKET_USE_POLLWATCH) */ + } while (cc < 0); + +#if defined(USE_KQUEUE) || defined(USE_EPOLL) || defined(USE_DEVPOLL) + done = process_fds(thread, thread->events, cc); +#elif defined(USE_SELECT) + process_fds(thread, maxfd, thread->read_fds_copy, + thread->write_fds_copy); + + /* + * Process reads on internal, control fd. + */ + if (FD_ISSET(ctlfd, thread->read_fds_copy)) { + done = process_ctlfd(thread); + } +#endif /* if defined(USE_KQUEUE) || defined(USE_EPOLL) || defined(USE_DEVPOLL) \ + * */ + } + + thread_log(thread, TRACE, "watcher exiting"); + return ((isc_threadresult_t)0); +} + +void +isc_socketmgr_setreserved(isc_socketmgr_t *manager, uint32_t reserved) { + REQUIRE(VALID_MANAGER(manager)); + + manager->reserved = reserved; +} + +void +isc_socketmgr_maxudp(isc_socketmgr_t *manager, unsigned int maxudp) { + REQUIRE(VALID_MANAGER(manager)); + + manager->maxudp = maxudp; +} + +/* + * Setup socket thread, thread->manager and thread->threadid must be filled. + */ + +static isc_result_t +setup_thread(isc__socketthread_t *thread) { + isc_result_t result = ISC_R_SUCCESS; + int i; + char strbuf[ISC_STRERRORSIZE]; + + REQUIRE(thread != NULL); + REQUIRE(VALID_MANAGER(thread->manager)); + REQUIRE(thread->threadid >= 0 && + thread->threadid < thread->manager->nthreads); + + thread->fds = + isc_mem_get(thread->manager->mctx, + thread->manager->maxsocks * sizeof(isc_socket_t *)); + + memset(thread->fds, 0, + thread->manager->maxsocks * sizeof(isc_socket_t *)); + + thread->fdstate = isc_mem_get(thread->manager->mctx, + thread->manager->maxsocks * sizeof(int)); + + memset(thread->fdstate, 0, thread->manager->maxsocks * sizeof(int)); + + thread->fdlock = isc_mem_get(thread->manager->mctx, + FDLOCK_COUNT * sizeof(isc_mutex_t)); + + for (i = 0; i < FDLOCK_COUNT; i++) { + isc_mutex_init(&thread->fdlock[i]); + } + + if (pipe(thread->pipe_fds) != 0) { + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, "pipe() failed: %s", + strbuf); + return (ISC_R_UNEXPECTED); + } + RUNTIME_CHECK(make_nonblock(thread->pipe_fds[0]) == ISC_R_SUCCESS); + +#ifdef USE_KQUEUE + thread->nevents = ISC_SOCKET_MAXEVENTS; + thread->events = isc_mem_get(thread->manager->mctx, + sizeof(struct kevent) * thread->nevents); + + thread->kqueue_fd = kqueue(); + if (thread->kqueue_fd == -1) { + result = isc__errno2result(errno); + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, "kqueue failed: %s", + strbuf); + isc_mem_put(thread->manager->mctx, thread->events, + sizeof(struct kevent) * thread->nevents); + return (result); + } + + result = watch_fd(thread, thread->pipe_fds[0], SELECT_POKE_READ); + if (result != ISC_R_SUCCESS) { + close(thread->kqueue_fd); + isc_mem_put(thread->manager->mctx, thread->events, + sizeof(struct kevent) * thread->nevents); + } + return (result); + +#elif defined(USE_EPOLL) + thread->nevents = ISC_SOCKET_MAXEVENTS; + thread->epoll_events = + isc_mem_get(thread->manager->mctx, + (thread->manager->maxsocks * sizeof(uint32_t))); + + memset(thread->epoll_events, 0, + thread->manager->maxsocks * sizeof(uint32_t)); + + thread->events = + isc_mem_get(thread->manager->mctx, + sizeof(struct epoll_event) * thread->nevents); + + thread->epoll_fd = epoll_create(thread->nevents); + if (thread->epoll_fd == -1) { + result = isc__errno2result(errno); + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, "epoll_create failed: %s", + strbuf); + return (result); + } + + result = watch_fd(thread, thread->pipe_fds[0], SELECT_POKE_READ); + return (result); + +#elif defined(USE_DEVPOLL) + thread->nevents = ISC_SOCKET_MAXEVENTS; + result = isc_resource_getcurlimit(isc_resource_openfiles, + &thread->open_max); + if (result != ISC_R_SUCCESS) { + thread->open_max = 64; + } + thread->calls = 0; + thread->events = isc_mem_get(thread->manager->mctx, + sizeof(struct pollfd) * thread->nevents); + + /* + * Note: fdpollinfo should be able to support all possible FDs, so + * it must have maxsocks entries (not nevents). + */ + thread->fdpollinfo = + isc_mem_get(thread->manager->mctx, + sizeof(pollinfo_t) * thread->manager->maxsocks); + memset(thread->fdpollinfo, 0, + sizeof(pollinfo_t) * thread->manager->maxsocks); + thread->devpoll_fd = open("/dev/poll", O_RDWR); + if (thread->devpoll_fd == -1) { + result = isc__errno2result(errno); + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "open(/dev/poll) failed: %s", strbuf); + isc_mem_put(thread->manager->mctx, thread->events, + sizeof(struct pollfd) * thread->nevents); + isc_mem_put(thread->manager->mctx, thread->fdpollinfo, + sizeof(pollinfo_t) * thread->manager->maxsocks); + return (result); + } + result = watch_fd(thread, thread->pipe_fds[0], SELECT_POKE_READ); + if (result != ISC_R_SUCCESS) { + close(thread->devpoll_fd); + isc_mem_put(thread->manager->mctx, thread->events, + sizeof(struct pollfd) * thread->nevents); + isc_mem_put(thread->manager->mctx, thread->fdpollinfo, + sizeof(pollinfo_t) * thread->manager->maxsocks); + return (result); + } + + return (ISC_R_SUCCESS); +#elif defined(USE_SELECT) + UNUSED(result); + +#if ISC_SOCKET_MAXSOCKETS > FD_SETSIZE + /* + * Note: this code should also cover the case of MAXSOCKETS <= + * FD_SETSIZE, but we separate the cases to avoid possible portability + * issues regarding howmany() and the actual representation of fd_set. + */ + thread->fd_bufsize = howmany(manager->maxsocks, NFDBITS) * + sizeof(fd_mask); +#else /* if ISC_SOCKET_MAXSOCKETS > FD_SETSIZE */ + thread->fd_bufsize = sizeof(fd_set); +#endif /* if ISC_SOCKET_MAXSOCKETS > FD_SETSIZE */ + + thread->read_fds = isc_mem_get(thread->manager->mctx, + thread->fd_bufsize); + thread->read_fds_copy = isc_mem_get(thread->manager->mctx, + thread->fd_bufsize); + thread->write_fds = isc_mem_get(thread->manager->mctx, + thread->fd_bufsize); + thread->write_fds_copy = isc_mem_get(thread->manager->mctx, + thread->fd_bufsize); + memset(thread->read_fds, 0, thread->fd_bufsize); + memset(thread->write_fds, 0, thread->fd_bufsize); + + (void)watch_fd(thread, thread->pipe_fds[0], SELECT_POKE_READ); + thread->maxfd = thread->pipe_fds[0]; + + return (ISC_R_SUCCESS); +#endif /* USE_KQUEUE */ +} + +static void +cleanup_thread(isc_mem_t *mctx, isc__socketthread_t *thread) { + isc_result_t result; + int i; + + result = unwatch_fd(thread, thread->pipe_fds[0], SELECT_POKE_READ); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, "epoll_ctl(DEL) failed"); + } +#ifdef USE_KQUEUE + close(thread->kqueue_fd); + isc_mem_put(mctx, thread->events, + sizeof(struct kevent) * thread->nevents); +#elif defined(USE_EPOLL) + close(thread->epoll_fd); + + isc_mem_put(mctx, thread->events, + sizeof(struct epoll_event) * thread->nevents); +#elif defined(USE_DEVPOLL) + close(thread->devpoll_fd); + isc_mem_put(mctx, thread->events, + sizeof(struct pollfd) * thread->nevents); + isc_mem_put(mctx, thread->fdpollinfo, + sizeof(pollinfo_t) * thread->manager->maxsocks); +#elif defined(USE_SELECT) + if (thread->read_fds != NULL) { + isc_mem_put(mctx, thread->read_fds, thread->fd_bufsize); + } + if (thread->read_fds_copy != NULL) { + isc_mem_put(mctx, thread->read_fds_copy, thread->fd_bufsize); + } + if (thread->write_fds != NULL) { + isc_mem_put(mctx, thread->write_fds, thread->fd_bufsize); + } + if (thread->write_fds_copy != NULL) { + isc_mem_put(mctx, thread->write_fds_copy, thread->fd_bufsize); + } +#endif /* USE_KQUEUE */ + for (i = 0; i < (int)thread->manager->maxsocks; i++) { + if (thread->fdstate[i] == CLOSE_PENDING) { + /* no need to lock */ + (void)close(i); + } + } + +#if defined(USE_EPOLL) + isc_mem_put(thread->manager->mctx, thread->epoll_events, + thread->manager->maxsocks * sizeof(uint32_t)); +#endif /* if defined(USE_EPOLL) */ + isc_mem_put(thread->manager->mctx, thread->fds, + thread->manager->maxsocks * sizeof(isc_socket_t *)); + isc_mem_put(thread->manager->mctx, thread->fdstate, + thread->manager->maxsocks * sizeof(int)); + + for (i = 0; i < FDLOCK_COUNT; i++) { + isc_mutex_destroy(&thread->fdlock[i]); + } + isc_mem_put(thread->manager->mctx, thread->fdlock, + FDLOCK_COUNT * sizeof(isc_mutex_t)); +} + +isc_result_t +isc_socketmgr_create(isc_mem_t *mctx, isc_socketmgr_t **managerp) { + return (isc_socketmgr_create2(mctx, managerp, 0, 1)); +} + +isc_result_t +isc_socketmgr_create2(isc_mem_t *mctx, isc_socketmgr_t **managerp, + unsigned int maxsocks, int nthreads) { + int i; + isc_socketmgr_t *manager; + + REQUIRE(managerp != NULL && *managerp == NULL); + + if (maxsocks == 0) { + maxsocks = ISC_SOCKET_MAXSOCKETS; + } + + manager = isc_mem_get(mctx, sizeof(*manager)); + + /* zero-clear so that necessary cleanup on failure will be easy */ + memset(manager, 0, sizeof(*manager)); + manager->maxsocks = maxsocks; + manager->reserved = 0; + manager->maxudp = 0; + manager->nthreads = nthreads; + manager->stats = NULL; + + manager->magic = SOCKET_MANAGER_MAGIC; + manager->mctx = NULL; + ISC_LIST_INIT(manager->socklist); + isc_mutex_init(&manager->lock); + isc_condition_init(&manager->shutdown_ok); + + /* + * Start up the select/poll thread. + */ + manager->threads = isc_mem_get(mctx, sizeof(isc__socketthread_t) * + manager->nthreads); + isc_mem_attach(mctx, &manager->mctx); + + for (i = 0; i < manager->nthreads; i++) { + manager->threads[i].manager = manager; + manager->threads[i].threadid = i; + setup_thread(&manager->threads[i]); + isc_thread_create(netthread, &manager->threads[i], + &manager->threads[i].thread); + char tname[1024]; + sprintf(tname, "isc-socket-%d", i); + isc_thread_setname(manager->threads[i].thread, tname); + } + + *managerp = manager; + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_socketmgr_getmaxsockets(isc_socketmgr_t *manager, unsigned int *nsockp) { + REQUIRE(VALID_MANAGER(manager)); + REQUIRE(nsockp != NULL); + + *nsockp = manager->maxsocks; + + return (ISC_R_SUCCESS); +} + +void +isc_socketmgr_setstats(isc_socketmgr_t *manager, isc_stats_t *stats) { + REQUIRE(VALID_MANAGER(manager)); + REQUIRE(ISC_LIST_EMPTY(manager->socklist)); + REQUIRE(manager->stats == NULL); + REQUIRE(isc_stats_ncounters(stats) == isc_sockstatscounter_max); + + isc_stats_attach(stats, &manager->stats); +} + +void +isc_socketmgr_destroy(isc_socketmgr_t **managerp) { + isc_socketmgr_t *manager; + + /* + * Destroy a socket manager. + */ + + REQUIRE(managerp != NULL); + manager = *managerp; + REQUIRE(VALID_MANAGER(manager)); + + LOCK(&manager->lock); + + /* + * Wait for all sockets to be destroyed. + */ + while (!ISC_LIST_EMPTY(manager->socklist)) { + manager_log(manager, CREATION, "sockets exist"); + WAIT(&manager->shutdown_ok, &manager->lock); + } + + UNLOCK(&manager->lock); + + /* + * Here, poke our select/poll thread. Do this by closing the write + * half of the pipe, which will send EOF to the read half. + * This is currently a no-op in the non-threaded case. + */ + for (int i = 0; i < manager->nthreads; i++) { + select_poke(manager, i, 0, SELECT_POKE_SHUTDOWN); + } + + /* + * Wait for thread to exit. + */ + for (int i = 0; i < manager->nthreads; i++) { + isc_thread_join(manager->threads[i].thread, NULL); + cleanup_thread(manager->mctx, &manager->threads[i]); + } + /* + * Clean up. + */ + isc_mem_put(manager->mctx, manager->threads, + sizeof(isc__socketthread_t) * manager->nthreads); + (void)isc_condition_destroy(&manager->shutdown_ok); + + if (manager->stats != NULL) { + isc_stats_detach(&manager->stats); + } + isc_mutex_destroy(&manager->lock); + manager->magic = 0; + isc_mem_putanddetach(&manager->mctx, manager, sizeof(*manager)); + + *managerp = NULL; +} + +static isc_result_t +socket_recv(isc_socket_t *sock, isc_socketevent_t *dev, isc_task_t *task, + unsigned int flags) { + int io_state; + bool have_lock = false; + isc_task_t *ntask = NULL; + isc_result_t result = ISC_R_SUCCESS; + + dev->ev_sender = task; + + if (sock->type == isc_sockettype_udp) { + io_state = doio_recv(sock, dev); + } else { + LOCK(&sock->lock); + have_lock = true; + + if (ISC_LIST_EMPTY(sock->recv_list)) { + io_state = doio_recv(sock, dev); + } else { + io_state = DOIO_SOFT; + } + } + + switch (io_state) { + case DOIO_SOFT: + /* + * We couldn't read all or part of the request right now, so + * queue it. + * + * Attach to socket and to task + */ + isc_task_attach(task, &ntask); + dev->attributes |= ISC_SOCKEVENTATTR_ATTACHED; + + if (!have_lock) { + LOCK(&sock->lock); + have_lock = true; + } + + /* + * Enqueue the request. If the socket was previously not being + * watched, poke the watcher to start paying attention to it. + */ + bool do_poke = ISC_LIST_EMPTY(sock->recv_list); + ISC_LIST_ENQUEUE(sock->recv_list, dev, ev_link); + if (do_poke) { + select_poke(sock->manager, sock->threadid, sock->fd, + SELECT_POKE_READ); + } + + socket_log(sock, NULL, EVENT, + "socket_recv: event %p -> task %p", dev, ntask); + + if ((flags & ISC_SOCKFLAG_IMMEDIATE) != 0) { + result = ISC_R_INPROGRESS; + } + break; + + case DOIO_EOF: + dev->result = ISC_R_EOF; + FALLTHROUGH; + + case DOIO_HARD: + case DOIO_SUCCESS: + if ((flags & ISC_SOCKFLAG_IMMEDIATE) == 0) { + send_recvdone_event(sock, &dev); + } + break; + } + + if (have_lock) { + UNLOCK(&sock->lock); + } + + return (result); +} + +isc_result_t +isc_socket_recv(isc_socket_t *sock, isc_region_t *region, unsigned int minimum, + isc_task_t *task, isc_taskaction_t action, void *arg) { + isc_socketevent_t *dev; + isc_socketmgr_t *manager; + + REQUIRE(VALID_SOCKET(sock)); + REQUIRE(action != NULL); + + manager = sock->manager; + REQUIRE(VALID_MANAGER(manager)); + + INSIST(sock->bound); + + dev = allocate_socketevent(manager->mctx, sock, ISC_SOCKEVENT_RECVDONE, + action, arg); + if (dev == NULL) { + return (ISC_R_NOMEMORY); + } + + return (isc_socket_recv2(sock, region, minimum, task, dev, 0)); +} + +isc_result_t +isc_socket_recv2(isc_socket_t *sock, isc_region_t *region, unsigned int minimum, + isc_task_t *task, isc_socketevent_t *event, + unsigned int flags) { + event->ev_sender = sock; + event->result = ISC_R_UNSET; + event->region = *region; + event->n = 0; + event->offset = 0; + event->attributes = 0; + + /* + * UDP sockets are always partial read. + */ + if (sock->type == isc_sockettype_udp) { + event->minimum = 1; + } else { + if (minimum == 0) { + event->minimum = region->length; + } else { + event->minimum = minimum; + } + } + + return (socket_recv(sock, event, task, flags)); +} + +static isc_result_t +socket_send(isc_socket_t *sock, isc_socketevent_t *dev, isc_task_t *task, + const isc_sockaddr_t *address, struct in6_pktinfo *pktinfo, + unsigned int flags) { + int io_state; + bool have_lock = false; + isc_task_t *ntask = NULL; + isc_result_t result = ISC_R_SUCCESS; + + dev->ev_sender = task; + + set_dev_address(address, sock, dev); + if (pktinfo != NULL) { + dev->attributes |= ISC_SOCKEVENTATTR_PKTINFO; + dev->pktinfo = *pktinfo; + + if (!isc_sockaddr_issitelocal(&dev->address) && + !isc_sockaddr_islinklocal(&dev->address)) + { + socket_log(sock, NULL, TRACE, + "pktinfo structure provided, ifindex %u " + "(set to 0)", + pktinfo->ipi6_ifindex); + + /* + * Set the pktinfo index to 0 here, to let the + * kernel decide what interface it should send on. + */ + dev->pktinfo.ipi6_ifindex = 0; + } + } + + if (sock->type == isc_sockettype_udp) { + io_state = doio_send(sock, dev); + } else { + LOCK(&sock->lock); + have_lock = true; + + if (ISC_LIST_EMPTY(sock->send_list)) { + io_state = doio_send(sock, dev); + } else { + io_state = DOIO_SOFT; + } + } + + switch (io_state) { + case DOIO_SOFT: + /* + * We couldn't send all or part of the request right now, so + * queue it unless ISC_SOCKFLAG_NORETRY is set. + */ + if ((flags & ISC_SOCKFLAG_NORETRY) == 0) { + isc_task_attach(task, &ntask); + dev->attributes |= ISC_SOCKEVENTATTR_ATTACHED; + + if (!have_lock) { + LOCK(&sock->lock); + have_lock = true; + } + + /* + * Enqueue the request. If the socket was previously + * not being watched, poke the watcher to start + * paying attention to it. + */ + bool do_poke = ISC_LIST_EMPTY(sock->send_list); + ISC_LIST_ENQUEUE(sock->send_list, dev, ev_link); + if (do_poke) { + select_poke(sock->manager, sock->threadid, + sock->fd, SELECT_POKE_WRITE); + } + socket_log(sock, NULL, EVENT, + "socket_send: event %p -> task %p", dev, + ntask); + + if ((flags & ISC_SOCKFLAG_IMMEDIATE) != 0) { + result = ISC_R_INPROGRESS; + } + break; + } + + FALLTHROUGH; + + case DOIO_HARD: + case DOIO_SUCCESS: + if (!have_lock) { + LOCK(&sock->lock); + have_lock = true; + } + if ((flags & ISC_SOCKFLAG_IMMEDIATE) == 0) { + send_senddone_event(sock, &dev); + } + break; + } + + if (have_lock) { + UNLOCK(&sock->lock); + } + + return (result); +} + +isc_result_t +isc_socket_send(isc_socket_t *sock, isc_region_t *region, isc_task_t *task, + isc_taskaction_t action, void *arg) { + /* + * REQUIRE() checking is performed in isc_socket_sendto(). + */ + return (isc_socket_sendto(sock, region, task, action, arg, NULL, NULL)); +} + +isc_result_t +isc_socket_sendto(isc_socket_t *sock, isc_region_t *region, isc_task_t *task, + isc_taskaction_t action, void *arg, + const isc_sockaddr_t *address, struct in6_pktinfo *pktinfo) { + isc_socketevent_t *dev; + isc_socketmgr_t *manager; + + REQUIRE(VALID_SOCKET(sock)); + REQUIRE(region != NULL); + REQUIRE(task != NULL); + REQUIRE(action != NULL); + + manager = sock->manager; + REQUIRE(VALID_MANAGER(manager)); + + INSIST(sock->bound); + + dev = allocate_socketevent(manager->mctx, sock, ISC_SOCKEVENT_SENDDONE, + action, arg); + if (dev == NULL) { + return (ISC_R_NOMEMORY); + } + + dev->region = *region; + + return (socket_send(sock, dev, task, address, pktinfo, 0)); +} + +isc_result_t +isc_socket_sendto2(isc_socket_t *sock, isc_region_t *region, isc_task_t *task, + const isc_sockaddr_t *address, struct in6_pktinfo *pktinfo, + isc_socketevent_t *event, unsigned int flags) { + REQUIRE(VALID_SOCKET(sock)); + REQUIRE((flags & ~(ISC_SOCKFLAG_IMMEDIATE | ISC_SOCKFLAG_NORETRY)) == + 0); + if ((flags & ISC_SOCKFLAG_NORETRY) != 0) { + REQUIRE(sock->type == isc_sockettype_udp); + } + event->ev_sender = sock; + event->result = ISC_R_UNSET; + event->region = *region; + event->n = 0; + event->offset = 0; + event->attributes &= ~ISC_SOCKEVENTATTR_ATTACHED; + + return (socket_send(sock, event, task, address, pktinfo, flags)); +} + +void +isc_socket_cleanunix(const isc_sockaddr_t *sockaddr, bool active) { +#ifdef ISC_PLATFORM_HAVESYSUNH + int s; + struct stat sb; + char strbuf[ISC_STRERRORSIZE]; + + if (sockaddr->type.sa.sa_family != AF_UNIX) { + return; + } + +#ifndef S_ISSOCK +#if defined(S_IFMT) && defined(S_IFSOCK) +#define S_ISSOCK(mode) ((mode & S_IFMT) == S_IFSOCK) +#elif defined(_S_IFMT) && defined(S_IFSOCK) +#define S_ISSOCK(mode) ((mode & _S_IFMT) == S_IFSOCK) +#endif /* if defined(S_IFMT) && defined(S_IFSOCK) */ +#endif /* ifndef S_ISSOCK */ + +#ifndef S_ISFIFO +#if defined(S_IFMT) && defined(S_IFIFO) +#define S_ISFIFO(mode) ((mode & S_IFMT) == S_IFIFO) +#elif defined(_S_IFMT) && defined(S_IFIFO) +#define S_ISFIFO(mode) ((mode & _S_IFMT) == S_IFIFO) +#endif /* if defined(S_IFMT) && defined(S_IFIFO) */ +#endif /* ifndef S_ISFIFO */ + +#if !defined(S_ISFIFO) && !defined(S_ISSOCK) +/* cppcheck-suppress preprocessorErrorDirective */ +#error \ + You need to define S_ISFIFO and S_ISSOCK as appropriate for your platform. See <sys/stat.h>. +#endif /* if !defined(S_ISFIFO) && !defined(S_ISSOCK) */ + +#ifndef S_ISFIFO +#define S_ISFIFO(mode) 0 +#endif /* ifndef S_ISFIFO */ + +#ifndef S_ISSOCK +#define S_ISSOCK(mode) 0 +#endif /* ifndef S_ISSOCK */ + + if (stat(sockaddr->type.sunix.sun_path, &sb) < 0) { + switch (errno) { + case ENOENT: + if (active) { /* We exited cleanly last time */ + break; + } + FALLTHROUGH; + default: + strerror_r(errno, strbuf, sizeof(strbuf)); + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_SOCKET, + active ? ISC_LOG_ERROR : ISC_LOG_WARNING, + "isc_socket_cleanunix: stat(%s): %s", + sockaddr->type.sunix.sun_path, strbuf); + return; + } + } else { + if (!(S_ISSOCK(sb.st_mode) || S_ISFIFO(sb.st_mode))) { + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_SOCKET, + active ? ISC_LOG_ERROR : ISC_LOG_WARNING, + "isc_socket_cleanunix: %s: not a socket", + sockaddr->type.sunix.sun_path); + return; + } + } + + if (active) { + if (unlink(sockaddr->type.sunix.sun_path) < 0) { + strerror_r(errno, strbuf, sizeof(strbuf)); + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_SOCKET, ISC_LOG_ERROR, + "isc_socket_cleanunix: unlink(%s): %s", + sockaddr->type.sunix.sun_path, strbuf); + } + return; + } + + s = socket(AF_UNIX, SOCK_STREAM, 0); + if (s < 0) { + strerror_r(errno, strbuf, sizeof(strbuf)); + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_SOCKET, ISC_LOG_WARNING, + "isc_socket_cleanunix: socket(%s): %s", + sockaddr->type.sunix.sun_path, strbuf); + return; + } + + if (connect(s, (const struct sockaddr *)&sockaddr->type.sunix, + sizeof(sockaddr->type.sunix)) < 0) + { + switch (errno) { + case ECONNREFUSED: + case ECONNRESET: + if (unlink(sockaddr->type.sunix.sun_path) < 0) { + strerror_r(errno, strbuf, sizeof(strbuf)); + isc_log_write( + isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_SOCKET, ISC_LOG_WARNING, + "isc_socket_cleanunix: " + "unlink(%s): %s", + sockaddr->type.sunix.sun_path, strbuf); + } + break; + default: + strerror_r(errno, strbuf, sizeof(strbuf)); + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_SOCKET, ISC_LOG_WARNING, + "isc_socket_cleanunix: connect(%s): %s", + sockaddr->type.sunix.sun_path, strbuf); + break; + } + } + close(s); +#else /* ifdef ISC_PLATFORM_HAVESYSUNH */ + UNUSED(sockaddr); + UNUSED(active); +#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */ +} + +isc_result_t +isc_socket_permunix(const isc_sockaddr_t *sockaddr, uint32_t perm, + uint32_t owner, uint32_t group) { +#ifdef ISC_PLATFORM_HAVESYSUNH + isc_result_t result = ISC_R_SUCCESS; + char strbuf[ISC_STRERRORSIZE]; + char path[sizeof(sockaddr->type.sunix.sun_path)]; +#ifdef NEED_SECURE_DIRECTORY + char *slash; +#endif /* ifdef NEED_SECURE_DIRECTORY */ + + REQUIRE(sockaddr->type.sa.sa_family == AF_UNIX); + INSIST(strlen(sockaddr->type.sunix.sun_path) < sizeof(path)); + strlcpy(path, sockaddr->type.sunix.sun_path, sizeof(path)); + +#ifdef NEED_SECURE_DIRECTORY + slash = strrchr(path, '/'); + if (slash != NULL) { + if (slash != path) { + *slash = '\0'; + } else { + strlcpy(path, "/", sizeof(path)); + } + } else { + strlcpy(path, ".", sizeof(path)); + } +#endif /* ifdef NEED_SECURE_DIRECTORY */ + + if (chmod(path, perm) < 0) { + strerror_r(errno, strbuf, sizeof(strbuf)); + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_SOCKET, ISC_LOG_ERROR, + "isc_socket_permunix: chmod(%s, %d): %s", path, + perm, strbuf); + result = ISC_R_FAILURE; + } + if (chown(path, owner, group) < 0) { + strerror_r(errno, strbuf, sizeof(strbuf)); + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_SOCKET, ISC_LOG_ERROR, + "isc_socket_permunix: chown(%s, %d, %d): %s", + path, owner, group, strbuf); + result = ISC_R_FAILURE; + } + return (result); +#else /* ifdef ISC_PLATFORM_HAVESYSUNH */ + UNUSED(sockaddr); + UNUSED(perm); + UNUSED(owner); + UNUSED(group); + return (ISC_R_NOTIMPLEMENTED); +#endif /* ifdef ISC_PLATFORM_HAVESYSUNH */ +} + +isc_result_t +isc_socket_bind(isc_socket_t *sock, const isc_sockaddr_t *sockaddr, + isc_socket_options_t options) { + char strbuf[ISC_STRERRORSIZE]; + int on = 1; + + REQUIRE(VALID_SOCKET(sock)); + + LOCK(&sock->lock); + + INSIST(!sock->bound); + INSIST(!sock->dupped); + + if (sock->pf != sockaddr->type.sa.sa_family) { + UNLOCK(&sock->lock); + return (ISC_R_FAMILYMISMATCH); + } + + /* + * Only set SO_REUSEADDR when we want a specific port. + */ +#ifdef AF_UNIX + if (sock->pf == AF_UNIX) { + goto bind_socket; + } +#endif /* ifdef AF_UNIX */ + if ((options & ISC_SOCKET_REUSEADDRESS) != 0 && + isc_sockaddr_getport(sockaddr) != (in_port_t)0) + { + if (setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, (void *)&on, + sizeof(on)) < 0) + { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "setsockopt(%d) failed", sock->fd); + } +#if defined(__FreeBSD_kernel__) && defined(SO_REUSEPORT_LB) + if (setsockopt(sock->fd, SOL_SOCKET, SO_REUSEPORT_LB, + (void *)&on, sizeof(on)) < 0) + { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "setsockopt(%d) failed", sock->fd); + } +#elif defined(__linux__) && defined(SO_REUSEPORT) + if (setsockopt(sock->fd, SOL_SOCKET, SO_REUSEPORT, (void *)&on, + sizeof(on)) < 0) + { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "setsockopt(%d) failed", sock->fd); + } +#endif /* if defined(__FreeBSD_kernel__) && defined(SO_REUSEPORT_LB) */ + /* Press on... */ + } +#ifdef AF_UNIX +bind_socket: +#endif /* ifdef AF_UNIX */ + if (bind(sock->fd, &sockaddr->type.sa, sockaddr->length) < 0) { + inc_stats(sock->manager->stats, + sock->statsindex[STATID_BINDFAIL]); + + UNLOCK(&sock->lock); + switch (errno) { + case EACCES: + return (ISC_R_NOPERM); + case EADDRNOTAVAIL: + return (ISC_R_ADDRNOTAVAIL); + case EADDRINUSE: + return (ISC_R_ADDRINUSE); + case EINVAL: + return (ISC_R_BOUND); + default: + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, "bind: %s", + strbuf); + return (ISC_R_UNEXPECTED); + } + } + + socket_log(sock, sockaddr, TRACE, "bound"); + sock->bound = 1; + + UNLOCK(&sock->lock); + return (ISC_R_SUCCESS); +} + +/* + * Enable this only for specific OS versions, and only when they have repaired + * their problems with it. Until then, this is is broken and needs to be + * disabled by default. See RT22589 for details. + */ +#undef ENABLE_ACCEPTFILTER + +isc_result_t +isc_socket_filter(isc_socket_t *sock, const char *filter) { +#if defined(SO_ACCEPTFILTER) && defined(ENABLE_ACCEPTFILTER) + char strbuf[ISC_STRERRORSIZE]; + struct accept_filter_arg afa; +#else /* if defined(SO_ACCEPTFILTER) && defined(ENABLE_ACCEPTFILTER) */ + UNUSED(sock); + UNUSED(filter); +#endif /* if defined(SO_ACCEPTFILTER) && defined(ENABLE_ACCEPTFILTER) */ + + REQUIRE(VALID_SOCKET(sock)); + +#if defined(SO_ACCEPTFILTER) && defined(ENABLE_ACCEPTFILTER) + bzero(&afa, sizeof(afa)); + strlcpy(afa.af_name, filter, sizeof(afa.af_name)); + if (setsockopt(sock->fd, SOL_SOCKET, SO_ACCEPTFILTER, &afa, + sizeof(afa)) == -1) + { + strerror_r(errno, strbuf, sizeof(strbuf)); + socket_log(sock, NULL, CREATION, + "setsockopt(SO_ACCEPTFILTER): %s", strbuf); + return (ISC_R_FAILURE); + } + return (ISC_R_SUCCESS); +#else /* if defined(SO_ACCEPTFILTER) && defined(ENABLE_ACCEPTFILTER) */ + return (ISC_R_NOTIMPLEMENTED); +#endif /* if defined(SO_ACCEPTFILTER) && defined(ENABLE_ACCEPTFILTER) */ +} + +/* + * Try enabling TCP Fast Open for a given socket if the OS supports it. + */ +static void +set_tcp_fastopen(isc_socket_t *sock, unsigned int backlog) { +#if defined(ENABLE_TCP_FASTOPEN) && defined(TCP_FASTOPEN) + char strbuf[ISC_STRERRORSIZE]; + +/* + * FreeBSD, as of versions 10.3 and 11.0, defines TCP_FASTOPEN while also + * shipping a default kernel without TFO support, so we special-case it by + * performing an additional runtime check for TFO support using sysctl to + * prevent setsockopt() errors from being logged. + */ +#if defined(__FreeBSD__) && defined(HAVE_SYSCTLBYNAME) +#define SYSCTL_TFO "net.inet.tcp.fastopen.enabled" + unsigned int enabled; + size_t enabledlen = sizeof(enabled); + static bool tfo_notice_logged = false; + + if (sysctlbyname(SYSCTL_TFO, &enabled, &enabledlen, NULL, 0) < 0) { + /* + * This kernel does not support TCP Fast Open. There is + * nothing more we can do. + */ + return; + } else if (enabled == 0) { + /* + * This kernel does support TCP Fast Open, but it is disabled + * by sysctl. Notify the user, but do not nag. + */ + if (!tfo_notice_logged) { + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_SOCKET, ISC_LOG_NOTICE, + "TCP_FASTOPEN support is disabled by " + "sysctl (" SYSCTL_TFO " = 0)"); + tfo_notice_logged = true; + } + return; + } +#endif /* if defined(__FreeBSD__) && defined(HAVE_SYSCTLBYNAME) */ + +#ifdef __APPLE__ + backlog = 1; +#else /* ifdef __APPLE__ */ + backlog = backlog / 2; + if (backlog == 0) { + backlog = 1; + } +#endif /* ifdef __APPLE__ */ + if (setsockopt(sock->fd, IPPROTO_TCP, TCP_FASTOPEN, (void *)&backlog, + sizeof(backlog)) < 0) + { + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "setsockopt(%d, TCP_FASTOPEN) failed with %s", + sock->fd, strbuf); + /* TCP_FASTOPEN is experimental so ignore failures */ + } +#else /* if defined(ENABLE_TCP_FASTOPEN) && defined(TCP_FASTOPEN) */ + UNUSED(sock); + UNUSED(backlog); +#endif /* if defined(ENABLE_TCP_FASTOPEN) && defined(TCP_FASTOPEN) */ +} + +/* + * Set up to listen on a given socket. We do this by creating an internal + * event that will be dispatched when the socket has read activity. The + * watcher will send the internal event to the task when there is a new + * connection. + * + * Unlike in read, we don't preallocate a done event here. Every time there + * is a new connection we'll have to allocate a new one anyway, so we might + * as well keep things simple rather than having to track them. + */ +isc_result_t +isc_socket_listen(isc_socket_t *sock, unsigned int backlog) { + char strbuf[ISC_STRERRORSIZE]; + + REQUIRE(VALID_SOCKET(sock)); + + LOCK(&sock->lock); + + REQUIRE(!sock->listener); + REQUIRE(sock->bound); + REQUIRE(sock->type == isc_sockettype_tcp || + sock->type == isc_sockettype_unix); + + if (backlog == 0) { + backlog = SOMAXCONN; + } + + if (listen(sock->fd, (int)backlog) < 0) { + UNLOCK(&sock->lock); + strerror_r(errno, strbuf, sizeof(strbuf)); + + UNEXPECTED_ERROR(__FILE__, __LINE__, "listen: %s", strbuf); + + return (ISC_R_UNEXPECTED); + } + + set_tcp_fastopen(sock, backlog); + + sock->listener = 1; + + UNLOCK(&sock->lock); + return (ISC_R_SUCCESS); +} + +/* + * This should try to do aggressive accept() XXXMLG + */ +isc_result_t +isc_socket_accept(isc_socket_t *sock, isc_task_t *task, isc_taskaction_t action, + void *arg) { + isc_socket_newconnev_t *dev; + isc_socketmgr_t *manager; + isc_task_t *ntask = NULL; + isc_socket_t *nsock; + isc_result_t result; + bool do_poke = false; + + REQUIRE(VALID_SOCKET(sock)); + manager = sock->manager; + REQUIRE(VALID_MANAGER(manager)); + + LOCK(&sock->lock); + + REQUIRE(sock->listener); + + /* + * Sender field is overloaded here with the task we will be sending + * this event to. Just before the actual event is delivered the + * actual ev_sender will be touched up to be the socket. + */ + dev = (isc_socket_newconnev_t *)isc_event_allocate( + manager->mctx, task, ISC_SOCKEVENT_NEWCONN, action, arg, + sizeof(*dev)); + ISC_LINK_INIT(dev, ev_link); + + result = allocate_socket(manager, sock->type, &nsock); + if (result != ISC_R_SUCCESS) { + isc_event_free(ISC_EVENT_PTR(&dev)); + UNLOCK(&sock->lock); + return (result); + } + + /* + * Attach to socket and to task. + */ + isc_task_attach(task, &ntask); + if (isc_task_exiting(ntask)) { + free_socket(&nsock); + isc_task_detach(&ntask); + isc_event_free(ISC_EVENT_PTR(&dev)); + UNLOCK(&sock->lock); + return (ISC_R_SHUTTINGDOWN); + } + isc_refcount_increment0(&nsock->references); + nsock->statsindex = sock->statsindex; + + dev->ev_sender = ntask; + dev->newsocket = nsock; + + /* + * Poke watcher here. We still have the socket locked, so there + * is no race condition. We will keep the lock for such a short + * bit of time waking it up now or later won't matter all that much. + */ + do_poke = ISC_LIST_EMPTY(sock->accept_list); + ISC_LIST_ENQUEUE(sock->accept_list, dev, ev_link); + if (do_poke) { + select_poke(manager, sock->threadid, sock->fd, + SELECT_POKE_ACCEPT); + } + UNLOCK(&sock->lock); + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_socket_connect(isc_socket_t *sock, const isc_sockaddr_t *addr, + isc_task_t *task, isc_taskaction_t action, void *arg) { + isc_socket_connev_t *dev; + isc_task_t *ntask = NULL; + isc_socketmgr_t *manager; + int cc; + char strbuf[ISC_STRERRORSIZE]; + char addrbuf[ISC_SOCKADDR_FORMATSIZE]; + + REQUIRE(VALID_SOCKET(sock)); + REQUIRE(addr != NULL); + REQUIRE(task != NULL); + REQUIRE(action != NULL); + + manager = sock->manager; + REQUIRE(VALID_MANAGER(manager)); + REQUIRE(addr != NULL); + + if (isc_sockaddr_ismulticast(addr)) { + return (ISC_R_MULTICAST); + } + + LOCK(&sock->lock); + + dev = (isc_socket_connev_t *)isc_event_allocate( + manager->mctx, sock, ISC_SOCKEVENT_CONNECT, action, arg, + sizeof(*dev)); + ISC_LINK_INIT(dev, ev_link); + + if (sock->connecting) { + INSIST(isc_sockaddr_equal(&sock->peer_address, addr)); + goto queue; + } + + if (sock->connected) { + INSIST(isc_sockaddr_equal(&sock->peer_address, addr)); + dev->result = ISC_R_SUCCESS; + isc_task_sendto(task, ISC_EVENT_PTR(&dev), sock->threadid); + + UNLOCK(&sock->lock); + + return (ISC_R_SUCCESS); + } + + /* + * Try to do the connect right away, as there can be only one + * outstanding, and it might happen to complete. + */ + sock->peer_address = *addr; + cc = connect(sock->fd, &addr->type.sa, addr->length); + if (cc < 0) { + /* + * The socket is nonblocking and the connection cannot be + * completed immediately. It is possible to select(2) or + * poll(2) for completion by selecting the socket for writing. + * After select(2) indicates writability, use getsockopt(2) to + * read the SO_ERROR option at level SOL_SOCKET to determine + * whether connect() completed successfully (SO_ERROR is zero) + * or unsuccessfully (SO_ERROR is one of the usual error codes + * listed here, explaining the reason for the failure). + */ + if (sock->type == isc_sockettype_udp && errno == EINPROGRESS) { + cc = 0; + goto success; + } + if (SOFT_ERROR(errno) || errno == EINPROGRESS) { + goto queue; + } + + switch (errno) { +#define ERROR_MATCH(a, b) \ + case a: \ + dev->result = b; \ + goto err_exit; + ERROR_MATCH(EACCES, ISC_R_NOPERM); + ERROR_MATCH(EADDRNOTAVAIL, ISC_R_ADDRNOTAVAIL); + ERROR_MATCH(EAFNOSUPPORT, ISC_R_ADDRNOTAVAIL); + ERROR_MATCH(ECONNREFUSED, ISC_R_CONNREFUSED); + ERROR_MATCH(EHOSTUNREACH, ISC_R_HOSTUNREACH); +#ifdef EHOSTDOWN + ERROR_MATCH(EHOSTDOWN, ISC_R_HOSTUNREACH); +#endif /* ifdef EHOSTDOWN */ + ERROR_MATCH(ENETUNREACH, ISC_R_NETUNREACH); + ERROR_MATCH(ENOBUFS, ISC_R_NORESOURCES); + ERROR_MATCH(EPERM, ISC_R_HOSTUNREACH); + ERROR_MATCH(EPIPE, ISC_R_NOTCONNECTED); + ERROR_MATCH(ETIMEDOUT, ISC_R_TIMEDOUT); + ERROR_MATCH(ECONNRESET, ISC_R_CONNECTIONRESET); +#undef ERROR_MATCH + } + + sock->connected = 0; + + strerror_r(errno, strbuf, sizeof(strbuf)); + isc_sockaddr_format(addr, addrbuf, sizeof(addrbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, "connect(%s) %d/%s", + addrbuf, errno, strbuf); + + UNLOCK(&sock->lock); + inc_stats(sock->manager->stats, + sock->statsindex[STATID_CONNECTFAIL]); + isc_event_free(ISC_EVENT_PTR(&dev)); + return (ISC_R_UNEXPECTED); + + err_exit: + sock->connected = 0; + isc_task_sendto(task, ISC_EVENT_PTR(&dev), sock->threadid); + + UNLOCK(&sock->lock); + inc_stats(sock->manager->stats, + sock->statsindex[STATID_CONNECTFAIL]); + return (ISC_R_SUCCESS); + } + + /* + * If connect completed, fire off the done event. + */ +success: + if (cc == 0) { + sock->connected = 1; + sock->bound = 1; + dev->result = ISC_R_SUCCESS; + isc_task_sendto(task, ISC_EVENT_PTR(&dev), sock->threadid); + + UNLOCK(&sock->lock); + + inc_stats(sock->manager->stats, + sock->statsindex[STATID_CONNECT]); + + return (ISC_R_SUCCESS); + } + +queue: + + /* + * Attach to task. + */ + isc_task_attach(task, &ntask); + + dev->ev_sender = ntask; + + /* + * Poke watcher here. We still have the socket locked, so there + * is no race condition. We will keep the lock for such a short + * bit of time waking it up now or later won't matter all that much. + */ + bool do_poke = ISC_LIST_EMPTY(sock->connect_list); + ISC_LIST_ENQUEUE(sock->connect_list, dev, ev_link); + if (do_poke && !sock->connecting) { + sock->connecting = 1; + select_poke(manager, sock->threadid, sock->fd, + SELECT_POKE_CONNECT); + } + + UNLOCK(&sock->lock); + return (ISC_R_SUCCESS); +} + +/* + * Called when a socket with a pending connect() finishes. + */ +static void +internal_connect(isc_socket_t *sock) { + isc_socket_connev_t *dev; + int cc; + isc_result_t result; + socklen_t optlen; + char strbuf[ISC_STRERRORSIZE]; + char peerbuf[ISC_SOCKADDR_FORMATSIZE]; + + INSIST(VALID_SOCKET(sock)); + REQUIRE(sock->fd >= 0); + + /* + * Get the first item off the connect list. + * If it is empty, unlock the socket and return. + */ + dev = ISC_LIST_HEAD(sock->connect_list); + if (dev == NULL) { + INSIST(!sock->connecting); + goto finish; + } + + INSIST(sock->connecting); + sock->connecting = 0; + + /* + * Get any possible error status here. + */ + optlen = sizeof(cc); + if (getsockopt(sock->fd, SOL_SOCKET, SO_ERROR, (void *)&cc, + (void *)&optlen) != 0) + { + cc = errno; + } else { + errno = cc; + } + + if (errno != 0) { + /* + * If the error is EAGAIN, just re-select on this + * fd and pretend nothing strange happened. + */ + if (SOFT_ERROR(errno) || errno == EINPROGRESS) { + sock->connecting = 1; + return; + } + + inc_stats(sock->manager->stats, + sock->statsindex[STATID_CONNECTFAIL]); + + /* + * Translate other errors into ISC_R_* flavors. + */ + switch (errno) { +#define ERROR_MATCH(a, b) \ + case a: \ + result = b; \ + break; + ERROR_MATCH(EACCES, ISC_R_NOPERM); + ERROR_MATCH(EADDRNOTAVAIL, ISC_R_ADDRNOTAVAIL); + ERROR_MATCH(EAFNOSUPPORT, ISC_R_ADDRNOTAVAIL); + ERROR_MATCH(ECONNREFUSED, ISC_R_CONNREFUSED); + ERROR_MATCH(EHOSTUNREACH, ISC_R_HOSTUNREACH); +#ifdef EHOSTDOWN + ERROR_MATCH(EHOSTDOWN, ISC_R_HOSTUNREACH); +#endif /* ifdef EHOSTDOWN */ + ERROR_MATCH(ENETUNREACH, ISC_R_NETUNREACH); + ERROR_MATCH(ENOBUFS, ISC_R_NORESOURCES); + ERROR_MATCH(EPERM, ISC_R_HOSTUNREACH); + ERROR_MATCH(EPIPE, ISC_R_NOTCONNECTED); + ERROR_MATCH(ETIMEDOUT, ISC_R_TIMEDOUT); + ERROR_MATCH(ECONNRESET, ISC_R_CONNECTIONRESET); +#undef ERROR_MATCH + default: + result = ISC_R_UNEXPECTED; + isc_sockaddr_format(&sock->peer_address, peerbuf, + sizeof(peerbuf)); + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "internal_connect: connect(%s) %s", + peerbuf, strbuf); + } + } else { + inc_stats(sock->manager->stats, + sock->statsindex[STATID_CONNECT]); + result = ISC_R_SUCCESS; + sock->connected = 1; + sock->bound = 1; + } + + do { + dev->result = result; + send_connectdone_event(sock, &dev); + dev = ISC_LIST_HEAD(sock->connect_list); + } while (dev != NULL); + +finish: + unwatch_fd(&sock->manager->threads[sock->threadid], sock->fd, + SELECT_POKE_CONNECT); +} + +isc_result_t +isc_socket_getpeername(isc_socket_t *sock, isc_sockaddr_t *addressp) { + isc_result_t result; + + REQUIRE(VALID_SOCKET(sock)); + REQUIRE(addressp != NULL); + + LOCK(&sock->lock); + + if (sock->connected) { + *addressp = sock->peer_address; + result = ISC_R_SUCCESS; + } else { + result = ISC_R_NOTCONNECTED; + } + + UNLOCK(&sock->lock); + + return (result); +} + +isc_result_t +isc_socket_getsockname(isc_socket_t *sock, isc_sockaddr_t *addressp) { + socklen_t len; + isc_result_t result; + char strbuf[ISC_STRERRORSIZE]; + + REQUIRE(VALID_SOCKET(sock)); + REQUIRE(addressp != NULL); + + LOCK(&sock->lock); + + if (!sock->bound) { + result = ISC_R_NOTBOUND; + goto out; + } + + result = ISC_R_SUCCESS; + + len = sizeof(addressp->type); + if (getsockname(sock->fd, &addressp->type.sa, (void *)&len) < 0) { + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, "getsockname: %s", strbuf); + result = ISC_R_UNEXPECTED; + goto out; + } + addressp->length = (unsigned int)len; + +out: + UNLOCK(&sock->lock); + + return (result); +} + +/* + * Run through the list of events on this socket, and cancel the ones + * queued for task "task" of type "how". "how" is a bitmask. + */ +void +isc_socket_cancel(isc_socket_t *sock, isc_task_t *task, unsigned int how) { + REQUIRE(VALID_SOCKET(sock)); + + /* + * Quick exit if there is nothing to do. Don't even bother locking + * in this case. + */ + if (how == 0) { + return; + } + + LOCK(&sock->lock); + + /* + * All of these do the same thing, more or less. + * Each will: + * o If the internal event is marked as "posted" try to + * remove it from the task's queue. If this fails, mark it + * as canceled instead, and let the task clean it up later. + * o For each I/O request for that task of that type, post + * its done event with status of "ISC_R_CANCELED". + * o Reset any state needed. + */ + if (((how & ISC_SOCKCANCEL_RECV) != 0) && + !ISC_LIST_EMPTY(sock->recv_list)) + { + isc_socketevent_t *dev; + isc_socketevent_t *next; + isc_task_t *current_task; + + dev = ISC_LIST_HEAD(sock->recv_list); + + while (dev != NULL) { + current_task = dev->ev_sender; + next = ISC_LIST_NEXT(dev, ev_link); + + if ((task == NULL) || (task == current_task)) { + dev->result = ISC_R_CANCELED; + send_recvdone_event(sock, &dev); + } + dev = next; + } + } + + if (((how & ISC_SOCKCANCEL_SEND) != 0) && + !ISC_LIST_EMPTY(sock->send_list)) + { + isc_socketevent_t *dev; + isc_socketevent_t *next; + isc_task_t *current_task; + + dev = ISC_LIST_HEAD(sock->send_list); + + while (dev != NULL) { + current_task = dev->ev_sender; + next = ISC_LIST_NEXT(dev, ev_link); + + if ((task == NULL) || (task == current_task)) { + dev->result = ISC_R_CANCELED; + send_senddone_event(sock, &dev); + } + dev = next; + } + } + + if (((how & ISC_SOCKCANCEL_ACCEPT) != 0) && + !ISC_LIST_EMPTY(sock->accept_list)) + { + isc_socket_newconnev_t *dev; + isc_socket_newconnev_t *next; + isc_task_t *current_task; + + dev = ISC_LIST_HEAD(sock->accept_list); + while (dev != NULL) { + current_task = dev->ev_sender; + next = ISC_LIST_NEXT(dev, ev_link); + + if ((task == NULL) || (task == current_task)) { + ISC_LIST_UNLINK(sock->accept_list, dev, + ev_link); + + isc_refcount_decrementz( + &NEWCONNSOCK(dev)->references); + free_socket((isc_socket_t **)&dev->newsocket); + + dev->result = ISC_R_CANCELED; + dev->ev_sender = sock; + isc_task_sendtoanddetach(¤t_task, + ISC_EVENT_PTR(&dev), + sock->threadid); + } + + dev = next; + } + } + + if (((how & ISC_SOCKCANCEL_CONNECT) != 0) && + !ISC_LIST_EMPTY(sock->connect_list)) + { + isc_socket_connev_t *dev; + isc_socket_connev_t *next; + isc_task_t *current_task; + + INSIST(sock->connecting); + sock->connecting = 0; + + dev = ISC_LIST_HEAD(sock->connect_list); + + while (dev != NULL) { + current_task = dev->ev_sender; + next = ISC_LIST_NEXT(dev, ev_link); + + if ((task == NULL) || (task == current_task)) { + dev->result = ISC_R_CANCELED; + send_connectdone_event(sock, &dev); + } + dev = next; + } + } + + UNLOCK(&sock->lock); +} + +isc_sockettype_t +isc_socket_gettype(isc_socket_t *sock) { + REQUIRE(VALID_SOCKET(sock)); + + return (sock->type); +} + +void +isc_socket_ipv6only(isc_socket_t *sock, bool yes) { +#if defined(IPV6_V6ONLY) && !defined(__OpenBSD__) + int onoff = yes ? 1 : 0; +#else /* if defined(IPV6_V6ONLY) */ + UNUSED(yes); + UNUSED(sock); +#endif /* if defined(IPV6_V6ONLY) */ + + REQUIRE(VALID_SOCKET(sock)); + INSIST(!sock->dupped); + +#if defined(IPV6_V6ONLY) && !defined(__OpenBSD__) + if (sock->pf == AF_INET6) { + if (setsockopt(sock->fd, IPPROTO_IPV6, IPV6_V6ONLY, + (void *)&onoff, sizeof(int)) < 0) + { + char strbuf[ISC_STRERRORSIZE]; + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "setsockopt(%d, IPV6_V6ONLY) failed: " + "%s", + sock->fd, strbuf); + } + } +#endif /* ifdef IPV6_V6ONLY */ +} + +static void +setdscp(isc_socket_t *sock, isc_dscp_t dscp) { +#if defined(IP_TOS) || defined(IPV6_TCLASS) + int value = dscp << 2; +#endif /* if defined(IP_TOS) || defined(IPV6_TCLASS) */ + + sock->dscp = dscp; + +#ifdef IP_TOS + if (sock->pf == AF_INET) { + if (setsockopt(sock->fd, IPPROTO_IP, IP_TOS, (void *)&value, + sizeof(value)) < 0) + { + char strbuf[ISC_STRERRORSIZE]; + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "setsockopt(%d, IP_TOS, %.02x) " + "failed: %s", + sock->fd, value >> 2, strbuf); + } + } +#endif /* ifdef IP_TOS */ +#ifdef IPV6_TCLASS + if (sock->pf == AF_INET6) { + if (setsockopt(sock->fd, IPPROTO_IPV6, IPV6_TCLASS, + (void *)&value, sizeof(value)) < 0) + { + char strbuf[ISC_STRERRORSIZE]; + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "setsockopt(%d, IPV6_TCLASS, %.02x) " + "failed: %s", + sock->fd, dscp >> 2, strbuf); + } + } +#endif /* ifdef IPV6_TCLASS */ +} + +void +isc_socket_dscp(isc_socket_t *sock, isc_dscp_t dscp) { + REQUIRE(VALID_SOCKET(sock)); + REQUIRE(dscp < 0x40); + +#if !defined(IP_TOS) && !defined(IPV6_TCLASS) + UNUSED(dscp); +#else /* if !defined(IP_TOS) && !defined(IPV6_TCLASS) */ + if (dscp < 0) { + return; + } + + /* The DSCP value must not be changed once it has been set. */ + if (isc_dscp_check_value != -1) { + INSIST(dscp == isc_dscp_check_value); + } +#endif /* if !defined(IP_TOS) && !defined(IPV6_TCLASS) */ + +#ifdef notyet + REQUIRE(!sock->dupped); +#endif /* ifdef notyet */ + + setdscp(sock, dscp); +} + +isc_socketevent_t * +isc_socket_socketevent(isc_mem_t *mctx, void *sender, isc_eventtype_t eventtype, + isc_taskaction_t action, void *arg) { + return (allocate_socketevent(mctx, sender, eventtype, action, arg)); +} + +void +isc_socket_setname(isc_socket_t *sock, const char *name, void *tag) { + /* + * Name 'sock'. + */ + + REQUIRE(VALID_SOCKET(sock)); + + LOCK(&sock->lock); + strlcpy(sock->name, name, sizeof(sock->name)); + sock->tag = tag; + UNLOCK(&sock->lock); +} + +const char * +isc_socket_getname(isc_socket_t *sock) { + return (sock->name); +} + +void * +isc_socket_gettag(isc_socket_t *sock) { + return (sock->tag); +} + +int +isc_socket_getfd(isc_socket_t *sock) { + return ((short)sock->fd); +} + +static isc_once_t hasreuseport_once = ISC_ONCE_INIT; +static bool hasreuseport = false; + +static void +init_hasreuseport() { +/* + * SO_REUSEPORT works very differently on *BSD and on Linux (because why not). + * We only want to use it on Linux, if it's available. On BSD we want to dup() + * sockets instead of re-binding them. + */ +#if (defined(SO_REUSEPORT) && defined(__linux__)) || \ + (defined(SO_REUSEPORT_LB) && defined(__FreeBSD_kernel__)) + int sock, yes = 1; + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + sock = socket(AF_INET6, SOCK_DGRAM, 0); + if (sock < 0) { + return; + } + } + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&yes, + sizeof(yes)) < 0) + { + close(sock); + return; +#if defined(__FreeBSD_kernel__) + } else if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT_LB, (void *)&yes, + sizeof(yes)) < 0) +#else /* if defined(__FreeBSD_kernel__) */ + } else if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, (void *)&yes, + sizeof(yes)) < 0) +#endif /* if defined(__FreeBSD_kernel__) */ + { + close(sock); + return; + } + hasreuseport = true; + close(sock); +#endif /* if (defined(SO_REUSEPORT) && defined(__linux__)) || \ + * (defined(SO_REUSEPORT_LB) && defined(__FreeBSD_kernel__)) */ +} + +bool +isc_socket_hasreuseport() { + RUNTIME_CHECK(isc_once_do(&hasreuseport_once, init_hasreuseport) == + ISC_R_SUCCESS); + return (hasreuseport); +} + +#if defined(HAVE_LIBXML2) || defined(HAVE_JSON_C) +static const char * +_socktype(isc_sockettype_t type) { + switch (type) { + case isc_sockettype_udp: + return ("udp"); + case isc_sockettype_tcp: + return ("tcp"); + case isc_sockettype_unix: + return ("unix"); + default: + return ("not-initialized"); + } +} +#endif /* if defined(HAVE_LIBXML2) || defined(HAVE_JSON_C) */ + +#ifdef HAVE_LIBXML2 +#define TRY0(a) \ + do { \ + xmlrc = (a); \ + if (xmlrc < 0) \ + goto error; \ + } while (0) +int +isc_socketmgr_renderxml(isc_socketmgr_t *mgr, void *writer0) { + isc_socket_t *sock = NULL; + char peerbuf[ISC_SOCKADDR_FORMATSIZE]; + isc_sockaddr_t addr; + socklen_t len; + int xmlrc; + xmlTextWriterPtr writer = (xmlTextWriterPtr)writer0; + + LOCK(&mgr->lock); + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "sockets")); + sock = ISC_LIST_HEAD(mgr->socklist); + while (sock != NULL) { + LOCK(&sock->lock); + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "socket")); + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "id")); + TRY0(xmlTextWriterWriteFormatString(writer, "%p", sock)); + TRY0(xmlTextWriterEndElement(writer)); + + if (sock->name[0] != 0) { + TRY0(xmlTextWriterStartElement(writer, + ISC_XMLCHAR "name")); + TRY0(xmlTextWriterWriteFormatString(writer, "%s", + sock->name)); + TRY0(xmlTextWriterEndElement(writer)); /* name */ + } + + TRY0(xmlTextWriterStartElement(writer, + ISC_XMLCHAR "references")); + TRY0(xmlTextWriterWriteFormatString( + writer, "%d", + (int)isc_refcount_current(&sock->references))); + TRY0(xmlTextWriterEndElement(writer)); + + TRY0(xmlTextWriterWriteElement( + writer, ISC_XMLCHAR "type", + ISC_XMLCHAR _socktype(sock->type))); + + if (sock->connected) { + isc_sockaddr_format(&sock->peer_address, peerbuf, + sizeof(peerbuf)); + TRY0(xmlTextWriterWriteElement( + writer, ISC_XMLCHAR "peer-address", + ISC_XMLCHAR peerbuf)); + } + + len = sizeof(addr); + if (getsockname(sock->fd, &addr.type.sa, (void *)&len) == 0) { + isc_sockaddr_format(&addr, peerbuf, sizeof(peerbuf)); + TRY0(xmlTextWriterWriteElement( + writer, ISC_XMLCHAR "local-address", + ISC_XMLCHAR peerbuf)); + } + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "states")); + if (sock->listener) { + TRY0(xmlTextWriterWriteElement(writer, + ISC_XMLCHAR "state", + ISC_XMLCHAR "listener")); + } + if (sock->connected) { + TRY0(xmlTextWriterWriteElement( + writer, ISC_XMLCHAR "state", + ISC_XMLCHAR "connected")); + } + if (sock->connecting) { + TRY0(xmlTextWriterWriteElement( + writer, ISC_XMLCHAR "state", + ISC_XMLCHAR "connecting")); + } + if (sock->bound) { + TRY0(xmlTextWriterWriteElement(writer, + ISC_XMLCHAR "state", + ISC_XMLCHAR "bound")); + } + + TRY0(xmlTextWriterEndElement(writer)); /* states */ + + TRY0(xmlTextWriterEndElement(writer)); /* socket */ + + UNLOCK(&sock->lock); + sock = ISC_LIST_NEXT(sock, link); + } + TRY0(xmlTextWriterEndElement(writer)); /* sockets */ + +error: + if (sock != NULL) { + UNLOCK(&sock->lock); + } + + UNLOCK(&mgr->lock); + + return (xmlrc); +} +#endif /* HAVE_LIBXML2 */ + +#ifdef HAVE_JSON_C +#define CHECKMEM(m) \ + do { \ + if (m == NULL) { \ + result = ISC_R_NOMEMORY; \ + goto error; \ + } \ + } while (0) + +isc_result_t +isc_socketmgr_renderjson(isc_socketmgr_t *mgr, void *stats0) { + isc_result_t result = ISC_R_SUCCESS; + isc_socket_t *sock = NULL; + char peerbuf[ISC_SOCKADDR_FORMATSIZE]; + isc_sockaddr_t addr; + socklen_t len; + json_object *obj, *array = json_object_new_array(); + json_object *stats = (json_object *)stats0; + + CHECKMEM(array); + + LOCK(&mgr->lock); + + sock = ISC_LIST_HEAD(mgr->socklist); + while (sock != NULL) { + json_object *states, *entry = json_object_new_object(); + char buf[255]; + + CHECKMEM(entry); + json_object_array_add(array, entry); + + LOCK(&sock->lock); + + snprintf(buf, sizeof(buf), "%p", sock); + obj = json_object_new_string(buf); + CHECKMEM(obj); + json_object_object_add(entry, "id", obj); + + if (sock->name[0] != 0) { + obj = json_object_new_string(sock->name); + CHECKMEM(obj); + json_object_object_add(entry, "name", obj); + } + + obj = json_object_new_int( + (int)isc_refcount_current(&sock->references)); + CHECKMEM(obj); + json_object_object_add(entry, "references", obj); + + obj = json_object_new_string(_socktype(sock->type)); + CHECKMEM(obj); + json_object_object_add(entry, "type", obj); + + if (sock->connected) { + isc_sockaddr_format(&sock->peer_address, peerbuf, + sizeof(peerbuf)); + obj = json_object_new_string(peerbuf); + CHECKMEM(obj); + json_object_object_add(entry, "peer-address", obj); + } + + len = sizeof(addr); + if (getsockname(sock->fd, &addr.type.sa, (void *)&len) == 0) { + isc_sockaddr_format(&addr, peerbuf, sizeof(peerbuf)); + obj = json_object_new_string(peerbuf); + CHECKMEM(obj); + json_object_object_add(entry, "local-address", obj); + } + + states = json_object_new_array(); + CHECKMEM(states); + json_object_object_add(entry, "states", states); + + if (sock->listener) { + obj = json_object_new_string("listener"); + CHECKMEM(obj); + json_object_array_add(states, obj); + } + + if (sock->connected) { + obj = json_object_new_string("connected"); + CHECKMEM(obj); + json_object_array_add(states, obj); + } + + if (sock->connecting) { + obj = json_object_new_string("connecting"); + CHECKMEM(obj); + json_object_array_add(states, obj); + } + + if (sock->bound) { + obj = json_object_new_string("bound"); + CHECKMEM(obj); + json_object_array_add(states, obj); + } + + UNLOCK(&sock->lock); + sock = ISC_LIST_NEXT(sock, link); + } + + json_object_object_add(stats, "sockets", array); + array = NULL; + result = ISC_R_SUCCESS; + +error: + if (array != NULL) { + json_object_put(array); + } + + if (sock != NULL) { + UNLOCK(&sock->lock); + } + + UNLOCK(&mgr->lock); + + return (result); +} +#endif /* HAVE_JSON_C */ diff --git a/lib/isc/unix/socket_p.h b/lib/isc/unix/socket_p.h new file mode 100644 index 0000000..3323cab --- /dev/null +++ b/lib/isc/unix/socket_p.h @@ -0,0 +1,27 @@ +/* + * 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. + */ + +#ifndef ISC_SOCKET_P_H +#define ISC_SOCKET_P_H + +/*! \file */ + +#include <sys/time.h> + +typedef struct isc_socketwait isc_socketwait_t; +int +isc__socketmgr_waitevents(isc_socketmgr_t *, struct timeval *, + isc_socketwait_t **); +isc_result_t +isc__socketmgr_dispatch(isc_socketmgr_t *, isc_socketwait_t *); +#endif /* ISC_SOCKET_P_H */ diff --git a/lib/isc/unix/stdio.c b/lib/isc/unix/stdio.c new file mode 100644 index 0000000..12ab678 --- /dev/null +++ b/lib/isc/unix/stdio.c @@ -0,0 +1,152 @@ +/* + * 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. + */ + +#include <errno.h> +#include <unistd.h> + +#include <isc/stat.h> +#include <isc/stdio.h> +#include <isc/util.h> + +#include "errno2result.h" + +isc_result_t +isc_stdio_open(const char *filename, const char *mode, FILE **fp) { + FILE *f; + + f = fopen(filename, mode); + if (f == NULL) { + return (isc__errno2result(errno)); + } + *fp = f; + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_stdio_close(FILE *f) { + int r; + + r = fclose(f); + if (r == 0) { + return (ISC_R_SUCCESS); + } else { + return (isc__errno2result(errno)); + } +} + +isc_result_t +isc_stdio_seek(FILE *f, off_t offset, int whence) { + int r; + + r = fseeko(f, offset, whence); + if (r == 0) { + return (ISC_R_SUCCESS); + } else { + return (isc__errno2result(errno)); + } +} + +isc_result_t +isc_stdio_tell(FILE *f, off_t *offsetp) { + off_t r; + + REQUIRE(offsetp != NULL); + + r = ftello(f); + if (r >= 0) { + *offsetp = r; + return (ISC_R_SUCCESS); + } else { + return (isc__errno2result(errno)); + } +} + +isc_result_t +isc_stdio_read(void *ptr, size_t size, size_t nmemb, FILE *f, size_t *nret) { + isc_result_t result = ISC_R_SUCCESS; + size_t r; + + clearerr(f); + r = fread(ptr, size, nmemb, f); + if (r != nmemb) { + if (feof(f)) { + result = ISC_R_EOF; + } else { + result = isc__errno2result(errno); + } + } + if (nret != NULL) { + *nret = r; + } + return (result); +} + +isc_result_t +isc_stdio_write(const void *ptr, size_t size, size_t nmemb, FILE *f, + size_t *nret) { + isc_result_t result = ISC_R_SUCCESS; + size_t r; + + clearerr(f); + r = fwrite(ptr, size, nmemb, f); + if (r != nmemb) { + result = isc__errno2result(errno); + } + if (nret != NULL) { + *nret = r; + } + return (result); +} + +isc_result_t +isc_stdio_flush(FILE *f) { + int r; + + r = fflush(f); + if (r == 0) { + return (ISC_R_SUCCESS); + } else { + return (isc__errno2result(errno)); + } +} + +/* + * OpenBSD has deprecated ENOTSUP in favor of EOPNOTSUPP. + */ +#if defined(EOPNOTSUPP) && !defined(ENOTSUP) +#define ENOTSUP EOPNOTSUPP +#endif /* if defined(EOPNOTSUPP) && !defined(ENOTSUP) */ + +isc_result_t +isc_stdio_sync(FILE *f) { + struct stat buf; + int r; + + if (fstat(fileno(f), &buf) != 0) { + return (isc__errno2result(errno)); + } + + /* + * Only call fsync() on regular files. + */ + if ((buf.st_mode & S_IFMT) != S_IFREG) { + return (ISC_R_SUCCESS); + } + + r = fsync(fileno(f)); + if (r == 0) { + return (ISC_R_SUCCESS); + } else { + return (isc__errno2result(errno)); + } +} diff --git a/lib/isc/unix/stdtime.c b/lib/isc/unix/stdtime.c new file mode 100644 index 0000000..ada19d0 --- /dev/null +++ b/lib/isc/unix/stdtime.c @@ -0,0 +1,68 @@ +/* + * 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. + */ + +/*! \file */ + +#include <errno.h> +#include <stdbool.h> +#include <stddef.h> /* NULL */ +#include <stdlib.h> /* NULL */ +#include <syslog.h> +#include <time.h> + +#include <isc/stdtime.h> +#include <isc/strerr.h> +#include <isc/util.h> + +#define NS_PER_S 1000000000 /*%< Nanoseconds per second. */ + +#if defined(CLOCK_REALTIME_COARSE) +#define CLOCKSOURCE CLOCK_REALTIME_COARSE +#elif defined(CLOCK_REALTIME_FAST) +#define CLOCKSOURCE CLOCK_REALTIME_FAST +#else /* if defined(CLOCK_REALTIME_COARSE) */ +#define CLOCKSOURCE CLOCK_REALTIME +#endif /* if defined(CLOCK_REALTIME_COARSE) */ + +void +isc_stdtime_get(isc_stdtime_t *t) { + REQUIRE(t != NULL); + + struct timespec ts; + + if (clock_gettime(CLOCKSOURCE, &ts) == -1) { + char strbuf[ISC_STRERRORSIZE]; + strerror_r(errno, strbuf, sizeof(strbuf)); + isc_error_fatal(__FILE__, __LINE__, "clock_gettime failed: %s", + strbuf); + } + + REQUIRE(ts.tv_sec > 0 && ts.tv_nsec >= 0 && ts.tv_nsec < NS_PER_S); + + *t = (isc_stdtime_t)ts.tv_sec; +} + +void +isc_stdtime_tostring(isc_stdtime_t t, char *out, size_t outlen) { + time_t when; + + REQUIRE(out != NULL); + REQUIRE(outlen >= 26); + + UNUSED(outlen); + + /* time_t and isc_stdtime_t might be different sizes */ + when = t; + INSIST((ctime_r(&when, out) != NULL)); + *(out + strlen(out) - 1) = '\0'; +} diff --git a/lib/isc/unix/syslog.c b/lib/isc/unix/syslog.c new file mode 100644 index 0000000..81b99ca --- /dev/null +++ b/lib/isc/unix/syslog.c @@ -0,0 +1,73 @@ +/* + * 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. + */ + +/*! \file */ + +#include <stdlib.h> +#include <syslog.h> + +#include <isc/result.h> +#include <isc/string.h> +#include <isc/syslog.h> +#include <isc/util.h> + +static struct dsn_c_pvt_sfnt { + int val; + const char *strval; +} facilities[] = { { LOG_KERN, "kern" }, + { LOG_USER, "user" }, + { LOG_MAIL, "mail" }, + { LOG_DAEMON, "daemon" }, + { LOG_AUTH, "auth" }, + { LOG_SYSLOG, "syslog" }, + { LOG_LPR, "lpr" }, +#ifdef LOG_NEWS + { LOG_NEWS, "news" }, +#endif /* ifdef LOG_NEWS */ +#ifdef LOG_UUCP + { LOG_UUCP, "uucp" }, +#endif /* ifdef LOG_UUCP */ +#ifdef LOG_CRON + { LOG_CRON, "cron" }, +#endif /* ifdef LOG_CRON */ +#ifdef LOG_AUTHPRIV + { LOG_AUTHPRIV, "authpriv" }, +#endif /* ifdef LOG_AUTHPRIV */ +#ifdef LOG_FTP + { LOG_FTP, "ftp" }, +#endif /* ifdef LOG_FTP */ + { LOG_LOCAL0, "local0" }, + { LOG_LOCAL1, "local1" }, + { LOG_LOCAL2, "local2" }, + { LOG_LOCAL3, "local3" }, + { LOG_LOCAL4, "local4" }, + { LOG_LOCAL5, "local5" }, + { LOG_LOCAL6, "local6" }, + { LOG_LOCAL7, "local7" }, + { 0, NULL } }; + +isc_result_t +isc_syslog_facilityfromstring(const char *str, int *facilityp) { + int i; + + REQUIRE(str != NULL); + REQUIRE(facilityp != NULL); + + for (i = 0; facilities[i].strval != NULL; i++) { + if (strcasecmp(facilities[i].strval, str) == 0) { + *facilityp = facilities[i].val; + return (ISC_R_SUCCESS); + } + } + return (ISC_R_NOTFOUND); +} diff --git a/lib/isc/unix/time.c b/lib/isc/unix/time.c new file mode 100644 index 0000000..02291e4 --- /dev/null +++ b/lib/isc/unix/time.c @@ -0,0 +1,553 @@ +/* + * 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. + */ + +/*! \file */ + +#include <errno.h> +#include <inttypes.h> +#include <limits.h> +#include <stdbool.h> +#include <stdlib.h> +#include <sys/time.h> /* Required for struct timeval on some platforms. */ +#include <syslog.h> +#include <time.h> + +#include <isc/log.h> +#include <isc/platform.h> +#include <isc/print.h> +#include <isc/strerr.h> +#include <isc/string.h> +#include <isc/time.h> +#include <isc/tm.h> +#include <isc/util.h> + +#define NS_PER_S 1000000000 /*%< Nanoseconds per second. */ +#define NS_PER_US 1000 /*%< Nanoseconds per microsecond. */ +#define NS_PER_MS 1000000 /*%< Nanoseconds per millisecond. */ + +#if defined(CLOCK_REALTIME) +#define CLOCKSOURCE_HIRES CLOCK_REALTIME +#endif /* #if defined(CLOCK_REALTIME) */ + +#if defined(CLOCK_REALTIME_COARSE) +#define CLOCKSOURCE CLOCK_REALTIME_COARSE +#elif defined(CLOCK_REALTIME_FAST) +#define CLOCKSOURCE CLOCK_REALTIME_FAST +#else /* if defined(CLOCK_REALTIME_COARSE) */ +#define CLOCKSOURCE CLOCK_REALTIME +#endif /* if defined(CLOCK_REALTIME_COARSE) */ + +#if !defined(CLOCKSOURCE_HIRES) +#define CLOCKSOURCE_HIRES CLOCKSOURCE +#endif /* #ifndef CLOCKSOURCE_HIRES */ + +/*% + *** Intervals + ***/ + +#if !defined(UNIT_TESTING) +static const isc_interval_t zero_interval = { 0, 0 }; +const isc_interval_t *const isc_interval_zero = &zero_interval; +#endif + +void +isc_interval_set(isc_interval_t *i, unsigned int seconds, + unsigned int nanoseconds) { + REQUIRE(i != NULL); + REQUIRE(nanoseconds < NS_PER_S); + + i->seconds = seconds; + i->nanoseconds = nanoseconds; +} + +bool +isc_interval_iszero(const isc_interval_t *i) { + REQUIRE(i != NULL); + INSIST(i->nanoseconds < NS_PER_S); + + if (i->seconds == 0 && i->nanoseconds == 0) { + return (true); + } + + return (false); +} + +/*** + *** Absolute Times + ***/ + +#if !defined(UNIT_TESTING) +static const isc_time_t epoch = { 0, 0 }; +const isc_time_t *const isc_time_epoch = &epoch; +#endif + +void +isc_time_set(isc_time_t *t, unsigned int seconds, unsigned int nanoseconds) { + REQUIRE(t != NULL); + REQUIRE(nanoseconds < NS_PER_S); + + t->seconds = seconds; + t->nanoseconds = nanoseconds; +} + +void +isc_time_settoepoch(isc_time_t *t) { + REQUIRE(t != NULL); + + t->seconds = 0; + t->nanoseconds = 0; +} + +bool +isc_time_isepoch(const isc_time_t *t) { + REQUIRE(t != NULL); + INSIST(t->nanoseconds < NS_PER_S); + + if (t->seconds == 0 && t->nanoseconds == 0) { + return (true); + } + + return (false); +} + +static isc_result_t +time_now(isc_time_t *t, clockid_t clock) { + struct timespec ts; + char strbuf[ISC_STRERRORSIZE]; + + REQUIRE(t != NULL); + + if (clock_gettime(clock, &ts) == -1) { + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, "%s", strbuf); + return (ISC_R_UNEXPECTED); + } + + if (ts.tv_sec < 0 || ts.tv_nsec < 0 || ts.tv_nsec >= NS_PER_S) { + return (ISC_R_UNEXPECTED); + } + + /* + * Ensure the tv_sec value fits in t->seconds. + */ + if (sizeof(ts.tv_sec) > sizeof(t->seconds) && + ((ts.tv_sec | (unsigned int)-1) ^ (unsigned int)-1) != 0U) + { + return (ISC_R_RANGE); + } + + t->seconds = ts.tv_sec; + t->nanoseconds = ts.tv_nsec; + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_time_now_hires(isc_time_t *t) { + return time_now(t, CLOCKSOURCE_HIRES); +} + +isc_result_t +isc_time_now(isc_time_t *t) { + return time_now(t, CLOCKSOURCE); +} + +isc_result_t +isc_time_nowplusinterval(isc_time_t *t, const isc_interval_t *i) { + struct timespec ts; + char strbuf[ISC_STRERRORSIZE]; + + REQUIRE(t != NULL); + REQUIRE(i != NULL); + INSIST(i->nanoseconds < NS_PER_S); + + if (clock_gettime(CLOCKSOURCE, &ts) == -1) { + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, "%s", strbuf); + return (ISC_R_UNEXPECTED); + } + + if (ts.tv_sec < 0 || ts.tv_nsec < 0 || ts.tv_nsec >= NS_PER_S) { + return (ISC_R_UNEXPECTED); + } + + /* + * Ensure the resulting seconds value fits in the size of an + * unsigned int. (It is written this way as a slight optimization; + * note that even if both values == INT_MAX, then when added + * and getting another 1 added below the result is UINT_MAX.) + */ + if ((ts.tv_sec > INT_MAX || i->seconds > INT_MAX) && + ((long long)ts.tv_sec + i->seconds > UINT_MAX)) + { + return (ISC_R_RANGE); + } + + t->seconds = ts.tv_sec + i->seconds; + t->nanoseconds = ts.tv_nsec + i->nanoseconds; + if (t->nanoseconds >= NS_PER_S) { + t->seconds++; + t->nanoseconds -= NS_PER_S; + } + + return (ISC_R_SUCCESS); +} + +int +isc_time_compare(const isc_time_t *t1, const isc_time_t *t2) { + REQUIRE(t1 != NULL && t2 != NULL); + INSIST(t1->nanoseconds < NS_PER_S && t2->nanoseconds < NS_PER_S); + + if (t1->seconds < t2->seconds) { + return (-1); + } + if (t1->seconds > t2->seconds) { + return (1); + } + if (t1->nanoseconds < t2->nanoseconds) { + return (-1); + } + if (t1->nanoseconds > t2->nanoseconds) { + return (1); + } + return (0); +} + +isc_result_t +isc_time_add(const isc_time_t *t, const isc_interval_t *i, isc_time_t *result) { + REQUIRE(t != NULL && i != NULL && result != NULL); + REQUIRE(t->nanoseconds < NS_PER_S && i->nanoseconds < NS_PER_S); + + /* Seconds */ + if (t->seconds > UINT_MAX - i->seconds) { + return (ISC_R_RANGE); + } + result->seconds = t->seconds + i->seconds; + + /* Nanoseconds */ + result->nanoseconds = t->nanoseconds + i->nanoseconds; + if (result->nanoseconds >= NS_PER_S) { + if (result->seconds == UINT_MAX) { + return (ISC_R_RANGE); + } + result->nanoseconds -= NS_PER_S; + result->seconds++; + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_time_subtract(const isc_time_t *t, const isc_interval_t *i, + isc_time_t *result) { + REQUIRE(t != NULL && i != NULL && result != NULL); + REQUIRE(t->nanoseconds < NS_PER_S && i->nanoseconds < NS_PER_S); + + /* Seconds */ + if (t->seconds < i->seconds) { + return (ISC_R_RANGE); + } + result->seconds = t->seconds - i->seconds; + + /* Nanoseconds */ + if (t->nanoseconds >= i->nanoseconds) { + result->nanoseconds = t->nanoseconds - i->nanoseconds; + } else { + if (result->seconds == 0) { + return (ISC_R_RANGE); + } + result->seconds--; + result->nanoseconds = NS_PER_S + t->nanoseconds - + i->nanoseconds; + } + + return (ISC_R_SUCCESS); +} + +uint64_t +isc_time_microdiff(const isc_time_t *t1, const isc_time_t *t2) { + uint64_t i1, i2, i3; + + REQUIRE(t1 != NULL && t2 != NULL); + INSIST(t1->nanoseconds < NS_PER_S && t2->nanoseconds < NS_PER_S); + + i1 = (uint64_t)t1->seconds * NS_PER_S + t1->nanoseconds; + i2 = (uint64_t)t2->seconds * NS_PER_S + t2->nanoseconds; + + if (i1 <= i2) { + return (0); + } + + i3 = i1 - i2; + + /* + * Convert to microseconds. + */ + i3 /= NS_PER_US; + + return (i3); +} + +uint32_t +isc_time_seconds(const isc_time_t *t) { + REQUIRE(t != NULL); + INSIST(t->nanoseconds < NS_PER_S); + + return ((uint32_t)t->seconds); +} + +isc_result_t +isc_time_secondsastimet(const isc_time_t *t, time_t *secondsp) { + time_t seconds; + + REQUIRE(t != NULL); + INSIST(t->nanoseconds < NS_PER_S); + + /* + * Ensure that the number of seconds represented by t->seconds + * can be represented by a time_t. Since t->seconds is an + * unsigned int and since time_t is mostly opaque, this is + * trickier than it seems. (This standardized opaqueness of + * time_t is *very* frustrating; time_t is not even limited to + * being an integral type.) + * + * The mission, then, is to avoid generating any kind of warning + * about "signed versus unsigned" while trying to determine if + * the unsigned int t->seconds is out range for tv_sec, + * which is pretty much only true if time_t is a signed integer + * of the same size as the return value of isc_time_seconds. + * + * If the paradox in the if clause below is true, t->seconds is + * out of range for time_t. + */ + seconds = (time_t)t->seconds; + + INSIST(sizeof(unsigned int) == sizeof(uint32_t)); + INSIST(sizeof(time_t) >= sizeof(uint32_t)); + + if (t->seconds > (~0U >> 1) && seconds <= (time_t)(~0U >> 1)) { + return (ISC_R_RANGE); + } + + *secondsp = seconds; + + return (ISC_R_SUCCESS); +} + +uint32_t +isc_time_nanoseconds(const isc_time_t *t) { + REQUIRE(t != NULL); + + ENSURE(t->nanoseconds < NS_PER_S); + + return ((uint32_t)t->nanoseconds); +} + +void +isc_time_formattimestamp(const isc_time_t *t, char *buf, unsigned int len) { + time_t now; + unsigned int flen; + struct tm tm; + + REQUIRE(t != NULL); + INSIST(t->nanoseconds < NS_PER_S); + REQUIRE(buf != NULL); + REQUIRE(len > 0); + + now = (time_t)t->seconds; + flen = strftime(buf, len, "%d-%b-%Y %X", localtime_r(&now, &tm)); + INSIST(flen < len); + if (flen != 0) { + snprintf(buf + flen, len - flen, ".%03u", + t->nanoseconds / NS_PER_MS); + } else { + strlcpy(buf, "99-Bad-9999 99:99:99.999", len); + } +} + +void +isc_time_formathttptimestamp(const isc_time_t *t, char *buf, unsigned int len) { + time_t now; + unsigned int flen; + struct tm tm; + + REQUIRE(t != NULL); + INSIST(t->nanoseconds < NS_PER_S); + REQUIRE(buf != NULL); + REQUIRE(len > 0); + + /* + * 5 spaces, 1 comma, 3 GMT, 2 %d, 4 %Y, 8 %H:%M:%S, 3+ %a, 3+ + * %b (29+) + */ + now = (time_t)t->seconds; + flen = strftime(buf, len, "%a, %d %b %Y %H:%M:%S GMT", + gmtime_r(&now, &tm)); + INSIST(flen < len); +} + +isc_result_t +isc_time_parsehttptimestamp(char *buf, isc_time_t *t) { + struct tm t_tm; + time_t when; + char *p; + + REQUIRE(buf != NULL); + REQUIRE(t != NULL); + + p = isc_tm_strptime(buf, "%a, %d %b %Y %H:%M:%S", &t_tm); + if (p == NULL) { + return (ISC_R_UNEXPECTED); + } + when = isc_tm_timegm(&t_tm); + if (when == -1) { + return (ISC_R_UNEXPECTED); + } + isc_time_set(t, when, 0); + return (ISC_R_SUCCESS); +} + +void +isc_time_formatISO8601L(const isc_time_t *t, char *buf, unsigned int len) { + time_t now; + unsigned int flen; + struct tm tm; + + REQUIRE(t != NULL); + INSIST(t->nanoseconds < NS_PER_S); + REQUIRE(buf != NULL); + REQUIRE(len > 0); + + now = (time_t)t->seconds; + flen = strftime(buf, len, "%Y-%m-%dT%H:%M:%S", localtime_r(&now, &tm)); + INSIST(flen < len); +} + +void +isc_time_formatISO8601Lms(const isc_time_t *t, char *buf, unsigned int len) { + time_t now; + unsigned int flen; + struct tm tm; + + REQUIRE(t != NULL); + INSIST(t->nanoseconds < NS_PER_S); + REQUIRE(buf != NULL); + REQUIRE(len > 0); + + now = (time_t)t->seconds; + flen = strftime(buf, len, "%Y-%m-%dT%H:%M:%S", localtime_r(&now, &tm)); + INSIST(flen < len); + if (flen > 0U && len - flen >= 6) { + snprintf(buf + flen, len - flen, ".%03u", + t->nanoseconds / NS_PER_MS); + } +} + +void +isc_time_formatISO8601Lus(const isc_time_t *t, char *buf, unsigned int len) { + time_t now; + unsigned int flen; + struct tm tm; + + REQUIRE(t != NULL); + INSIST(t->nanoseconds < NS_PER_S); + REQUIRE(buf != NULL); + REQUIRE(len > 0); + + now = (time_t)t->seconds; + flen = strftime(buf, len, "%Y-%m-%dT%H:%M:%S", localtime_r(&now, &tm)); + INSIST(flen < len); + if (flen > 0U && len - flen >= 6) { + snprintf(buf + flen, len - flen, ".%06u", + t->nanoseconds / NS_PER_US); + } +} + +void +isc_time_formatISO8601(const isc_time_t *t, char *buf, unsigned int len) { + time_t now; + unsigned int flen; + struct tm tm; + + REQUIRE(t != NULL); + INSIST(t->nanoseconds < NS_PER_S); + REQUIRE(buf != NULL); + REQUIRE(len > 0); + + now = (time_t)t->seconds; + flen = strftime(buf, len, "%Y-%m-%dT%H:%M:%SZ", gmtime_r(&now, &tm)); + INSIST(flen < len); +} + +void +isc_time_formatISO8601ms(const isc_time_t *t, char *buf, unsigned int len) { + time_t now; + unsigned int flen; + struct tm tm; + + REQUIRE(t != NULL); + INSIST(t->nanoseconds < NS_PER_S); + REQUIRE(buf != NULL); + REQUIRE(len > 0); + + now = (time_t)t->seconds; + flen = strftime(buf, len, "%Y-%m-%dT%H:%M:%SZ", gmtime_r(&now, &tm)); + INSIST(flen < len); + if (flen > 0U && len - flen >= 5) { + flen -= 1; /* rewind one character (Z) */ + snprintf(buf + flen, len - flen, ".%03uZ", + t->nanoseconds / NS_PER_MS); + } +} + +void +isc_time_formatISO8601us(const isc_time_t *t, char *buf, unsigned int len) { + time_t now; + unsigned int flen; + struct tm tm; + + REQUIRE(t != NULL); + INSIST(t->nanoseconds < NS_PER_S); + REQUIRE(buf != NULL); + REQUIRE(len > 0); + + now = (time_t)t->seconds; + flen = strftime(buf, len, "%Y-%m-%dT%H:%M:%SZ", gmtime_r(&now, &tm)); + INSIST(flen < len); + if (flen > 0U && len - flen >= 5) { + flen -= 1; /* rewind one character (Z) */ + snprintf(buf + flen, len - flen, ".%06uZ", + t->nanoseconds / NS_PER_US); + } +} + +void +isc_time_formatshorttimestamp(const isc_time_t *t, char *buf, + unsigned int len) { + time_t now; + unsigned int flen; + struct tm tm; + + REQUIRE(t != NULL); + INSIST(t->nanoseconds < NS_PER_S); + REQUIRE(buf != NULL); + REQUIRE(len > 0); + + now = (time_t)t->seconds; + flen = strftime(buf, len, "%Y%m%d%H%M%S", gmtime_r(&now, &tm)); + INSIST(flen < len); + if (flen > 0U && len - flen >= 5) { + snprintf(buf + flen, len - flen, "%03u", + t->nanoseconds / NS_PER_MS); + } +} diff --git a/lib/isc/url.c b/lib/isc/url.c new file mode 100644 index 0000000..cccb712 --- /dev/null +++ b/lib/isc/url.c @@ -0,0 +1,671 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 and MIT + * + * 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. + */ + +/* + * Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <ctype.h> +#include <limits.h> +#include <stddef.h> +#include <string.h> + +#include <isc/url.h> +#include <isc/util.h> + +#ifndef BIT_AT +#define BIT_AT(a, i) \ + (!!((unsigned int)(a)[(unsigned int)(i) >> 3] & \ + (1 << ((unsigned int)(i)&7)))) +#endif + +#if HTTP_PARSER_STRICT +#define T(v) 0 +#else +#define T(v) v +#endif + +static const uint8_t normal_url_char[32] = { + /* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, + /* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ + 0 | T(2) | 0 | 0 | T(16) | 0 | 0 | 0, + /* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, + /* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, + /* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ + 0 | 2 | 4 | 0 | 16 | 32 | 64 | 128, + /* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, + /* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, +}; + +#undef T + +typedef enum { + s_dead = 1, /* important that this is > 0 */ + + s_start_req_or_res, + s_res_or_resp_H, + s_start_res, + s_res_H, + s_res_HT, + s_res_HTT, + s_res_HTTP, + s_res_http_major, + s_res_http_dot, + s_res_http_minor, + s_res_http_end, + s_res_first_status_code, + s_res_status_code, + s_res_status_start, + s_res_status, + s_res_line_almost_done, + + s_start_req, + + s_req_method, + s_req_spaces_before_url, + s_req_schema, + s_req_schema_slash, + s_req_schema_slash_slash, + s_req_server_start, + s_req_server, + s_req_server_with_at, + s_req_path, + s_req_query_string_start, + s_req_query_string, + s_req_fragment_start, + s_req_fragment, + s_req_http_start, + s_req_http_H, + s_req_http_HT, + s_req_http_HTT, + s_req_http_HTTP, + s_req_http_I, + s_req_http_IC, + s_req_http_major, + s_req_http_dot, + s_req_http_minor, + s_req_http_end, + s_req_line_almost_done, + + s_header_field_start, + s_header_field, + s_header_value_discard_ws, + s_header_value_discard_ws_almost_done, + s_header_value_discard_lws, + s_header_value_start, + s_header_value, + s_header_value_lws, + + s_header_almost_done, + + s_chunk_size_start, + s_chunk_size, + s_chunk_parameters, + s_chunk_size_almost_done, + + s_headers_almost_done, + s_headers_done, + + /* + * Important: 's_headers_done' must be the last 'header' state. All + * states beyond this must be 'body' states. It is used for overflow + * checking. See the PARSING_HEADER() macro. + */ + + s_chunk_data, + s_chunk_data_almost_done, + s_chunk_data_done, + + s_body_identity, + s_body_identity_eof, + + s_message_done +} state_t; + +typedef enum { + s_http_host_dead = 1, + s_http_userinfo_start, + s_http_userinfo, + s_http_host_start, + s_http_host_v6_start, + s_http_host, + s_http_host_v6, + s_http_host_v6_end, + s_http_host_v6_zone_start, + s_http_host_v6_zone, + s_http_host_port_start, + s_http_host_port +} host_state_t; + +/* Macros for character classes; depends on strict-mode */ +#define IS_MARK(c) \ + ((c) == '-' || (c) == '_' || (c) == '.' || (c) == '!' || (c) == '~' || \ + (c) == '*' || (c) == '\'' || (c) == '(' || (c) == ')') +#define IS_USERINFO_CHAR(c) \ + (isalnum((unsigned char)c) || IS_MARK(c) || (c) == '%' || \ + (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \ + (c) == '$' || (c) == ',') + +#if HTTP_PARSER_STRICT +#define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c)) +#define IS_HOST_CHAR(c) (isalnum((unsigned char)c) || (c) == '.' || (c) == '-') +#else +#define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c) || ((c)&0x80)) +#define IS_HOST_CHAR(c) \ + (isalnum((unsigned char)c) || (c) == '.' || (c) == '-' || (c) == '_') +#endif + +/* + * Our URL parser. + * + * This is designed to be shared by http_parser_execute() for URL validation, + * hence it has a state transition + byte-for-byte interface. In addition, it + * is meant to be embedded in http_parser_parse_url(), which does the dirty + * work of turning state transitions URL components for its API. + * + * This function should only be invoked with non-space characters. It is + * assumed that the caller cares about (and can detect) the transition between + * URL and non-URL states by looking for these. + */ +static state_t +parse_url_char(state_t s, const char ch) { + if (ch == ' ' || ch == '\r' || ch == '\n') { + return (s_dead); + } + +#if HTTP_PARSER_STRICT + if (ch == '\t' || ch == '\f') { + return (s_dead); + } +#endif + + switch (s) { + case s_req_spaces_before_url: + /* Proxied requests are followed by scheme of an absolute URI + * (alpha). All methods except CONNECT are followed by '/' or + * '*'. + */ + + if (ch == '/' || ch == '*') { + return (s_req_path); + } + + if (isalpha((unsigned char)ch)) { + return (s_req_schema); + } + + break; + + case s_req_schema: + if (isalpha((unsigned char)ch)) { + return (s); + } + + if (ch == ':') { + return (s_req_schema_slash); + } + + break; + + case s_req_schema_slash: + if (ch == '/') { + return (s_req_schema_slash_slash); + } + + break; + + case s_req_schema_slash_slash: + if (ch == '/') { + return (s_req_server_start); + } + + break; + + case s_req_server_with_at: + if (ch == '@') { + return (s_dead); + } + + FALLTHROUGH; + case s_req_server_start: + case s_req_server: + if (ch == '/') { + return (s_req_path); + } + + if (ch == '?') { + return (s_req_query_string_start); + } + + if (ch == '@') { + return (s_req_server_with_at); + } + + if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') { + return (s_req_server); + } + + break; + + case s_req_path: + if (IS_URL_CHAR(ch)) { + return (s); + } + + switch (ch) { + case '?': + return (s_req_query_string_start); + + case '#': + return (s_req_fragment_start); + } + + break; + + case s_req_query_string_start: + case s_req_query_string: + if (IS_URL_CHAR(ch)) { + return (s_req_query_string); + } + + switch (ch) { + case '?': + /* allow extra '?' in query string */ + return (s_req_query_string); + + case '#': + return (s_req_fragment_start); + } + + break; + + case s_req_fragment_start: + if (IS_URL_CHAR(ch)) { + return (s_req_fragment); + } + + switch (ch) { + case '?': + return (s_req_fragment); + + case '#': + return (s); + } + + break; + + case s_req_fragment: + if (IS_URL_CHAR(ch)) { + return (s); + } + + switch (ch) { + case '?': + case '#': + return (s); + } + + break; + + default: + break; + } + + /* + * We should never fall out of the switch above unless there's an + * error. + */ + return (s_dead); +} + +static host_state_t +http_parse_host_char(host_state_t s, const char ch) { + switch (s) { + case s_http_userinfo: + case s_http_userinfo_start: + if (ch == '@') { + return (s_http_host_start); + } + + if (IS_USERINFO_CHAR(ch)) { + return (s_http_userinfo); + } + break; + + case s_http_host_start: + if (ch == '[') { + return (s_http_host_v6_start); + } + + if (IS_HOST_CHAR(ch)) { + return (s_http_host); + } + + break; + + case s_http_host: + if (IS_HOST_CHAR(ch)) { + return (s_http_host); + } + + FALLTHROUGH; + case s_http_host_v6_end: + if (ch == ':') { + return (s_http_host_port_start); + } + + break; + + case s_http_host_v6: + if (ch == ']') { + return (s_http_host_v6_end); + } + + FALLTHROUGH; + case s_http_host_v6_start: + if (isxdigit((unsigned char)ch) || ch == ':' || ch == '.') { + return (s_http_host_v6); + } + + if (s == s_http_host_v6 && ch == '%') { + return (s_http_host_v6_zone_start); + } + break; + + case s_http_host_v6_zone: + if (ch == ']') { + return (s_http_host_v6_end); + } + + FALLTHROUGH; + case s_http_host_v6_zone_start: + /* RFC 6874 Zone ID consists of 1*( unreserved / pct-encoded) */ + if (isalnum((unsigned char)ch) || ch == '%' || ch == '.' || + ch == '-' || ch == '_' || ch == '~') + { + return (s_http_host_v6_zone); + } + break; + + case s_http_host_port: + case s_http_host_port_start: + if (isdigit((unsigned char)ch)) { + return (s_http_host_port); + } + + break; + + default: + break; + } + + return (s_http_host_dead); +} + +static isc_result_t +http_parse_host(const char *buf, isc_url_parser_t *up, int found_at) { + host_state_t s; + const char *p = NULL; + size_t buflen = up->field_data[ISC_UF_HOST].off + + up->field_data[ISC_UF_HOST].len; + + REQUIRE((up->field_set & (1 << ISC_UF_HOST)) != 0); + + up->field_data[ISC_UF_HOST].len = 0; + + s = found_at ? s_http_userinfo_start : s_http_host_start; + + for (p = buf + up->field_data[ISC_UF_HOST].off; p < buf + buflen; p++) { + host_state_t new_s = http_parse_host_char(s, *p); + + if (new_s == s_http_host_dead) { + return (ISC_R_FAILURE); + } + + switch (new_s) { + case s_http_host: + if (s != s_http_host) { + up->field_data[ISC_UF_HOST].off = + (uint16_t)(p - buf); + } + up->field_data[ISC_UF_HOST].len++; + break; + + case s_http_host_v6: + if (s != s_http_host_v6) { + up->field_data[ISC_UF_HOST].off = + (uint16_t)(p - buf); + } + up->field_data[ISC_UF_HOST].len++; + break; + + case s_http_host_v6_zone_start: + case s_http_host_v6_zone: + up->field_data[ISC_UF_HOST].len++; + break; + + case s_http_host_port: + if (s != s_http_host_port) { + up->field_data[ISC_UF_PORT].off = + (uint16_t)(p - buf); + up->field_data[ISC_UF_PORT].len = 0; + up->field_set |= (1 << ISC_UF_PORT); + } + up->field_data[ISC_UF_PORT].len++; + break; + + case s_http_userinfo: + if (s != s_http_userinfo) { + up->field_data[ISC_UF_USERINFO].off = + (uint16_t)(p - buf); + up->field_data[ISC_UF_USERINFO].len = 0; + up->field_set |= (1 << ISC_UF_USERINFO); + } + up->field_data[ISC_UF_USERINFO].len++; + break; + + default: + break; + } + + s = new_s; + } + + /* Make sure we don't end somewhere unexpected */ + switch (s) { + case s_http_host_start: + case s_http_host_v6_start: + case s_http_host_v6: + case s_http_host_v6_zone_start: + case s_http_host_v6_zone: + case s_http_host_port_start: + case s_http_userinfo: + case s_http_userinfo_start: + return (ISC_R_FAILURE); + default: + break; + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_url_parse(const char *buf, size_t buflen, bool is_connect, + isc_url_parser_t *up) { + state_t s; + isc_url_field_t uf, old_uf; + int found_at = 0; + const char *p = NULL; + + if (buflen == 0) { + return (ISC_R_FAILURE); + } + + up->port = up->field_set = 0; + s = is_connect ? s_req_server_start : s_req_spaces_before_url; + old_uf = ISC_UF_MAX; + + for (p = buf; p < buf + buflen; p++) { + s = parse_url_char(s, *p); + + /* Figure out the next field that we're operating on */ + switch (s) { + case s_dead: + return (ISC_R_FAILURE); + + /* Skip delimiters */ + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_server_start: + case s_req_query_string_start: + case s_req_fragment_start: + continue; + + case s_req_schema: + uf = ISC_UF_SCHEMA; + break; + + case s_req_server_with_at: + found_at = 1; + FALLTHROUGH; + case s_req_server: + uf = ISC_UF_HOST; + break; + + case s_req_path: + uf = ISC_UF_PATH; + break; + + case s_req_query_string: + uf = ISC_UF_QUERY; + break; + + case s_req_fragment: + uf = ISC_UF_FRAGMENT; + break; + + default: + UNREACHABLE(); + } + + /* Nothing's changed; soldier on */ + if (uf == old_uf) { + up->field_data[uf].len++; + continue; + } + + up->field_data[uf].off = (uint16_t)(p - buf); + up->field_data[uf].len = 1; + + up->field_set |= (1 << uf); + old_uf = uf; + } + + /* host must be present if there is a schema */ + /* parsing http:///toto will fail */ + if ((up->field_set & (1 << ISC_UF_SCHEMA)) && + (up->field_set & (1 << ISC_UF_HOST)) == 0) + { + return (ISC_R_FAILURE); + } + + if (up->field_set & (1 << ISC_UF_HOST)) { + isc_result_t result; + + result = http_parse_host(buf, up, found_at); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + + /* CONNECT requests can only contain "hostname:port" */ + if (is_connect && + up->field_set != ((1 << ISC_UF_HOST) | (1 << ISC_UF_PORT))) + { + return (ISC_R_FAILURE); + } + + if (up->field_set & (1 << ISC_UF_PORT)) { + uint16_t off; + uint16_t len; + const char *pp = NULL; + const char *end = NULL; + unsigned long v; + + off = up->field_data[ISC_UF_PORT].off; + len = up->field_data[ISC_UF_PORT].len; + end = buf + off + len; + + /* + * NOTE: The characters are already validated and are in the + * [0-9] range + */ + INSIST(off + len <= buflen); + + v = 0; + for (pp = buf + off; pp < end; pp++) { + v *= 10; + v += *pp - '0'; + + /* Ports have a max value of 2^16 */ + if (v > 0xffff) { + return (ISC_R_RANGE); + } + } + + up->port = (uint16_t)v; + } + + return (ISC_R_SUCCESS); +} diff --git a/lib/isc/utf8.c b/lib/isc/utf8.c new file mode 100644 index 0000000..a348c5d --- /dev/null +++ b/lib/isc/utf8.c @@ -0,0 +1,89 @@ +/* + * 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. + */ + +#include <string.h> + +#include <isc/utf8.h> +#include <isc/util.h> + +/* + * UTF-8 is defined in "The Unicode Standard -- Version 4.0" + * Also see RFC 3629. + * + * Char. number range | UTF-8 octet sequence + * (hexadecimal) | (binary) + * --------------------+--------------------------------------------- + * 0000 0000-0000 007F | 0xxxxxxx + * 0000 0080-0000 07FF | 110xxxxx 10xxxxxx + * 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx + * 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + */ +bool +isc_utf8_valid(const unsigned char *buf, size_t len) { + REQUIRE(buf != NULL); + + for (size_t i = 0; i < len; i++) { + if (buf[i] <= 0x7f) { + continue; + } + if ((i + 1) < len && (buf[i] & 0xe0) == 0xc0 && + (buf[i + 1] & 0xc0) == 0x80) + { + unsigned int w; + w = (buf[i] & 0x1f) << 6; + w |= (buf[++i] & 0x3f); + if (w < 0x80) { + return (false); + } + continue; + } + if ((i + 2) < len && (buf[i] & 0xf0) == 0xe0 && + (buf[i + 1] & 0xc0) == 0x80 && (buf[i + 2] & 0xc0) == 0x80) + { + unsigned int w; + w = (buf[i] & 0x0f) << 12; + w |= (buf[++i] & 0x3f) << 6; + w |= (buf[++i] & 0x3f); + if (w < 0x0800) { + return (false); + } + continue; + } + if ((i + 3) < len && (buf[i] & 0xf8) == 0xf0 && + (buf[i + 1] & 0xc0) == 0x80 && + (buf[i + 2] & 0xc0) == 0x80 && (buf[i + 3] & 0xc0) == 0x80) + { + unsigned int w; + w = (buf[i] & 0x07) << 18; + w |= (buf[++i] & 0x3f) << 12; + w |= (buf[++i] & 0x3f) << 6; + w |= (buf[++i] & 0x3f); + if (w < 0x10000 || w > 0x10FFFF) { + return (false); + } + continue; + } + return (false); + } + return (true); +} + +bool +isc_utf8_bom(const unsigned char *buf, size_t len) { + REQUIRE(buf != NULL); + + if (len >= 3U && !memcmp(buf, "\xef\xbb\xbf", 3)) { + return (true); + } + return (false); +} diff --git a/lib/isc/version.c b/lib/isc/version.c new file mode 100644 index 0000000..17aa210 --- /dev/null +++ b/lib/isc/version.c @@ -0,0 +1,18 @@ +/* + * 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. + */ + +/*! \file */ + +#include <isc/version.h> + +const char isc_version[] = VERSION; diff --git a/lib/isc/win32/.dir-locals.el b/lib/isc/win32/.dir-locals.el new file mode 100644 index 0000000..b16a1be --- /dev/null +++ b/lib/isc/win32/.dir-locals.el @@ -0,0 +1,35 @@ +;;; Directory Local Variables +;;; For more information see (info "(emacs) Directory Variables") + +((c-mode . + ((eval . + (set (make-local-variable 'directory-of-current-dir-locals-file) + (file-name-directory (locate-dominating-file default-directory ".dir-locals.el")) + ) + ) + (eval . + (set (make-local-variable 'include-directories) + (list + (expand-file-name + (concat directory-of-current-dir-locals-file "../../../")) + (expand-file-name + (concat directory-of-current-dir-locals-file "include")) + (expand-file-name + (concat directory-of-current-dir-locals-file "../include")) + (expand-file-name + (concat directory-of-current-dir-locals-file "../")) + (expand-file-name + (concat directory-of-current-dir-locals-file "./")) + (expand-file-name + (concat directory-of-current-dir-locals-file "../../dns/include")) + (expand-file-name "/usr/local/opt/openssl@1.1/include") + (expand-file-name "/usr/local/opt/libxml2/include/libxml2") + (expand-file-name "/usr/local/include") + ) + ) + ) + + (eval setq flycheck-clang-include-path include-directories) + (eval setq flycheck-cpp-include-path include-directories) + ) + )) diff --git a/lib/isc/win32/DLLMain.c b/lib/isc/win32/DLLMain.c new file mode 100644 index 0000000..abaaf40 --- /dev/null +++ b/lib/isc/win32/DLLMain.c @@ -0,0 +1,52 @@ +/* + * 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. + */ + +#include <stdio.h> +#include <windows.h> + +#include <isc/mem.h> +#include <isc/util.h> + +#include "lib_p.h" + +/* + * Called when we enter the DLL + */ +__declspec(dllexport) BOOL WINAPI + DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { + switch (fdwReason) { + /* + * The DLL is loading due to process initialization or a call to + * LoadLibrary. + */ + case DLL_PROCESS_ATTACH: + isc__initialize(); + break; + + /* + * The DLL is unloading from a process due to process + * termination or a call to FreeLibrary. + */ + case DLL_PROCESS_DETACH: + isc__shutdown(); + break; + + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + break; + + default: + break; + } + return (TRUE); +} diff --git a/lib/isc/win32/Makefile.in b/lib/isc/win32/Makefile.in new file mode 100644 index 0000000..e8377ff --- /dev/null +++ b/lib/isc/win32/Makefile.in @@ -0,0 +1,36 @@ +# 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. + +srcdir = @srcdir@ +VPATH = @srcdir@ +top_srcdir = @top_srcdir@ + +CINCLUDES = -I${srcdir}/.. \ + -I./include \ + -I${srcdir}/include \ + -I${srcdir}/../include +CDEFINES = +CWARNINGS = + +# Alphabetically +OBJS = condition.@O@ dir.@O@ errno.@O@ file.@O@ \ + meminfo.@O@ fsaccess.@O@ \ + once.@O@ stdtime.@O@ thread.@O@ time.@O@ pk11_api.@O@ + +# Alphabetically +SRCS = condition.c dir.c errno.c file.c \ + meminfo.c once.c fsaccess.c \ + stdtime.c thread.c time.c pk11_api.c + +SUBDIRS = include +TARGETS = ${OBJS} + +@BIND9_MAKE_RULES@ diff --git a/lib/isc/win32/condition.c b/lib/isc/win32/condition.c new file mode 100644 index 0000000..ca11bce --- /dev/null +++ b/lib/isc/win32/condition.c @@ -0,0 +1,255 @@ +/* + * 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. + */ + +#include <inttypes.h> +#include <stdbool.h> + +#include <isc/assertions.h> +#include <isc/condition.h> +#include <isc/error.h> +#include <isc/strerr.h> +#include <isc/thread.h> +#include <isc/time.h> +#include <isc/util.h> + +#define LSIGNAL 0 +#define LBROADCAST 1 + +void +isc_condition_init(isc_condition_t *cond) { + HANDLE h; + + REQUIRE(cond != NULL); + + cond->waiters = 0; + /* + * This handle is shared across all threads + */ + h = CreateEvent(NULL, FALSE, FALSE, NULL); + if (h == NULL) { + char strbuf[ISC_STRERRORSIZE]; + DWORD err = GetLastError(); + strerror_r(err, strbuf, sizeof(strbuf)); + isc_error_fatal(__FILE__, __LINE__, "CreateEvent failed: %s", + strbuf); + } + cond->events[LSIGNAL] = h; + + /* + * The threadlist will hold the actual events needed + * for the wait condition + */ + ISC_LIST_INIT(cond->threadlist); +} + +/* + * Add the thread to the threadlist along with the required events + */ +static isc_result_t +register_thread(unsigned long thrd, isc_condition_t *gblcond, + isc_condition_thread_t **localcond) { + HANDLE hc; + isc_condition_thread_t *newthread; + + REQUIRE(localcond != NULL && *localcond == NULL); + + newthread = malloc(sizeof(isc_condition_thread_t)); + if (newthread == NULL) { + return (ISC_R_NOMEMORY); + } + + /* + * Create the thread-specific handle + */ + hc = CreateEvent(NULL, FALSE, FALSE, NULL); + if (hc == NULL) { + free(newthread); + return (ISC_R_UNEXPECTED); + } + + /* + * Add the thread ID and handles to list of threads for broadcast + */ + newthread->handle[LSIGNAL] = gblcond->events[LSIGNAL]; + newthread->handle[LBROADCAST] = hc; + newthread->th = thrd; + + /* + * The thread is holding the manager lock so this is safe + */ + ISC_LINK_INIT(newthread, link); + ISC_LIST_APPEND(gblcond->threadlist, newthread, link); + *localcond = newthread; + return (ISC_R_SUCCESS); +} + +static isc_result_t +find_thread_condition(uintptr_t thrd, isc_condition_t *cond, + isc_condition_thread_t **threadcondp) { + isc_condition_thread_t *threadcond; + + REQUIRE(threadcondp != NULL && *threadcondp == NULL); + + /* + * Look for the thread ID. + */ + for (threadcond = ISC_LIST_HEAD(cond->threadlist); threadcond != NULL; + threadcond = ISC_LIST_NEXT(threadcond, link)) + { + if (threadcond->th == thrd) { + *threadcondp = threadcond; + return (ISC_R_SUCCESS); + } + } + + /* + * Not found, so add it. + */ + return (register_thread(thrd, cond, threadcondp)); +} + +isc_result_t +isc_condition_signal(isc_condition_t *cond) { + /* + * Unlike pthreads, the caller MUST hold the lock associated with + * the condition variable when calling us. + */ + REQUIRE(cond != NULL); + + if (!SetEvent(cond->events[LSIGNAL])) { + /* XXX */ + return (ISC_R_UNEXPECTED); + } + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_condition_broadcast(isc_condition_t *cond) { + isc_condition_thread_t *threadcond; + bool failed = false; + + /* + * Unlike pthreads, the caller MUST hold the lock associated with + * the condition variable when calling us. + */ + REQUIRE(cond != NULL); + + /* + * Notify every thread registered for this + */ + for (threadcond = ISC_LIST_HEAD(cond->threadlist); threadcond != NULL; + threadcond = ISC_LIST_NEXT(threadcond, link)) + { + if (!SetEvent(threadcond->handle[LBROADCAST])) { + failed = true; + } + } + + if (failed) { + return (ISC_R_UNEXPECTED); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_condition_destroy(isc_condition_t *cond) { + isc_condition_thread_t *next, *threadcond; + + REQUIRE(cond != NULL); + REQUIRE(cond->waiters == 0); + + (void)CloseHandle(cond->events[LSIGNAL]); + + /* + * Delete the threadlist + */ + threadcond = ISC_LIST_HEAD(cond->threadlist); + + while (threadcond != NULL) { + next = ISC_LIST_NEXT(threadcond, link); + DEQUEUE(cond->threadlist, threadcond, link); + (void)CloseHandle(threadcond->handle[LBROADCAST]); + free(threadcond); + threadcond = next; + } + + return (ISC_R_SUCCESS); +} + +/* + * This is always called when the mutex (lock) is held, but because + * we are waiting we need to release it and reacquire it as soon as the wait + * is over. This allows other threads to make use of the object guarded + * by the mutex but it should never try to delete it as long as the + * number of waiters > 0. Always reacquire the mutex regardless of the + * result of the wait. Note that EnterCriticalSection will wait to acquire + * the mutex. + */ +static isc_result_t +wait(isc_condition_t *cond, isc_mutex_t *mutex, DWORD milliseconds) { + DWORD result; + isc_result_t tresult; + isc_condition_thread_t *threadcond = NULL; + + /* + * Get the thread events needed for the wait + */ + tresult = find_thread_condition(isc_thread_self(), cond, &threadcond); + if (tresult != ISC_R_SUCCESS) { + return (tresult); + } + + cond->waiters++; + LeaveCriticalSection(mutex); + result = WaitForMultipleObjects(2, threadcond->handle, FALSE, + milliseconds); + EnterCriticalSection(mutex); + cond->waiters--; + if (result == WAIT_FAILED) { + /* XXX */ + return (ISC_R_UNEXPECTED); + } + if (result == WAIT_TIMEOUT) { + return (ISC_R_TIMEDOUT); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_condition_wait(isc_condition_t *cond, isc_mutex_t *mutex) { + return (wait(cond, mutex, INFINITE)); +} + +isc_result_t +isc_condition_waituntil(isc_condition_t *cond, isc_mutex_t *mutex, + isc_time_t *t) { + DWORD milliseconds; + uint64_t microseconds; + isc_time_t now; + + if (isc_time_now(&now) != ISC_R_SUCCESS) { + /* XXX */ + return (ISC_R_UNEXPECTED); + } + + microseconds = isc_time_microdiff(t, &now); + if (microseconds > 0xFFFFFFFFi64 * 1000) { + milliseconds = 0xFFFFFFFF; + } else { + milliseconds = (DWORD)(microseconds / 1000); + } + + return (wait(cond, mutex, milliseconds)); +} diff --git a/lib/isc/win32/dir.c b/lib/isc/win32/dir.c new file mode 100644 index 0000000..9a512da --- /dev/null +++ b/lib/isc/win32/dir.c @@ -0,0 +1,314 @@ +/* + * 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. + */ + +#include <direct.h> +#include <io.h> +#include <process.h> +#include <string.h> +#include <sys/stat.h> + +#include <isc/assertions.h> +#include <isc/dir.h> +#include <isc/magic.h> +#include <isc/print.h> +#include <isc/string.h> +#include <isc/util.h> + +#include "errno2result.h" + +#define ISC_DIR_MAGIC ISC_MAGIC('D', 'I', 'R', '*') +#define VALID_DIR(dir) ISC_MAGIC_VALID(dir, ISC_DIR_MAGIC) + +static isc_result_t +start_directory(isc_dir_t *p); + +void +isc_dir_init(isc_dir_t *dir) { + REQUIRE(dir != NULL); + + dir->dirname[0] = '\0'; + + dir->entry.name[0] = '\0'; + dir->entry.length = 0; + memset(&(dir->entry.find_data), 0, sizeof(dir->entry.find_data)); + + dir->entry_filled = false; + dir->search_handle = INVALID_HANDLE_VALUE; + + dir->magic = ISC_DIR_MAGIC; +} + +/* + * Allocate workspace and open directory stream. If either one fails, + * NULL will be returned. + */ +isc_result_t +isc_dir_open(isc_dir_t *dir, const char *dirname) { + char *p; + isc_result_t result; + + REQUIRE(dirname != NULL); + REQUIRE(VALID_DIR(dir) && dir->search_handle == INVALID_HANDLE_VALUE); + + /* + * Copy directory name. Need to have enough space for the name, + * a possible path separator, the wildcard, and the final NUL. + */ + if (strlen(dirname) + 3 > sizeof(dir->dirname)) { + /* XXXDCL ? */ + return (ISC_R_NOSPACE); + } + strlcpy(dir->dirname, dirname, sizeof(dir->dirname)); + + /* + * Append path separator, if needed, and "*". + */ + p = dir->dirname + strlen(dir->dirname); + if (dir->dirname < p && *(p - 1) != '\\' && *(p - 1) != ':') { + *p++ = '\\'; + } + *p++ = '*'; + *p = '\0'; + + /* + * Open stream. + */ + result = start_directory(dir); + + return (result); +} + +/* + * Return previously retrieved file or get next one. Unix's dirent has + * separate open and read functions, but the Win32 and DOS interfaces open + * the dir stream and reads the first file in one operation. + */ +isc_result_t +isc_dir_read(isc_dir_t *dir) { + REQUIRE(VALID_DIR(dir) && dir->search_handle != INVALID_HANDLE_VALUE); + + if (dir->entry_filled) { + /* + * start_directory() already filled in the first entry. + */ + dir->entry_filled = false; + } else { + /* + * Fetch next file in directory. + */ + if (FindNextFile(dir->search_handle, &dir->entry.find_data) == + FALSE) + { + /* + * Either the last file has been processed or + * an error has occurred. The former is not + * really an error, but the latter is. + */ + if (GetLastError() == ERROR_NO_MORE_FILES) { + return (ISC_R_NOMORE); + } else { + return (ISC_R_UNEXPECTED); + } + } + } + + /* + * Make sure that the space for the name is long enough. + */ + strlcpy(dir->entry.name, dir->entry.find_data.cFileName, + sizeof(dir->entry.name)); + dir->entry.length = strlen(dir->entry.name); + + return (ISC_R_SUCCESS); +} + +/* + * Close directory stream. + */ +void +isc_dir_close(isc_dir_t *dir) { + REQUIRE(VALID_DIR(dir) && dir->search_handle != INVALID_HANDLE_VALUE); + + FindClose(dir->search_handle); + dir->search_handle = INVALID_HANDLE_VALUE; +} + +/* + * Reposition directory stream at start. + */ +isc_result_t +isc_dir_reset(isc_dir_t *dir) { + isc_result_t result; + + REQUIRE(VALID_DIR(dir) && dir->search_handle != INVALID_HANDLE_VALUE); + REQUIRE(dir->dirname != NULL); + + /* + * NT cannot reposition the seek pointer to the beginning of the + * the directory stream, but rather the directory needs to be + * closed and reopened. The latter might fail. + */ + + isc_dir_close(dir); + + result = start_directory(dir); + + return (result); +} + +/* + * Initialize isc_dir_t structure with new directory. The function + * returns 0 on failure and nonzero on success. + * + * Note: + * - Be sure to close previous stream before opening new one + */ +static isc_result_t +start_directory(isc_dir_t *dir) { + REQUIRE(VALID_DIR(dir)); + REQUIRE(dir->search_handle == INVALID_HANDLE_VALUE); + + dir->entry_filled = false; + + /* + * Open stream and retrieve first file. + */ + dir->search_handle = FindFirstFile(dir->dirname, &dir->entry.find_data); + + if (dir->search_handle == INVALID_HANDLE_VALUE) { + /* + * Something went wrong but we don't know what. GetLastError() + * could give us more information about the error, but the + * MSDN documentation is frustratingly thin about what + * possible errors could have resulted. (Score one for + * the Unix manual pages.) So there is just this lame error + * instead of being able to differentiate ISC_R_NOTFOUND + * from ISC_R_UNEXPECTED. + */ + return (ISC_R_FAILURE); + } + + /* + * Make sure that the space for the name is long enough. + */ + INSIST(sizeof(dir->entry.name) > + strlen(dir->entry.find_data.cFileName)); + + /* + * Fill in the data for the first entry of the directory. + */ + strlcpy(dir->entry.name, dir->entry.find_data.cFileName, + sizeof(dir->entry.name)); + dir->entry.length = strlen(dir->entry.name); + + dir->entry_filled = true; + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_dir_chdir(const char *dirname) { + /* + * Change the current directory to 'dirname'. + */ + + REQUIRE(dirname != NULL); + + if (chdir(dirname) < 0) { + return (isc__errno2result(errno)); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_dir_chroot(const char *dirname) { + return (ISC_R_NOTIMPLEMENTED); +} + +isc_result_t +isc_dir_createunique(char *templet) { + isc_result_t result; + char *x; + char *p; + int i; + int pid; + + REQUIRE(templet != NULL); + + /* + * mkdtemp is not portable, so this emulates it. + */ + + pid = getpid(); + + /* + * Replace trailing Xs with the process-id, zero-filled. + */ + for (x = templet + strlen(templet) - 1; *x == 'X' && x >= templet; + x--, pid /= 10) + { + *x = pid % 10 + '0'; + } + + x++; /* Set x to start of ex-Xs. */ + + do { + i = mkdir(templet); + if (i == 0) { + i = chmod(templet, 0700); + } + + if (i == 0 || errno != EEXIST) { + break; + } + + /* + * The BSD algorithm. + */ + p = x; + while (*p != '\0') { + if (isdigit((unsigned char)*p)) { + *p = 'a'; + } else if (*p != 'z') { + ++*p; + } else { + /* + * Reset character and move to next. + */ + *p++ = 'a'; + continue; + } + + break; + } + + if (*p == '\0') { + /* + * Tried all combinations. errno should already + * be EEXIST, but ensure it is anyway for + * isc__errno2result(). + */ + errno = EEXIST; + break; + } + } while (1); + + if (i == -1) { + result = isc__errno2result(errno); + } else { + result = ISC_R_SUCCESS; + } + + return (result); +} diff --git a/lib/isc/win32/errno.c b/lib/isc/win32/errno.c new file mode 100644 index 0000000..fd6899f --- /dev/null +++ b/lib/isc/win32/errno.c @@ -0,0 +1,21 @@ +/* + * 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. + */ + +/*! \file */ + +#include "errno2result.h" + +isc_result_t +isc_errno_toresult(int err) { + return (isc__errno2resultx(err, false, 0, 0)); +} diff --git a/lib/isc/win32/errno2result.c b/lib/isc/win32/errno2result.c new file mode 100644 index 0000000..593c27b --- /dev/null +++ b/lib/isc/win32/errno2result.c @@ -0,0 +1,118 @@ +/* + * 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. + */ + +#include "errno2result.h" +#include <stdbool.h> +#include <winsock2.h> + +#include <isc/result.h> +#include <isc/strerr.h> +#include <isc/string.h> +#include <isc/util.h> + +/* + * Convert a POSIX errno value into an isc_result_t. The + * list of supported errno values is not complete; new users + * of this function should add any expected errors that are + * not already there. + */ +isc_result_t +isc__errno2resultx(int posixerrno, bool dolog, const char *file, int line) { + char strbuf[ISC_STRERRORSIZE]; + + switch (posixerrno) { + case ENOTDIR: + case WSAELOOP: + case WSAEINVAL: + case EINVAL: /* XXX sometimes this is not for files */ + case ENAMETOOLONG: + case WSAENAMETOOLONG: + case EBADF: + case WSAEBADF: + return (ISC_R_INVALIDFILE); + case ENOENT: + return (ISC_R_FILENOTFOUND); + case EACCES: + case WSAEACCES: + case EPERM: + return (ISC_R_NOPERM); + case EEXIST: + return (ISC_R_FILEEXISTS); + case EIO: + return (ISC_R_IOERROR); + case ENOMEM: + return (ISC_R_NOMEMORY); +#ifdef EOVERFLOW + case EOVERFLOW: + return (ISC_R_RANGE); +#endif /* ifdef EOVERFLOW */ + case ENFILE: + case EMFILE: + case WSAEMFILE: + return (ISC_R_TOOMANYOPENFILES); + case ENOSPC: + return (ISC_R_DISCFULL); + case ERROR_CANCELLED: + return (ISC_R_CANCELED); + case ERROR_CONNECTION_REFUSED: + case WSAECONNREFUSED: + return (ISC_R_CONNREFUSED); + case WSAENOTCONN: + case ERROR_CONNECTION_INVALID: + return (ISC_R_NOTCONNECTED); + case ERROR_HOST_UNREACHABLE: + case WSAEHOSTUNREACH: + return (ISC_R_HOSTUNREACH); + case ERROR_NETWORK_UNREACHABLE: + case WSAENETUNREACH: + return (ISC_R_NETUNREACH); + case ERROR_NO_NETWORK: + return (ISC_R_NETUNREACH); + case ERROR_PORT_UNREACHABLE: + return (ISC_R_HOSTUNREACH); + case ERROR_SEM_TIMEOUT: + return (ISC_R_TIMEDOUT); + case WSAECONNRESET: + case WSAENETRESET: + case WSAECONNABORTED: + case WSAEDISCON: + case ERROR_OPERATION_ABORTED: + case ERROR_CONNECTION_ABORTED: + case ERROR_REQUEST_ABORTED: + return (ISC_R_CONNECTIONRESET); + case WSAEADDRNOTAVAIL: + return (ISC_R_ADDRNOTAVAIL); + case ERROR_NETNAME_DELETED: + case WSAENETDOWN: + return (ISC_R_NETUNREACH); + case WSAEHOSTDOWN: + return (ISC_R_HOSTUNREACH); + case WSAENOBUFS: + return (ISC_R_NORESOURCES); + default: + if (dolog) { + strerror_r(posixerrno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(file, line, + "unable to convert errno " + "to isc_result: %d: %s", + posixerrno, strbuf); + } + /* + * XXXDCL would be nice if perhaps this function could + * return the system's error string, so the caller + * might have something more descriptive than "unexpected + * error" to log with. + */ + return (ISC_R_UNEXPECTED); + } +} diff --git a/lib/isc/win32/errno2result.h b/lib/isc/win32/errno2result.h new file mode 100644 index 0000000..ce2d72e --- /dev/null +++ b/lib/isc/win32/errno2result.h @@ -0,0 +1,35 @@ +/* + * 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. + */ + +#ifndef UNIX_ERRNO2RESULT_H +#define UNIX_ERRNO2RESULT_H 1 + +/* XXXDCL this should be moved to lib/isc/include/isc/errno2result.h. */ + +#include <errno.h> /* Provides errno. */ +#include <stdbool.h> + +#include <isc/lang.h> +#include <isc/types.h> + +ISC_LANG_BEGINDECLS + +#define isc__errno2result(posixerrno) \ + isc__errno2resultx(posixerrno, true, __FILE__, __LINE__) + +isc_result_t +isc__errno2resultx(int posixerrno, bool dolog, const char *file, int line); + +ISC_LANG_ENDDECLS + +#endif /* UNIX_ERRNO2RESULT_H */ diff --git a/lib/isc/win32/file.c b/lib/isc/win32/file.c new file mode 100644 index 0000000..32f6a19 --- /dev/null +++ b/lib/isc/win32/file.c @@ -0,0 +1,1003 @@ +/* + * 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. + */ + +#undef rename +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <io.h> +#include <limits.h> +#include <process.h> +#include <stdbool.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/utime.h> + +#include <isc/file.h> +#include <isc/md.h> +#include <isc/mem.h> +#include <isc/platform.h> +#include <isc/print.h> +#include <isc/random.h> +#include <isc/result.h> +#include <isc/stat.h> +#include <isc/string.h> +#include <isc/time.h> +#include <isc/util.h> + +#include "errno2result.h" + +static const char alphnum[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuv" + "wxyz0123456789"; + +/* + * Emulate UNIX mkstemp, which returns an open FD to the new file + * + */ +static int +gettemp(char *path, bool binary, int *doopen) { + char *start, *trv; + struct stat sbuf; + int flags = O_CREAT | O_EXCL | O_RDWR; + + if (binary) { + flags |= _O_BINARY; + } + + trv = strrchr(path, 'X'); + trv++; + /* extra X's get set to 0's */ + while (*--trv == 'X') { + uint32_t which = isc_random_uniform(sizeof(alphnum) - 1); + *trv = alphnum[which]; + } + /* + * check the target directory; if you have six X's and it + * doesn't exist this runs for a *very* long time. + */ + for (start = trv + 1;; --trv) { + if (trv <= path) { + break; + } + if (*trv == '\\') { + *trv = '\0'; + if (stat(path, &sbuf)) { + return (0); + } + if (!S_ISDIR(sbuf.st_mode)) { + errno = ENOTDIR; + return (0); + } + *trv = '\\'; + break; + } + } + + for (;;) { + if (doopen) { + if ((*doopen = open(path, flags, + _S_IREAD | _S_IWRITE)) >= 0) + { + return (1); + } + if (errno != EEXIST) { + return (0); + } + } else if (stat(path, &sbuf)) { + return (errno == ENOENT ? 1 : 0); + } + + /* tricky little algorithm for backward compatibility */ + for (trv = start;;) { + if (!*trv) { + return (0); + } + if (*trv == 'z') { + *trv++ = 'a'; + } else { + if (isdigit((unsigned char)*trv)) { + *trv = 'a'; + } else { + ++*trv; + } + break; + } + } + } + /*NOTREACHED*/ +} + +static int +mkstemp(char *path, bool binary) { + int fd; + + return (gettemp(path, binary, &fd) ? fd : -1); +} + +/* + * XXXDCL As the API for accessing file statistics undoubtedly gets expanded, + * it might be good to provide a mechanism that allows for the results + * of a previous stat() to be used again without having to do another stat, + * such as perl's mechanism of using "_" in place of a file name to indicate + * that the results of the last stat should be used. But then you get into + * annoying MP issues. BTW, Win32 has stat(). + */ +static isc_result_t +file_stats(const char *file, struct stat *stats) { + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(file != NULL); + REQUIRE(stats != NULL); + + if (stat(file, stats) != 0) { + result = isc__errno2result(errno); + } + + return (result); +} + +static isc_result_t +fd_stats(int fd, struct stat *stats) { + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(stats != NULL); + + if (fstat(fd, stats) != 0) { + result = isc__errno2result(errno); + } + + return (result); +} + +isc_result_t +isc_file_getsizefd(int fd, off_t *size) { + isc_result_t result; + struct stat stats; + + REQUIRE(size != NULL); + + result = fd_stats(fd, &stats); + + if (result == ISC_R_SUCCESS) { + *size = stats.st_size; + } + return (result); +} + +isc_result_t +isc_file_mode(const char *file, mode_t *modep) { + isc_result_t result; + struct stat stats; + + REQUIRE(modep != NULL); + + result = file_stats(file, &stats); + if (result == ISC_R_SUCCESS) { + *modep = (stats.st_mode & 07777); + } + return (result); +} + +/* + * isc_file_safemovefile is needed to be defined here to ensure that + * any file with the new name is renamed to a backup name and then the + * rename is done. If all goes well then the backup can be deleted, + * otherwise it gets renamed back. + */ + +int +isc_file_safemovefile(const char *oldname, const char *newname) { + BOOL filestatus; + char buf[512]; + struct stat sbuf; + BOOL exists = FALSE; + int tmpfd; + + /* + * Make sure we have something to do + */ + if (stat(oldname, &sbuf) != 0) { + errno = ENOENT; + return (-1); + } + + /* + * Rename to a backup the new file if it still exists + */ + if (stat(newname, &sbuf) == 0) { + exists = TRUE; + strlcpy(buf, newname, sizeof(buf)); + strlcat(buf, ".XXXXX", sizeof(buf)); + tmpfd = mkstemp(buf, true); + if (tmpfd > 0) { + _close(tmpfd); + } + (void)DeleteFile(buf); + _chmod(newname, _S_IREAD | _S_IWRITE); + + filestatus = MoveFile(newname, buf); + } + /* Now rename the file to the new name + */ + _chmod(oldname, _S_IREAD | _S_IWRITE); + + filestatus = MoveFile(oldname, newname); + if (filestatus == 0) { + /* + * Try to rename the backup back to the original name + * if the backup got created + */ + if (exists == TRUE) { + filestatus = MoveFile(buf, newname); + if (filestatus == 0) { + errno = EACCES; + } + } + return (-1); + } + + /* + * Delete the backup file if it got created + */ + if (exists == TRUE) { + (void)DeleteFile(buf); + } + return (0); +} + +isc_result_t +isc_file_getmodtime(const char *file, isc_time_t *time) { + int fh; + + REQUIRE(file != NULL); + REQUIRE(time != NULL); + + if ((fh = open(file, _O_RDONLY | _O_BINARY)) < 0) { + return (isc__errno2result(errno)); + } + + if (!GetFileTime((HANDLE)_get_osfhandle(fh), NULL, NULL, + &time->absolute)) + { + close(fh); + errno = EINVAL; + return (isc__errno2result(errno)); + } + close(fh); + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_file_getsize(const char *file, off_t *size) { + isc_result_t result; + struct stat stats; + + REQUIRE(file != NULL); + REQUIRE(size != NULL); + + result = file_stats(file, &stats); + + if (result == ISC_R_SUCCESS) { + *size = stats.st_size; + } + + return (result); +} + +isc_result_t +isc_file_settime(const char *file, isc_time_t *time) { + int fh; + + REQUIRE(file != NULL && time != NULL); + + if ((fh = open(file, _O_RDWR | _O_BINARY)) < 0) { + return (isc__errno2result(errno)); + } + + /* + * Set the date via the filedate system call and return. Failing + * this call implies the new file times are not supported by the + * underlying file system. + */ + if (!SetFileTime((HANDLE)_get_osfhandle(fh), NULL, &time->absolute, + &time->absolute)) + { + close(fh); + errno = EINVAL; + return (isc__errno2result(errno)); + } + + close(fh); + return (ISC_R_SUCCESS); +} + +#undef TEMPLATE +#define TEMPLATE "XXXXXXXXXX.tmp" /* 14 characters. */ + +isc_result_t +isc_file_mktemplate(const char *path, char *buf, size_t buflen) { + return (isc_file_template(path, TEMPLATE, buf, buflen)); +} + +isc_result_t +isc_file_template(const char *path, const char *templet, char *buf, + size_t buflen) { + char *s; + + REQUIRE(templet != NULL); + REQUIRE(buf != NULL); + + if (path == NULL) { + path = ""; + } + + s = strrchr(templet, '\\'); + if (s != NULL) { + templet = s + 1; + } + + s = strrchr(path, '\\'); + + if (s != NULL) { + size_t prefixlen = s - path + 1; + if ((prefixlen + strlen(templet) + 1) > buflen) { + return (ISC_R_NOSPACE); + } + + /* Copy 'prefixlen' bytes and NUL terminate. */ + strlcpy(buf, path, ISC_MIN(prefixlen + 1, buflen)); + strlcat(buf, templet, buflen); + } else { + if ((strlen(templet) + 1) > buflen) { + return (ISC_R_NOSPACE); + } + + strlcpy(buf, templet, buflen); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_file_renameunique(const char *file, char *templet) { + int fd; + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(file != NULL); + REQUIRE(templet != NULL); + + fd = mkstemp(templet, true); + if (fd == -1) { + result = isc__errno2result(errno); + } else { + close(fd); + } + + if (result == ISC_R_SUCCESS) { + int res; + res = isc_file_safemovefile(file, templet); + if (res != 0) { + result = isc__errno2result(errno); + (void)unlink(templet); + } + } + return (result); +} + +static isc_result_t +openuniquemode(char *templet, int mode, bool binary, FILE **fp) { + int fd; + FILE *f; + isc_result_t result = ISC_R_SUCCESS; + + REQUIRE(templet != NULL); + REQUIRE(fp != NULL && *fp == NULL); + + /* + * Win32 does not have mkstemp. Using emulation above. + */ + fd = mkstemp(templet, binary); + + if (fd == -1) { + result = isc__errno2result(errno); + } + if (result == ISC_R_SUCCESS) { +#if 1 + UNUSED(mode); +#else /* if 1 */ + (void)fchmod(fd, mode); +#endif /* if 1 */ + f = fdopen(fd, binary ? "wb+" : "w+"); + if (f == NULL) { + result = isc__errno2result(errno); + (void)remove(templet); + (void)close(fd); + } else { + *fp = f; + } + } + + return (result); +} + +isc_result_t +isc_file_openuniqueprivate(char *templet, FILE **fp) { + int mode = _S_IREAD | _S_IWRITE; + return (openuniquemode(templet, mode, false, fp)); +} + +isc_result_t +isc_file_openunique(char *templet, FILE **fp) { + int mode = _S_IREAD | _S_IWRITE; + return (openuniquemode(templet, mode, false, fp)); +} + +isc_result_t +isc_file_openuniquemode(char *templet, int mode, FILE **fp) { + return (openuniquemode(templet, mode, false, fp)); +} + +isc_result_t +isc_file_bopenuniqueprivate(char *templet, FILE **fp) { + int mode = _S_IREAD | _S_IWRITE; + return (openuniquemode(templet, mode, true, fp)); +} + +isc_result_t +isc_file_bopenunique(char *templet, FILE **fp) { + int mode = _S_IREAD | _S_IWRITE; + return (openuniquemode(templet, mode, true, fp)); +} + +isc_result_t +isc_file_bopenuniquemode(char *templet, int mode, FILE **fp) { + return (openuniquemode(templet, mode, true, fp)); +} + +isc_result_t +isc_file_remove(const char *filename) { + int r; + + REQUIRE(filename != NULL); + + r = unlink(filename); + if (r == 0) { + return (ISC_R_SUCCESS); + } else { + return (isc__errno2result(errno)); + } +} + +isc_result_t +isc_file_rename(const char *oldname, const char *newname) { + int r; + + REQUIRE(oldname != NULL); + REQUIRE(newname != NULL); + + r = isc_file_safemovefile(oldname, newname); + if (r == 0) { + return (ISC_R_SUCCESS); + } else { + return (isc__errno2result(errno)); + } +} + +bool +isc_file_exists(const char *pathname) { + struct stat stats; + + REQUIRE(pathname != NULL); + + return (file_stats(pathname, &stats) == ISC_R_SUCCESS); +} + +isc_result_t +isc_file_isplainfile(const char *filename) { + /* + * This function returns success if filename is a plain file. + */ + struct stat filestat; + memset(&filestat, 0, sizeof(struct stat)); + + if ((stat(filename, &filestat)) == -1) { + return (isc__errno2result(errno)); + } + + if (!S_ISREG(filestat.st_mode)) { + return (ISC_R_INVALIDFILE); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_file_isplainfilefd(int fd) { + /* + * This function returns success if filename is a plain file. + */ + struct stat filestat; + memset(&filestat, 0, sizeof(struct stat)); + + if ((fstat(fd, &filestat)) == -1) { + return (isc__errno2result(errno)); + } + + if (!S_ISREG(filestat.st_mode)) { + return (ISC_R_INVALIDFILE); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_file_isdirectory(const char *filename) { + /* + * This function returns success if filename is a directory. + */ + struct stat filestat; + memset(&filestat, 0, sizeof(struct stat)); + + if ((stat(filename, &filestat)) == -1) { + return (isc__errno2result(errno)); + } + + if (!S_ISDIR(filestat.st_mode)) { + return (ISC_R_INVALIDFILE); + } + + return (ISC_R_SUCCESS); +} + +bool +isc_file_isabsolute(const char *filename) { + REQUIRE(filename != NULL); + /* + * Look for c:\path\... style, c:/path/... or \\computer\shar\path... + * the UNC style file specs + */ + if ((filename[0] == '\\') && (filename[1] == '\\')) { + return (true); + } + if (isalpha(filename[0]) && filename[1] == ':' && filename[2] == '\\') { + return (true); + } + if (isalpha(filename[0]) && filename[1] == ':' && filename[2] == '/') { + return (true); + } + return (false); +} + +bool +isc_file_iscurrentdir(const char *filename) { + REQUIRE(filename != NULL); + return (filename[0] == '.' && filename[1] == '\0'); +} + +bool +isc_file_ischdiridempotent(const char *filename) { + REQUIRE(filename != NULL); + + if (isc_file_isabsolute(filename)) { + return (true); + } + if (filename[0] == '\\') { + return (true); + } + if (filename[0] == '/') { + return (true); + } + if (isc_file_iscurrentdir(filename)) { + return (true); + } + return (false); +} + +const char * +isc_file_basename(const char *filename) { + char *s; + + REQUIRE(filename != NULL); + + s = strrchr(filename, '\\'); + if (s == NULL) { + return (filename); + } + return (s + 1); +} + +isc_result_t +isc_file_progname(const char *filename, char *progname, size_t namelen) { + const char *s; + const char *p; + size_t len; + + REQUIRE(filename != NULL); + REQUIRE(progname != NULL); + + /* + * Strip the path from the name + */ + s = isc_file_basename(filename); + if (s == NULL) { + return (ISC_R_NOSPACE); + } + + /* + * Strip any and all suffixes + */ + p = strchr(s, '.'); + if (p == NULL) { + if (namelen <= strlen(s)) { + return (ISC_R_NOSPACE); + } + + strlcpy(progname, s, namelen); + return (ISC_R_SUCCESS); + } + + /* + * Copy the result to the buffer + */ + len = p - s; + if (len >= namelen) { + return (ISC_R_NOSPACE); + } + + /* Copy up to 'len' bytes and NUL terminate. */ + strlcpy(progname, s, ISC_MIN(len + 1, namelen)); + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_file_absolutepath(const char *filename, char *path, size_t pathlen) { + char *ptrname; + DWORD retval; + + REQUIRE(filename != NULL); + REQUIRE(path != NULL); + + retval = GetFullPathName(filename, (DWORD)pathlen, path, &ptrname); + + /* Something went wrong in getting the path */ + if (retval == 0) { + return (ISC_R_NOTFOUND); + } + /* Caller needs to provide a larger buffer to contain the string */ + if (retval >= pathlen) { + return (ISC_R_NOSPACE); + } + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_file_truncate(const char *filename, isc_offset_t size) { + int fh; + + REQUIRE(filename != NULL && size >= 0); + + if ((fh = open(filename, _O_RDWR | _O_BINARY)) < 0) { + return (isc__errno2result(errno)); + } + + if (_chsize(fh, size) != 0) { + close(fh); + return (isc__errno2result(errno)); + } + close(fh); + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_file_safecreate(const char *filename, FILE **fp) { + isc_result_t result; + int flags; + struct stat sb; + FILE *f; + int fd; + + REQUIRE(filename != NULL); + REQUIRE(fp != NULL && *fp == NULL); + + result = file_stats(filename, &sb); + if (result == ISC_R_SUCCESS) { + if ((sb.st_mode & S_IFREG) == 0) { + return (ISC_R_INVALIDFILE); + } + flags = O_WRONLY | O_TRUNC; + } else if (result == ISC_R_FILENOTFOUND) { + flags = O_WRONLY | O_CREAT | O_EXCL; + } else { + return (result); + } + + fd = open(filename, flags, S_IRUSR | S_IWUSR); + if (fd == -1) { + return (isc__errno2result(errno)); + } + + f = fdopen(fd, "w"); + if (f == NULL) { + result = isc__errno2result(errno); + close(fd); + return (result); + } + + *fp = f; + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_file_splitpath(isc_mem_t *mctx, const char *path, char **dirname, + char const **basename) { + char *dir; + const char *file, *slash; + char *backslash; + + slash = strrchr(path, '/'); + + backslash = strrchr(path, '\\'); + if ((slash != NULL && backslash != NULL && backslash > slash) || + (slash == NULL && backslash != NULL)) + { + slash = backslash; + } + + if (slash == path) { + file = ++slash; + dir = isc_mem_strdup(mctx, "/"); + } else if (slash != NULL) { + file = ++slash; + dir = isc_mem_allocate(mctx, slash - path); + strlcpy(dir, path, slash - path); + } else { + file = path; + dir = isc_mem_strdup(mctx, "."); + } + + if (dir == NULL) { + return (ISC_R_NOMEMORY); + } + + if (*file == '\0') { + isc_mem_free(mctx, dir); + return (ISC_R_INVALIDFILE); + } + + *dirname = dir; + *basename = file; + + return (ISC_R_SUCCESS); +} + +void * +isc_file_mmap(void *addr, size_t len, int prot, int flags, int fd, + off_t offset) { + void *buf; + ssize_t ret; + off_t end; + + UNUSED(addr); + UNUSED(prot); + UNUSED(flags); + + end = lseek(fd, 0, SEEK_END); + lseek(fd, offset, SEEK_SET); + if (end - offset < (off_t)len) { + len = end - offset; + } + + buf = malloc(len); + if (buf == NULL) { + return (NULL); + } + + ret = read(fd, buf, (unsigned int)len); + if (ret != (ssize_t)len) { + free(buf); + buf = NULL; + } + + return (buf); +} + +int +isc_file_munmap(void *addr, size_t len) { + UNUSED(len); + free(addr); + return (0); +} + +#define DISALLOW "\\/:ABCDEFGHIJKLMNOPQRSTUVWXYZ" + +static isc_result_t +digest2hex(unsigned char *digest, unsigned int digestlen, char *hash, + size_t hashlen) { + unsigned int i; + int ret; + for (i = 0; i < digestlen; i++) { + size_t left = hashlen - i * 2; + ret = snprintf(hash + i * 2, left, "%02x", digest[i]); + if (ret < 0 || (size_t)ret >= left) { + return (ISC_R_NOSPACE); + } + } + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_file_sanitize(const char *dir, const char *base, const char *ext, + char *path, size_t length) { + char buf[PATH_MAX]; + unsigned char digest[ISC_MAX_MD_SIZE]; + unsigned int digestlen; + char hash[ISC_MAX_MD_SIZE * 2 + 1]; + size_t l = 0; + isc_result_t err; + + REQUIRE(base != NULL); + REQUIRE(path != NULL); + + l = strlen(base) + 1; + + /* + * allow room for a full sha256 hash (64 chars + * plus null terminator) + */ + if (l < 65) { + l = 65; + } + + if (dir != NULL) { + l += strlen(dir) + 1; + } + if (ext != NULL) { + l += strlen(ext) + 1; + } + + if (l > length || l > PATH_MAX) { + return (ISC_R_NOSPACE); + } + + /* Check whether the full-length SHA256 hash filename exists */ + err = isc_md(ISC_MD_SHA256, (const unsigned char *)base, strlen(base), + digest, &digestlen); + if (err != ISC_R_SUCCESS) { + return (err); + } + + err = digest2hex(digest, digestlen, hash, sizeof(hash)); + if (err != ISC_R_SUCCESS) { + return (err); + } + + snprintf(buf, sizeof(buf), "%s%s%s%s%s", dir != NULL ? dir : "", + dir != NULL ? "/" : "", hash, ext != NULL ? "." : "", + ext != NULL ? ext : ""); + if (isc_file_exists(buf)) { + strlcpy(path, buf, length); + return (ISC_R_SUCCESS); + } + + /* Check for a truncated SHA256 hash filename */ + hash[16] = '\0'; + snprintf(buf, sizeof(buf), "%s%s%s%s%s", dir != NULL ? dir : "", + dir != NULL ? "/" : "", hash, ext != NULL ? "." : "", + ext != NULL ? ext : ""); + if (isc_file_exists(buf)) { + strlcpy(path, buf, length); + return (ISC_R_SUCCESS); + } + + /* + * If neither hash filename already exists, then we'll use + * the original base name if it has no disallowed characters, + * or the truncated hash name if it does. + */ + if (strpbrk(base, DISALLOW) != NULL) { + strlcpy(path, buf, length); + return (ISC_R_SUCCESS); + } + + snprintf(buf, sizeof(buf), "%s%s%s%s%s", dir != NULL ? dir : "", + dir != NULL ? "/" : "", base, ext != NULL ? "." : "", + ext != NULL ? ext : ""); + strlcpy(path, buf, length); + return (ISC_R_SUCCESS); +} + +/* + * Based on http://blog.aaronballman.com/2011/08/how-to-check-access-rights/ + */ +bool +isc_file_isdirwritable(const char *path) { + DWORD length = 0; + HANDLE hToken = NULL; + PSECURITY_DESCRIPTOR security = NULL; + bool answer = false; + + if (isc_file_isdirectory(path) != ISC_R_SUCCESS) { + return (answer); + } + + /* + * Figure out buffer size. GetFileSecurity() should not succeed. + */ + if (GetFileSecurity(path, + OWNER_SECURITY_INFORMATION | + GROUP_SECURITY_INFORMATION | + DACL_SECURITY_INFORMATION, + NULL, 0, &length)) + { + return (answer); + } + + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + return (answer); + } + + security = malloc(length); + if (security == NULL) { + return (answer); + } + + /* + * GetFileSecurity() should succeed. + */ + if (!GetFileSecurity(path, + OWNER_SECURITY_INFORMATION | + GROUP_SECURITY_INFORMATION | + DACL_SECURITY_INFORMATION, + security, length, &length)) + { + return (answer); + } + + if (OpenProcessToken(GetCurrentProcess(), + TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_DUPLICATE | + STANDARD_RIGHTS_READ, + &hToken)) + { + HANDLE hImpersonatedToken = NULL; + + if (DuplicateToken(hToken, SecurityImpersonation, + &hImpersonatedToken)) + { + GENERIC_MAPPING mapping; + PRIVILEGE_SET privileges = { 0 }; + DWORD grantedAccess = 0; + DWORD privilegesLength = sizeof(privileges); + BOOL result = FALSE; + DWORD genericAccessRights = GENERIC_WRITE; + + mapping.GenericRead = FILE_GENERIC_READ; + mapping.GenericWrite = FILE_GENERIC_WRITE; + mapping.GenericExecute = FILE_GENERIC_EXECUTE; + mapping.GenericAll = FILE_ALL_ACCESS; + + MapGenericMask(&genericAccessRights, &mapping); + if (AccessCheck(security, hImpersonatedToken, + genericAccessRights, &mapping, + &privileges, &privilegesLength, + &grantedAccess, &result)) + { + answer = result; + } + CloseHandle(hImpersonatedToken); + } + CloseHandle(hToken); + } + free(security); + return (answer); +} diff --git a/lib/isc/win32/fsaccess.c b/lib/isc/win32/fsaccess.c new file mode 100644 index 0000000..909f9e5 --- /dev/null +++ b/lib/isc/win32/fsaccess.c @@ -0,0 +1,404 @@ +/* + * 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. + */ + +/* + * Note that Win32 does not have the concept of files having access + * and ownership bits. The FAT File system only has a readonly flag + * for everyone and that's all. NTFS uses ACL's which is a totally + * different concept of controlling access. + * + * This code needs to be revisited to set up proper access control for + * NTFS file systems. Nothing can be done for FAT file systems. + */ + +#include <aclapi.h> +#include <errno.h> +#include <io.h> +#include <stdbool.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <isc/file.h> +#include <isc/stat.h> +#include <isc/string.h> + +#include "errno2result.h" + +/* + * The OS-independent part of the API is in lib/isc. + */ +#include "../fsaccess.c" + +/* Store the user account name locally */ +static char username[255] = "\0"; +static DWORD namelen = 0; + +/* + * In order to set or retrieve access information, we need to obtain + * the File System type. These could be UNC-type shares. + */ + +BOOL +is_ntfs(const char *file) { + char drive[255]; + char FSType[20]; + char tmpbuf[256]; + char *machinename; + char *sharename; + char filename[1024]; + char *last; + + REQUIRE(filename != NULL); + + if (isc_file_absolutepath(file, filename, sizeof(filename)) != + ISC_R_SUCCESS) + { + return (FALSE); + } + + /* + * Look for c:\path\... style, c:/path/... or \\computer\shar\path... + * the UNC style file specs + */ + if (isalpha(filename[0]) && filename[1] == ':' && + (filename[2] == '\\' || filename[2] == '/')) + { + /* Copy 'c:\' or 'c:/' and NUL terminate. */ + strlcpy(drive, filename, ISC_MIN(3 + 1, sizeof(drive))); + } else if ((filename[0] == '\\') && (filename[1] == '\\')) { + /* Find the machine and share name and rebuild the UNC */ + strlcpy(tmpbuf, filename, sizeof(tmpbuf)); + machinename = strtok_r(tmpbuf, "\\", &last); + sharename = strtok_r(NULL, "\\", &last); + strlcpy(drive, "\\\\", sizeof(drive)); + strlcat(drive, machinename, sizeof(drive)); + strlcat(drive, "\\", sizeof(drive)); + strlcat(drive, sharename, sizeof(drive)); + strlcat(drive, "\\", sizeof(drive)); + } else { /* Not determinable */ + return (FALSE); + } + + GetVolumeInformation(drive, NULL, 0, NULL, 0, NULL, FSType, + sizeof(FSType)); + if (strcmp(FSType, "NTFS") == 0) { + return (TRUE); + } else { + return (FALSE); + } +} + +/* + * If it's not NTFS, we assume that it is FAT and proceed + * with almost nothing to do. Only the write flag can be set or + * cleared. + */ +isc_result_t +FAT_fsaccess_set(const char *path, isc_fsaccess_t access) { + int mode; + isc_fsaccess_t bits; + + /* + * Done with checking bad bits. Set mode_t. + */ + mode = 0; + +#define SET_AND_CLEAR1(modebit) \ + if ((access & bits) != 0) { \ + mode |= modebit; \ + access &= ~bits; \ + } +#define SET_AND_CLEAR(user, group, other) \ + SET_AND_CLEAR1(user); \ + bits <<= STEP; \ + SET_AND_CLEAR1(group); \ + bits <<= STEP; \ + SET_AND_CLEAR1(other); + + bits = ISC_FSACCESS_READ | ISC_FSACCESS_LISTDIRECTORY; + + SET_AND_CLEAR(S_IRUSR, S_IRGRP, S_IROTH); + + bits = ISC_FSACCESS_WRITE | ISC_FSACCESS_CREATECHILD | + ISC_FSACCESS_DELETECHILD; + + SET_AND_CLEAR(S_IWUSR, S_IWGRP, S_IWOTH); + + INSIST(access == 0); + + if (_chmod(path, mode) < 0) { + return (isc__errno2result(errno)); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +NTFS_Access_Control(const char *filename, const char *user, int access, + bool isdir) { + SECURITY_DESCRIPTOR sd; + BYTE aclBuffer[1024]; + PACL pacl = (PACL)&aclBuffer; + BYTE sidBuffer[100]; + PSID psid = (PSID)&sidBuffer; + DWORD sidBufferSize = sizeof(sidBuffer); + BYTE adminSidBuffer[100]; + PSID padminsid = (PSID)&adminSidBuffer; + DWORD adminSidBufferSize = sizeof(adminSidBuffer); + BYTE otherSidBuffer[100]; + PSID pothersid = (PSID)&otherSidBuffer; + DWORD otherSidBufferSize = sizeof(otherSidBuffer); + char domainBuffer[100]; + DWORD domainBufferSize = sizeof(domainBuffer); + SID_NAME_USE snu; + DWORD NTFSbits; + int caccess; + + /* Initialize an ACL */ + if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) { + return (ISC_R_NOPERM); + } + if (!InitializeAcl(pacl, sizeof(aclBuffer), ACL_REVISION)) { + return (ISC_R_NOPERM); + } + if (!LookupAccountName(0, user, psid, &sidBufferSize, domainBuffer, + &domainBufferSize, &snu)) + { + return (ISC_R_NOPERM); + } + domainBufferSize = sizeof(domainBuffer); + if (!LookupAccountName(0, "Administrators", padminsid, + &adminSidBufferSize, domainBuffer, + &domainBufferSize, &snu)) + { + (void)GetLastError(); + return (ISC_R_NOPERM); + } + domainBufferSize = sizeof(domainBuffer); + if (!LookupAccountName(0, "Everyone", pothersid, &otherSidBufferSize, + domainBuffer, &domainBufferSize, &snu)) + { + (void)GetLastError(); + return (ISC_R_NOPERM); + } + + caccess = access; + /* Owner check */ + + NTFSbits = 0; + if ((caccess & ISC_FSACCESS_READ) != 0) { + NTFSbits |= FILE_GENERIC_READ; + } + if ((caccess & ISC_FSACCESS_WRITE) != 0) { + NTFSbits |= FILE_GENERIC_WRITE; + } + if ((caccess & ISC_FSACCESS_EXECUTE) != 0) { + NTFSbits |= FILE_GENERIC_EXECUTE; + } + + /* For directories check the directory-specific bits */ + if (isdir) { + if ((caccess & ISC_FSACCESS_CREATECHILD) != 0) { + NTFSbits |= FILE_ADD_SUBDIRECTORY | FILE_ADD_FILE; + } + if ((caccess & ISC_FSACCESS_DELETECHILD) != 0) { + NTFSbits |= FILE_DELETE_CHILD; + } + if ((caccess & ISC_FSACCESS_LISTDIRECTORY) != 0) { + NTFSbits |= FILE_LIST_DIRECTORY; + } + if ((caccess & ISC_FSACCESS_ACCESSCHILD) != 0) { + NTFSbits |= FILE_TRAVERSE; + } + } + + if (NTFSbits == + (FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE)) + { + NTFSbits |= FILE_ALL_ACCESS; + } + /* + * Owner and Administrator also get STANDARD_RIGHTS_ALL + * to ensure that they have full control + */ + + NTFSbits |= STANDARD_RIGHTS_ALL; + + /* Add the ACE to the ACL */ + if (!AddAccessAllowedAce(pacl, ACL_REVISION, NTFSbits, psid)) { + return (ISC_R_NOPERM); + } + if (!AddAccessAllowedAce(pacl, ACL_REVISION, NTFSbits, padminsid)) { + return (ISC_R_NOPERM); + } + + /* + * Group is ignored since we can be in multiple groups or no group + * and its meaning is not clear on Win32 + */ + + caccess = caccess >> STEP; + + /* + * Other check. We translate this to be the same as Everyone + */ + + caccess = caccess >> STEP; + + NTFSbits = 0; + if ((caccess & ISC_FSACCESS_READ) != 0) { + NTFSbits |= FILE_GENERIC_READ; + } + if ((caccess & ISC_FSACCESS_WRITE) != 0) { + NTFSbits |= FILE_GENERIC_WRITE; + } + if ((caccess & ISC_FSACCESS_EXECUTE) != 0) { + NTFSbits |= FILE_GENERIC_EXECUTE; + } + + /* For directories check the directory-specific bits */ + if (isdir == TRUE) { + if ((caccess & ISC_FSACCESS_CREATECHILD) != 0) { + NTFSbits |= FILE_ADD_SUBDIRECTORY | FILE_ADD_FILE; + } + if ((caccess & ISC_FSACCESS_DELETECHILD) != 0) { + NTFSbits |= FILE_DELETE_CHILD; + } + if ((caccess & ISC_FSACCESS_LISTDIRECTORY) != 0) { + NTFSbits |= FILE_LIST_DIRECTORY; + } + if ((caccess & ISC_FSACCESS_ACCESSCHILD) != 0) { + NTFSbits |= FILE_TRAVERSE; + } + } + /* Add the ACE to the ACL */ + if (!AddAccessAllowedAce(pacl, ACL_REVISION, NTFSbits, pothersid)) { + return (ISC_R_NOPERM); + } + + if (!SetSecurityDescriptorDacl(&sd, TRUE, pacl, FALSE)) { + return (ISC_R_NOPERM); + } + if (!SetFileSecurity(filename, DACL_SECURITY_INFORMATION, &sd)) { + return (ISC_R_NOPERM); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +NTFS_fsaccess_set(const char *path, isc_fsaccess_t access, bool isdir) { + /* + * For NTFS we first need to get the name of the account under + * which BIND is running + */ + if (namelen == 0) { + namelen = sizeof(username); + if (GetUserName(username, &namelen) == 0) { + return (ISC_R_FAILURE); + } + } + return (NTFS_Access_Control(path, username, access, isdir)); +} + +isc_result_t +isc_fsaccess_set(const char *path, isc_fsaccess_t access) { + struct stat statb; + bool is_dir = false; + isc_result_t result; + + if (stat(path, &statb) != 0) { + return (isc__errno2result(errno)); + } + + if ((statb.st_mode & S_IFDIR) != 0) { + is_dir = true; + } else if ((statb.st_mode & S_IFREG) == 0) { + return (ISC_R_INVALIDFILE); + } + + result = check_bad_bits(access, is_dir); + if (result != ISC_R_SUCCESS) { + return (result); + } + + /* + * Determine if this is a FAT or NTFS disk and + * call the appropriate function to set the permissions + */ + if (is_ntfs(path)) { + return (NTFS_fsaccess_set(path, access, is_dir)); + } else { + return (FAT_fsaccess_set(path, access)); + } +} + +isc_result_t +isc_fsaccess_changeowner(const char *filename, const char *user) { + SECURITY_DESCRIPTOR psd; + BYTE sidBuffer[500]; + BYTE groupBuffer[500]; + PSID psid = (PSID)&sidBuffer; + DWORD sidBufferSize = sizeof(sidBuffer); + char domainBuffer[100]; + DWORD domainBufferSize = sizeof(domainBuffer); + SID_NAME_USE snu; + PSID pSidGroup = (PSID)&groupBuffer; + DWORD groupBufferSize = sizeof(groupBuffer); + + /* + * Determine if this is a FAT or NTFS disk and + * call the appropriate function to set the ownership + * FAT disks do not have ownership attributes so it's + * a noop. + */ + if (is_ntfs(filename) == FALSE) { + return (ISC_R_SUCCESS); + } + + if (!InitializeSecurityDescriptor(&psd, SECURITY_DESCRIPTOR_REVISION)) { + return (ISC_R_NOPERM); + } + + if (!LookupAccountName(0, user, psid, &sidBufferSize, domainBuffer, + &domainBufferSize, &snu)) + { + return (ISC_R_NOPERM); + } + + /* Make sure administrators can get to it */ + domainBufferSize = sizeof(domainBuffer); + if (!LookupAccountName(0, "Administrators", pSidGroup, &groupBufferSize, + domainBuffer, &domainBufferSize, &snu)) + { + return (ISC_R_NOPERM); + } + + if (!SetSecurityDescriptorOwner(&psd, psid, FALSE)) { + return (ISC_R_NOPERM); + } + + if (!SetSecurityDescriptorGroup(&psd, pSidGroup, FALSE)) { + return (ISC_R_NOPERM); + } + + if (!SetFileSecurity(filename, + OWNER_SECURITY_INFORMATION | + GROUP_SECURITY_INFORMATION, + &psd)) + { + return (ISC_R_NOPERM); + } + + return (ISC_R_SUCCESS); +} diff --git a/lib/isc/win32/include/.clang-format b/lib/isc/win32/include/.clang-format new file mode 120000 index 0000000..e919bba --- /dev/null +++ b/lib/isc/win32/include/.clang-format @@ -0,0 +1 @@ +../../../../.clang-format.headers
\ No newline at end of file diff --git a/lib/isc/win32/include/Makefile.in b/lib/isc/win32/include/Makefile.in new file mode 100644 index 0000000..60a0a67 --- /dev/null +++ b/lib/isc/win32/include/Makefile.in @@ -0,0 +1,19 @@ +# 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. + +srcdir = @srcdir@ +VPATH = @srcdir@ +top_srcdir = @top_srcdir@ + +SUBDIRS = isc +TARGETS = + +@BIND9_MAKE_RULES@ diff --git a/lib/isc/win32/include/isc/Makefile.in b/lib/isc/win32/include/isc/Makefile.in new file mode 100644 index 0000000..7718caa --- /dev/null +++ b/lib/isc/win32/include/isc/Makefile.in @@ -0,0 +1,32 @@ +# 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. + +srcdir = @srcdir@ +VPATH = @srcdir@ +top_srcdir = @top_srcdir@ + +VERSION=@BIND9_VERSION@ + +HEADERS = align.h dir.h mutex.h net.h netdb.h once.h \ + stat.h stdtime.h thread.h time.h + +SUBDIRS = +TARGETS = + +@BIND9_MAKE_RULES@ + +installdirs: + $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${includedir}\isc + +install:: installdirs + for i in $(HEADERS); do \ + $(INSTALL_DATA) $(srcdir)\$$i $(includedir)\isc || exit 1; \ + done diff --git a/lib/isc/win32/include/isc/align.h b/lib/isc/win32/include/isc/align.h new file mode 100644 index 0000000..6e98665 --- /dev/null +++ b/lib/isc/win32/include/isc/align.h @@ -0,0 +1,15 @@ +/* + * 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. + */ + +#pragma once +#define alignas(x) __declspec(align(x)) diff --git a/lib/isc/win32/include/isc/bind_registry.h b/lib/isc/win32/include/isc/bind_registry.h new file mode 100644 index 0000000..6c997d6 --- /dev/null +++ b/lib/isc/win32/include/isc/bind_registry.h @@ -0,0 +1,42 @@ +/* + * 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. + */ + +#ifndef ISC_BINDREGISTRY_H +#define ISC_BINDREGISTRY_H + +/* + * BIND makes use of the following Registry keys in various places, especially + * during startup and installation + */ + +#define BIND_SUBKEY "Software\\ISC\\BIND" +#define BIND_SESSION "CurrentSession" +#define BIND_SESSION_SUBKEY "Software\\ISC\\BIND\\CurrentSession" +#define BIND_UNINSTALL_SUBKEY \ + "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\ISC BIND" + +#define EVENTLOG_APP_SUBKEY \ + "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application" +#define BIND_MESSAGE_SUBKEY \ + "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\named" +#define BIND_MESSAGE_NAME "named" + +#define BIND_SERVICE_SUBKEY "SYSTEM\\CurrentControlSet\\Services\\named" + +#define BIND_CONFIGFILE 0 +#define BIND_DEBUGLEVEL 1 +#define BIND_QUERYLOG 2 +#define BIND_FOREGROUND 3 +#define BIND_PORT 4 + +#endif /* ISC_BINDREGISTRY_H */ diff --git a/lib/isc/win32/include/isc/bindevt.h b/lib/isc/win32/include/isc/bindevt.h new file mode 100644 index 0000000..406efe3 --- /dev/null +++ b/lib/isc/win32/include/isc/bindevt.h @@ -0,0 +1,83 @@ +/* + * 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. + */ + +#ifndef ISC_BINDEVT_H +#define ISC_BINDEVT_H 1 + +/* + * This is used for the event log for both logging the messages and + * later on by the event viewer when looking at the events + */ + +/* + * Values are 32 bit values laid out as follows: + * + * 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 + * 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 + * +---+-+-+-----------------------+-------------------------------+ + * |Sev|C|R| Facility | Code | + * +---+-+-+-----------------------+-------------------------------+ + * + * where + * + * Sev - is the severity code + * + * 00 - Success + * 01 - Informational + * 10 - Warning + * 11 - Error + * + * C - is the Customer code flag + * + * R - is a reserved bit + * + * Facility - is the facility code + * + * Code - is the facility's status code + * + * + * Define the facility codes + */ + +/* + * Define the severity codes + */ + +/* + * MessageId: BIND_ERR_MSG + * + * MessageText: + * + * %1 + */ +#define BIND_ERR_MSG ((DWORD)0xC0000001L) + +/* + * MessageId: BIND_WARN_MSG + * + * MessageText: + * + * %1 + */ +#define BIND_WARN_MSG ((DWORD)0x80000002L) + +/* + * MessageId: BIND_INFO_MSG + * + * MessageText: + * + * %1 + */ +#define BIND_INFO_MSG ((DWORD)0x40000003L) + +#endif /* ISC_BINDEVT_H */ diff --git a/lib/isc/win32/include/isc/condition.h b/lib/isc/win32/include/isc/condition.h new file mode 100644 index 0000000..738419c --- /dev/null +++ b/lib/isc/win32/include/isc/condition.h @@ -0,0 +1,60 @@ +/* + * 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. + */ + +#ifndef ISC_CONDITION_H +#define ISC_CONDITION_H 1 + +#include <windows.h> + +#include <isc/lang.h> +#include <isc/mutex.h> +#include <isc/thread.h> +#include <isc/types.h> + +typedef struct isc_condition_thread isc_condition_thread_t; + +struct isc_condition_thread { + uintptr_t th; + HANDLE handle[2]; + ISC_LINK(isc_condition_thread_t) link; +}; + +typedef struct isc_condition { + HANDLE events[2]; + unsigned int waiters; + ISC_LIST(isc_condition_thread_t) threadlist; +} isc_condition_t; + +ISC_LANG_BEGINDECLS + +void +isc_condition_init(isc_condition_t *); + +isc_result_t +isc_condition_wait(isc_condition_t *, isc_mutex_t *); + +isc_result_t +isc_condition_signal(isc_condition_t *); + +isc_result_t +isc_condition_broadcast(isc_condition_t *); + +isc_result_t +isc_condition_destroy(isc_condition_t *); + +isc_result_t +isc_condition_waituntil(isc_condition_t *, isc_mutex_t *, isc_time_t *); + +ISC_LANG_ENDDECLS + +#endif /* ISC_CONDITION_H */ diff --git a/lib/isc/win32/include/isc/dir.h b/lib/isc/win32/include/isc/dir.h new file mode 100644 index 0000000..f0050dd --- /dev/null +++ b/lib/isc/win32/include/isc/dir.h @@ -0,0 +1,73 @@ +/* + * 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. + */ + +#ifndef ISC_DIR_H +#define ISC_DIR_H 1 + +#include <stdbool.h> +#include <stdlib.h> +#include <windows.h> + +#include <isc/lang.h> +#include <isc/platform.h> +#include <isc/result.h> + +typedef struct { + char name[NAME_MAX]; + unsigned int length; + WIN32_FIND_DATA find_data; +} isc_direntry_t; + +typedef struct { + unsigned int magic; + char dirname[PATH_MAX]; + isc_direntry_t entry; + bool entry_filled; + HANDLE search_handle; +} isc_dir_t; + +ISC_LANG_BEGINDECLS + +void +isc_dir_init(isc_dir_t *dir); + +isc_result_t +isc_dir_open(isc_dir_t *dir, const char *dirname); + +isc_result_t +isc_dir_read(isc_dir_t *dir); + +isc_result_t +isc_dir_reset(isc_dir_t *dir); + +void +isc_dir_close(isc_dir_t *dir); + +isc_result_t +isc_dir_chdir(const char *dirname); + +isc_result_t +isc_dir_chroot(const char *dirname); + +isc_result_t +isc_dir_createunique(char *templet); +/* + * Use a templet (such as from isc_file_mktemplate()) to create a uniquely + * named, empty directory. The templet string is modified in place. + * If result == ISC_R_SUCCESS, it is the name of the directory that was + * created. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_DIR_H */ diff --git a/lib/isc/win32/include/isc/ipv6.h b/lib/isc/win32/include/isc/ipv6.h new file mode 100644 index 0000000..4ab567a --- /dev/null +++ b/lib/isc/win32/include/isc/ipv6.h @@ -0,0 +1,130 @@ +/* + * 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. + */ + +#ifndef ISC_IPV6_H +#define ISC_IPV6_H 1 + +/***** +***** Module Info +*****/ + +/* + * IPv6 definitions for systems which do not support IPv6. + * + * MP: + * No impact. + * + * Reliability: + * No anticipated impact. + * + * Resources: + * N/A. + * + * Security: + * No anticipated impact. + * + * Standards: + * RFC2553. + */ + +#if _MSC_VER < 1300 +#define in6_addr in_addr6 +#endif /* if _MSC_VER < 1300 */ + +#ifndef IN6ADDR_ANY_INIT +#define IN6ADDR_ANY_INIT \ + { \ + { \ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 \ + } \ + } +#endif /* ifndef IN6ADDR_ANY_INIT */ +#ifndef IN6ADDR_LOOPBACK_INIT +#define IN6ADDR_LOOPBACK_INIT \ + { \ + { \ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 \ + } \ + } +#endif /* ifndef IN6ADDR_LOOPBACK_INIT */ +#ifndef IN6ADDR_V4MAPPED_INIT +#define IN6ADDR_V4MAPPED_INIT \ + { \ + { \ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0, 0, 0, 0 \ + } \ + } +#endif /* ifndef IN6ADDR_V4MAPPED_INIT */ + +LIBISC_EXTERNAL_DATA extern const struct in6_addr isc_in6addr_any; +LIBISC_EXTERNAL_DATA extern const struct in6_addr isc_in6addr_loopback; + +/* + * Unspecified + */ +#ifndef IN6_IS_ADDR_UNSPECIFIED +#define IN6_IS_ADDR_UNSPECIFIED(a) \ + (*((u_long *)((a)->s6_addr)) == 0 && \ + *((u_long *)((a)->s6_addr) + 1) == 0 && \ + *((u_long *)((a)->s6_addr) + 2) == 0 && \ + *((u_long *)((a)->s6_addr) + 3) == 0) +#endif /* ifndef IN6_IS_ADDR_UNSPECIFIED */ + +/* + * Loopback + */ +#ifndef IN6_IS_ADDR_LOOPBACK +#define IN6_IS_ADDR_LOOPBACK(a) \ + (*((u_long *)((a)->s6_addr)) == 0 && \ + *((u_long *)((a)->s6_addr) + 1) == 0 && \ + *((u_long *)((a)->s6_addr) + 2) == 0 && \ + *((u_long *)((a)->s6_addr) + 3) == htonl(1)) +#endif /* ifndef IN6_IS_ADDR_LOOPBACK */ + +/* + * IPv4 compatible + */ +#define IN6_IS_ADDR_V4COMPAT(a) \ + (*((u_long *)((a)->s6_addr)) == 0 && \ + *((u_long *)((a)->s6_addr) + 1) == 0 && \ + *((u_long *)((a)->s6_addr) + 2) == 0 && \ + *((u_long *)((a)->s6_addr) + 3) != 0 && \ + *((u_long *)((a)->s6_addr) + 3) != htonl(1)) + +/* + * Mapped + */ +#define IN6_IS_ADDR_V4MAPPED(a) \ + (*((u_long *)((a)->s6_addr)) == 0 && \ + *((u_long *)((a)->s6_addr) + 1) == 0 && \ + *((u_long *)((a)->s6_addr) + 2) == htonl(0x0000ffff)) + +/* + * Multicast + */ +#define IN6_IS_ADDR_MULTICAST(a) ((a)->s6_addr[0] == 0xffU) + +/* + * Unicast link / site local. + */ +#ifndef IN6_IS_ADDR_LINKLOCAL +#define IN6_IS_ADDR_LINKLOCAL(a) \ + (((a)->s6_addr[0] == 0xfe) && (((a)->s6_addr[1] & 0xc0) == 0x80)) +#endif /* ifndef IN6_IS_ADDR_LINKLOCAL */ + +#ifndef IN6_IS_ADDR_SITELOCAL +#define IN6_IS_ADDR_SITELOCAL(a) \ + (((a)->s6_addr[0] == 0xfe) && (((a)->s6_addr[1] & 0xc0) == 0xc0)) +#endif /* ifndef IN6_IS_ADDR_SITELOCAL */ + +#endif /* ISC_IPV6_H */ diff --git a/lib/isc/win32/include/isc/mutex.h b/lib/isc/win32/include/isc/mutex.h new file mode 100644 index 0000000..3fcc538 --- /dev/null +++ b/lib/isc/win32/include/isc/mutex.h @@ -0,0 +1,47 @@ +/* + * 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. + */ + +#ifndef ISC_MUTEX_H +#define ISC_MUTEX_H 1 + +#include <windows.h> + +#include <isc/net.h> +#include <isc/result.h> + +typedef CRITICAL_SECTION isc_mutex_t; + +/* + * This definition is here since some versions of WINBASE.H + * omits it for some reason. + */ +#if (_WIN32_WINNT < 0x0400) +WINBASEAPI BOOL WINAPI +TryEnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection); +#endif /* _WIN32_WINNT < 0x0400 */ + +#define isc_mutex_init(mp) InitializeCriticalSection((mp)) +#define isc_mutex_lock(mp) (EnterCriticalSection((mp)), ISC_R_SUCCESS) +#define isc_mutex_unlock(mp) (LeaveCriticalSection((mp)), ISC_R_SUCCESS) +#define isc_mutex_trylock(mp) \ + (TryEnterCriticalSection((mp)) ? ISC_R_SUCCESS : ISC_R_LOCKBUSY) +#define isc_mutex_destroy(mp) (DeleteCriticalSection((mp))) + +/* + * This is a placeholder for now since we are not keeping any mutex stats + */ +#define isc_mutex_stats(fp) \ + do { \ + } while (0) + +#endif /* ISC_MUTEX_H */ diff --git a/lib/isc/win32/include/isc/net.h b/lib/isc/win32/include/isc/net.h new file mode 100644 index 0000000..391614e --- /dev/null +++ b/lib/isc/win32/include/isc/net.h @@ -0,0 +1,402 @@ +/* + * 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. + */ + +#ifndef ISC_NET_H +#define ISC_NET_H 1 + +/***** +***** Module Info +*****/ + +/* + * Basic Networking Types + * + * This module is responsible for defining the following basic networking + * types: + * + * struct in_addr + * struct in6_addr + * struct in6_pktinfo + * struct sockaddr + * struct sockaddr_in + * struct sockaddr_in6 + * in_port_t + * + * It ensures that the AF_ and PF_ macros are defined. + * + * It declares ntoh[sl]() and hton[sl](). + * + * It declares inet_ntop(), and inet_pton(). + * + * It ensures that INADDR_ANY, IN6ADDR_ANY_INIT, in6addr_any, and + * in6addr_loopback are available. + * + * It ensures that IN_MULTICAST() is available to check for multicast + * addresses. + * + * MP: + * No impact. + * + * Reliability: + * No anticipated impact. + * + * Resources: + * N/A. + * + * Security: + * No anticipated impact. + * + * Standards: + * BSD Socket API + * RFC2553 + */ + +/*** + *** Imports. + ***/ +#include <inttypes.h> + +#include <isc/platform.h> + +/* + * Because of some sort of problem in the MS header files, this cannot + * be simple "#include <winsock2.h>", because winsock2.h tries to include + * windows.h, which then generates an error out of mswsock.h. _You_ + * figure it out. + */ +#ifndef _WINSOCKAPI_ +#define _WINSOCKAPI_ /* Prevent inclusion of winsock.h in windows.h */ +#endif /* ifndef _WINSOCKAPI_ */ + +#include <winsock2.h> +#include <ws2tcpip.h> + +#include <isc/ipv6.h> +#include <isc/lang.h> +#include <isc/types.h> + +#include <sys/types.h> + +/* + * This is here because named client, interfacemgr.c, etc. use the name as + * a variable + */ +#undef interface + +#ifndef INADDR_ANY +#define INADDR_ANY 0x00000000UL +#endif /* ifndef INADDR_ANY */ + +#ifndef INADDR_LOOPBACK +#define INADDR_LOOPBACK 0x7f000001UL +#endif /* ifndef INADDR_LOOPBACK */ + +#if _MSC_VER < 1300 +#define in6addr_any isc_in6addr_any +#define in6addr_loopback isc_in6addr_loopback +#endif /* if _MSC_VER < 1300 */ + +/* + * Ensure type in_port_t is defined. + */ +typedef uint16_t in_port_t; + +/* + * If this system does not have MSG_TRUNC (as returned from recvmsg()) + * ISC_PLATFORM_RECVOVERFLOW will be defined. This will enable the MSG_TRUNC + * faking code in socket.c. + */ +#ifndef MSG_TRUNC +#define ISC_PLATFORM_RECVOVERFLOW +#endif /* ifndef MSG_TRUNC */ + +#define ISC__IPADDR(x) ((uint32_t)htonl((uint32_t)(x))) + +#define ISC_IPADDR_ISMULTICAST(i) \ + (((uint32_t)(i)&ISC__IPADDR(0xf0000000)) == ISC__IPADDR(0xe0000000)) + +#define ISC_IPADDR_ISEXPERIMENTAL(i) \ + (((uint32_t)(i)&ISC__IPADDR(0xf0000000)) == ISC__IPADDR(0xf0000000)) + +/* + * Fix the FD_SET and FD_CLR Macros to properly cast + */ +#undef FD_CLR +#define FD_CLR(fd, set) \ + do { \ + u_int __i; \ + for (__i = 0; __i < ((fd_set FAR *)(set))->fd_count; __i++) { \ + if (((fd_set FAR *)(set))->fd_array[__i] == \ + (SOCKET)fd) { \ + while (__i < \ + ((fd_set FAR *)(set))->fd_count - 1) \ + { \ + ((fd_set FAR *)(set))->fd_array[__i] = \ + ((fd_set FAR *)(set)) \ + ->fd_array[__i + 1]; \ + __i++; \ + } \ + ((fd_set FAR *)(set))->fd_count--; \ + break; \ + } \ + } \ + } while (0) + +#undef FD_SET +#define FD_SET(fd, set) \ + do { \ + u_int __i; \ + for (__i = 0; __i < ((fd_set FAR *)(set))->fd_count; __i++) { \ + if (((fd_set FAR *)(set))->fd_array[__i] == \ + (SOCKET)(fd)) \ + { \ + break; \ + } \ + } \ + if (__i == ((fd_set FAR *)(set))->fd_count) { \ + if (((fd_set FAR *)(set))->fd_count < FD_SETSIZE) { \ + ((fd_set FAR *)(set))->fd_array[__i] = \ + (SOCKET)(fd); \ + ((fd_set FAR *)(set))->fd_count++; \ + } \ + } \ + } while (0) + +/* + * Windows Sockets errors redefined as regular Berkeley error constants. + * These are usually commented out in Windows NT to avoid conflicts with + * errno.h. Use the WSA constants instead. + */ + +#include <errno.h> + +#ifndef EWOULDBLOCK +#define EWOULDBLOCK WSAEWOULDBLOCK +#endif /* ifndef EWOULDBLOCK */ +#ifndef EINPROGRESS +#define EINPROGRESS WSAEINPROGRESS +#endif /* ifndef EINPROGRESS */ +#ifndef EALREADY +#define EALREADY WSAEALREADY +#endif /* ifndef EALREADY */ +#ifndef ENOTSOCK +#define ENOTSOCK WSAENOTSOCK +#endif /* ifndef ENOTSOCK */ +#ifndef EDESTADDRREQ +#define EDESTADDRREQ WSAEDESTADDRREQ +#endif /* ifndef EDESTADDRREQ */ +#ifndef EMSGSIZE +#define EMSGSIZE WSAEMSGSIZE +#endif /* ifndef EMSGSIZE */ +#ifndef EPROTOTYPE +#define EPROTOTYPE WSAEPROTOTYPE +#endif /* ifndef EPROTOTYPE */ +#ifndef ENOPROTOOPT +#define ENOPROTOOPT WSAENOPROTOOPT +#endif /* ifndef ENOPROTOOPT */ +#ifndef EPROTONOSUPPORT +#define EPROTONOSUPPORT WSAEPROTONOSUPPORT +#endif /* ifndef EPROTONOSUPPORT */ +#ifndef ESOCKTNOSUPPORT +#define ESOCKTNOSUPPORT WSAESOCKTNOSUPPORT +#endif /* ifndef ESOCKTNOSUPPORT */ +#ifndef EOPNOTSUPP +#define EOPNOTSUPP WSAEOPNOTSUPP +#endif /* ifndef EOPNOTSUPP */ +#ifndef EPFNOSUPPORT +#define EPFNOSUPPORT WSAEPFNOSUPPORT +#endif /* ifndef EPFNOSUPPORT */ +#ifndef EAFNOSUPPORT +#define EAFNOSUPPORT WSAEAFNOSUPPORT +#endif /* ifndef EAFNOSUPPORT */ +#ifndef EADDRINUSE +#define EADDRINUSE WSAEADDRINUSE +#endif /* ifndef EADDRINUSE */ +#ifndef EADDRNOTAVAIL +#define EADDRNOTAVAIL WSAEADDRNOTAVAIL +#endif /* ifndef EADDRNOTAVAIL */ +#ifndef ENETDOWN +#define ENETDOWN WSAENETDOWN +#endif /* ifndef ENETDOWN */ +#ifndef ENETUNREACH +#define ENETUNREACH WSAENETUNREACH +#endif /* ifndef ENETUNREACH */ +#ifndef ENETRESET +#define ENETRESET WSAENETRESET +#endif /* ifndef ENETRESET */ +#ifndef ECONNABORTED +#define ECONNABORTED WSAECONNABORTED +#endif /* ifndef ECONNABORTED */ +#ifndef ECONNRESET +#define ECONNRESET WSAECONNRESET +#endif /* ifndef ECONNRESET */ +#ifndef ENOBUFS +#define ENOBUFS WSAENOBUFS +#endif /* ifndef ENOBUFS */ +#ifndef EISCONN +#define EISCONN WSAEISCONN +#endif /* ifndef EISCONN */ +#ifndef ENOTCONN +#define ENOTCONN WSAENOTCONN +#endif /* ifndef ENOTCONN */ +#ifndef ESHUTDOWN +#define ESHUTDOWN WSAESHUTDOWN +#endif /* ifndef ESHUTDOWN */ +#ifndef ETOOMANYREFS +#define ETOOMANYREFS WSAETOOMANYREFS +#endif /* ifndef ETOOMANYREFS */ +#ifndef ETIMEDOUT +#define ETIMEDOUT WSAETIMEDOUT +#endif /* ifndef ETIMEDOUT */ +#ifndef ECONNREFUSED +#define ECONNREFUSED WSAECONNREFUSED +#endif /* ifndef ECONNREFUSED */ +#ifndef ELOOP +#define ELOOP WSAELOOP +#endif /* ifndef ELOOP */ +#ifndef EHOSTDOWN +#define EHOSTDOWN WSAEHOSTDOWN +#endif /* ifndef EHOSTDOWN */ +#ifndef EHOSTUNREACH +#define EHOSTUNREACH WSAEHOSTUNREACH +#endif /* ifndef EHOSTUNREACH */ +#ifndef EPROCLIM +#define EPROCLIM WSAEPROCLIM +#endif /* ifndef EPROCLIM */ +#ifndef EUSERS +#define EUSERS WSAEUSERS +#endif /* ifndef EUSERS */ +#ifndef EDQUOT +#define EDQUOT WSAEDQUOT +#endif /* ifndef EDQUOT */ +#ifndef ESTALE +#define ESTALE WSAESTALE +#endif /* ifndef ESTALE */ +#ifndef EREMOTE +#define EREMOTE WSAEREMOTE +#endif /* ifndef EREMOTE */ + +/*** + *** Functions. + ***/ + +ISC_LANG_BEGINDECLS + +isc_result_t +isc_net_probeipv4(void); +/* + * Check if the system's kernel supports IPv4. + * + * Returns: + * + * ISC_R_SUCCESS IPv4 is supported. + * ISC_R_NOTFOUND IPv4 is not supported. + * ISC_R_DISABLED IPv4 is disabled. + * ISC_R_UNEXPECTED + */ + +isc_result_t +isc_net_probeipv6(void); +/* + * Check if the system's kernel supports IPv6. + * + * Returns: + * + * ISC_R_SUCCESS IPv6 is supported. + * ISC_R_NOTFOUND IPv6 is not supported. + * ISC_R_DISABLED IPv6 is disabled. + * ISC_R_UNEXPECTED + */ + +isc_result_t +isc_net_probeunix(void); +/* + * Check if UNIX domain sockets are supported. + * + * Returns: + * + * ISC_R_SUCCESS + * ISC_R_NOTFOUND + */ + +#define ISC_NET_DSCPRECVV4 0x01 /* Can receive sent DSCP value IPv4 */ +#define ISC_NET_DSCPRECVV6 0x02 /* Can receive sent DSCP value IPv6 */ +#define ISC_NET_DSCPSETV4 0x04 /* Can set DSCP on socket IPv4 */ +#define ISC_NET_DSCPSETV6 0x08 /* Can set DSCP on socket IPv6 */ +#define ISC_NET_DSCPPKTV4 0x10 /* Can set DSCP on per packet IPv4 */ +#define ISC_NET_DSCPPKTV6 0x20 /* Can set DSCP on per packet IPv6 */ +#define ISC_NET_DSCPALL 0x3f /* All valid flags */ + +unsigned int +isc_net_probedscp(void); +/*%< + * Probe the level of DSCP support. + */ + +isc_result_t +isc_net_probe_ipv6only(void); +/* + * Check if the system's kernel supports the IPV6_V6ONLY socket option. + * + * Returns: + * + * ISC_R_SUCCESS the option is supported for both TCP and UDP. + * ISC_R_NOTFOUND IPv6 itself or the option is not supported. + * ISC_R_UNEXPECTED + */ + +isc_result_t +isc_net_probe_ipv6pktinfo(void); +/* + * Check if the system's kernel supports the IPV6_(RECV)PKTINFO socket option + * for UDP sockets. + * + * Returns: + * + * ISC_R_SUCCESS the option is supported. + * ISC_R_NOTFOUND IPv6 itself or the option is not supported. + * ISC_R_UNEXPECTED + */ + +void +isc_net_disableipv4(void); + +void +isc_net_disableipv6(void); + +void +isc_net_enableipv4(void); + +void +isc_net_enableipv6(void); + +isc_result_t +isc_net_getudpportrange(int af, in_port_t *low, in_port_t *high); +/*%< + * Returns system's default range of ephemeral UDP ports, if defined. + * If the range is not available or unknown, ISC_NET_PORTRANGELOW and + * ISC_NET_PORTRANGEHIGH will be returned. + * + * Requires: + * + *\li 'low' and 'high' must be non NULL. + * + * Returns: + * + *\li *low and *high will be the ports specifying the low and high ends of + * the range. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_NET_H */ diff --git a/lib/isc/win32/include/isc/netdb.h b/lib/isc/win32/include/isc/netdb.h new file mode 100644 index 0000000..e37ecb5 --- /dev/null +++ b/lib/isc/win32/include/isc/netdb.h @@ -0,0 +1,48 @@ +/* + * 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. + */ + +#ifndef ISC_NETDB_H +#define ISC_NETDB_H 1 + +/***** +***** Module Info +*****/ + +/* + * Portable netdb.h support. + * + * This module is responsible for defining the get<x>by<y> APIs. + * + * MP: + * No impact. + * + * Reliability: + * No anticipated impact. + * + * Resources: + * N/A. + * + * Security: + * No anticipated impact. + * + * Standards: + * BSD API + */ + +/*** + *** Imports. + ***/ + +#include <isc/net.h> + +#endif /* ISC_NETDB_H */ diff --git a/lib/isc/win32/include/isc/ntgroups.h b/lib/isc/win32/include/isc/ntgroups.h new file mode 100644 index 0000000..496f84a --- /dev/null +++ b/lib/isc/win32/include/isc/ntgroups.h @@ -0,0 +1,28 @@ +/* + * 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. + */ + +#ifndef ISC_NTGROUPS_H +#define ISC_NTGROUPS_H 1 + +#include <isc/lang.h> +#include <isc/result.h> + +ISC_LANG_BEGINDECLS + +isc_result_t +isc_ntsecurity_getaccountgroups(char *name, char **Groups, + unsigned int maxgroups, unsigned int *total); + +ISC_LANG_ENDDECLS + +#endif /* ISC_NTGROUPS_H */ diff --git a/lib/isc/win32/include/isc/ntpaths.h b/lib/isc/win32/include/isc/ntpaths.h new file mode 100644 index 0000000..37c915d --- /dev/null +++ b/lib/isc/win32/include/isc/ntpaths.h @@ -0,0 +1,66 @@ +/* + * 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. + */ + +/* + * Windows-specific path definitions + * These routines are used to set up and return system-specific path + * information about the files enumerated in NtPaths + */ + +#ifndef ISC_NTPATHS_H +#define ISC_NTPATHS_H + +#include <isc/lang.h> + +/* + * Index of paths needed + */ +enum NtPaths { + NAMED_CONF_PATH, + RESOLV_CONF_PATH, + RNDC_CONF_PATH, + NAMED_PID_PATH, + NAMED_LOCK_PATH, + LOCAL_STATE_DIR, + SYS_CONF_DIR, + RNDC_KEY_PATH, + SESSION_KEY_PATH, + BIND_KEYS_PATH +}; + +/* + * Define macros to get the path of the config files + */ +#define NAMED_CONFFILE isc_ntpaths_get(NAMED_CONF_PATH) +#define RNDC_CONFFILE isc_ntpaths_get(RNDC_CONF_PATH) +#define RNDC_KEYFILE isc_ntpaths_get(RNDC_KEY_PATH) +#define SESSION_KEYFILE isc_ntpaths_get(SESSION_KEY_PATH) +#define RESOLV_CONF isc_ntpaths_get(RESOLV_CONF_PATH) + +/* + * Information about where the files are on disk + */ +#define NAMED_LOCALSTATEDIR "/dns/bin" +#define NAMED_SYSCONFDIR "/dns/etc" + +ISC_LANG_BEGINDECLS + +void +isc_ntpaths_init(void); + +char * +isc_ntpaths_get(int); + +ISC_LANG_ENDDECLS + +#endif /* ISC_NTPATHS_H */ diff --git a/lib/isc/win32/include/isc/offset.h b/lib/isc/win32/include/isc/offset.h new file mode 100644 index 0000000..f09cfdb --- /dev/null +++ b/lib/isc/win32/include/isc/offset.h @@ -0,0 +1,26 @@ +/* + * 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. + */ + +#ifndef ISC_OFFSET_H +#define ISC_OFFSET_H 1 + +/* + * File offsets are operating-system dependent. + */ +#include <limits.h> /* Required for CHAR_BIT. */ + +#include <sys/types.h> + +typedef _off_t isc_offset_t; + +#endif /* ISC_OFFSET_H */ diff --git a/lib/isc/win32/include/isc/once.h b/lib/isc/win32/include/isc/once.h new file mode 100644 index 0000000..d2d841f --- /dev/null +++ b/lib/isc/win32/include/isc/once.h @@ -0,0 +1,40 @@ +/* + * 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. + */ + +#ifndef ISC_ONCE_H +#define ISC_ONCE_H 1 + +#include <isc/lang.h> +#include <isc/result.h> + +ISC_LANG_BEGINDECLS + +typedef struct { + int status; + int counter; +} isc_once_t; + +#define ISC_ONCE_INIT_NEEDED 0 +#define ISC_ONCE_INIT_DONE 1 + +#define ISC_ONCE_INIT \ + { \ + ISC_ONCE_INIT_NEEDED, 1 \ + } + +isc_result_t +isc_once_do(isc_once_t *controller, void (*function)(void)); + +ISC_LANG_ENDDECLS + +#endif /* ISC_ONCE_H */ diff --git a/lib/isc/win32/include/isc/platform.h.in b/lib/isc/win32/include/isc/platform.h.in new file mode 100644 index 0000000..b3fad9c --- /dev/null +++ b/lib/isc/win32/include/isc/platform.h.in @@ -0,0 +1,132 @@ +/* + * 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. + */ + +#ifndef ISC_PLATFORM_H +#define ISC_PLATFORM_H 1 + +/***** + ***** Platform-dependent defines. + *****/ + +#if _MSC_VER > 1400 +#define HAVE_TLS 1 +#define HAVE___DECLSPEC_THREAD 1 +#endif + +/* + * Some compatibility cludges + */ + +#if defined(_WIN32) || defined(_WIN64) +/* We are on Windows */ +# define strtok_r strtok_s + +#define ISC_STRERRORSIZE 128 + +#ifndef strtoull +#define strtoull _strtoui64 +#endif + +#include <stdint.h> +#if _MSC_VER < 1914 +typedef uint32_t socklen_t; +#endif + +#endif + +#define __builtin_unreachable() __assume(0) + +/* + * Remove __attribute__ ((foo)) on Windows + */ + +#define __attribute__(attribute) /* do nothing */ + +/* + * Limits + */ + +#ifndef NAME_MAX +#define NAME_MAX _MAX_FNAME +#endif + +#ifndef PATH_MAX +#define PATH_MAX _MAX_PATH +#endif + +/*** + *** Network. + ***/ + +#undef MSG_TRUNC + +typedef uint16_t sa_family_t; + +/* + * Define if the platform has <sys/un.h>. + */ +#undef ISC_PLATFORM_HAVESYSUNH + +/* + * Defines for the noreturn attribute. + */ +#define ISC_PLATFORM_NORETURN_PRE __declspec(noreturn) +#define ISC_PLATFORM_NORETURN_POST + +/* + * Set up a macro for importing and exporting from the DLL + */ + +#ifdef LIBISC_EXPORTS +#define LIBISC_EXTERNAL_DATA __declspec(dllexport) +#else +#define LIBISC_EXTERNAL_DATA __declspec(dllimport) +#endif + +#ifdef LIBDNS_EXPORTS +#define LIBDNS_EXTERNAL_DATA __declspec(dllexport) +#else +#define LIBDNS_EXTERNAL_DATA __declspec(dllimport) +#endif + +#ifdef LIBISCCC_EXPORTS +#define LIBISCCC_EXTERNAL_DATA __declspec(dllexport) +#else +#define LIBISCCC_EXTERNAL_DATA __declspec(dllimport) +#endif + +#ifdef LIBISCCFG_EXPORTS +#define LIBISCCFG_EXTERNAL_DATA __declspec(dllexport) +#else +#define LIBISCCFG_EXTERNAL_DATA __declspec(dllimport) +#endif + +#ifdef LIBNS_EXPORTS +#define LIBNS_EXTERNAL_DATA __declspec(dllexport) +#else +#define LIBNS_EXTERNAL_DATA __declspec(dllimport) +#endif + +#ifdef LIBBIND9_EXPORTS +#define LIBBIND9_EXTERNAL_DATA __declspec(dllexport) +#else +#define LIBBIND9_EXTERNAL_DATA __declspec(dllimport) +#endif + +#ifdef LIBTESTS_EXPORTS +#define LIBTESTS_EXTERNAL_DATA __declspec(dllexport) +#else +#define LIBTESTS_EXTERNAL_DATA __declspec(dllimport) +#endif + +#endif /* ISC_PLATFORM_H */ diff --git a/lib/isc/win32/include/isc/stat.h b/lib/isc/win32/include/isc/stat.h new file mode 100644 index 0000000..63577f9 --- /dev/null +++ b/lib/isc/win32/include/isc/stat.h @@ -0,0 +1,58 @@ +/* + * 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. + */ + +#ifndef ISC_STAT_H +#define ISC_STAT_H 1 + +#include <sys/stat.h> + +/* + * Windows doesn't typedef this. + */ +typedef unsigned short mode_t; + +/* open() under unix allows setting of read/write permissions + * at the owner, group and other levels. These don't exist in NT + * We'll just map them all to the NT equivalent + */ + +#define S_IREAD _S_IREAD /* read permission, owner */ +#define S_IWRITE _S_IWRITE /* write permission, owner */ +#define S_IRUSR _S_IREAD /* Owner read permission */ +#define S_IWUSR _S_IWRITE /* Owner write permission */ +#define S_IRGRP _S_IREAD /* Group read permission */ +#define S_IWGRP _S_IWRITE /* Group write permission */ +#define S_IROTH _S_IREAD /* Other read permission */ +#define S_IWOTH _S_IWRITE /* Other write permission */ + +#ifndef S_IFMT +#define S_IFMT _S_IFMT +#endif /* ifndef S_IFMT */ +#ifndef S_IFDIR +#define S_IFDIR _S_IFDIR +#endif /* ifndef S_IFDIR */ +#ifndef S_IFCHR +#define S_IFCHR _S_IFCHR +#endif /* ifndef S_IFCHR */ +#ifndef S_IFREG +#define S_IFREG _S_IFREG +#endif /* ifndef S_IFREG */ + +#ifndef S_ISDIR +#define S_ISDIR(m) (((m)&S_IFMT) == S_IFDIR) +#endif /* ifndef S_ISDIR */ +#ifndef S_ISREG +#define S_ISREG(m) (((m)&S_IFMT) == S_IFREG) +#endif /* ifndef S_ISREG */ + +#endif /* ISC_STAT_H */ diff --git a/lib/isc/win32/include/isc/stdatomic.h b/lib/isc/win32/include/isc/stdatomic.h new file mode 100644 index 0000000..dd5293f --- /dev/null +++ b/lib/isc/win32/include/isc/stdatomic.h @@ -0,0 +1,595 @@ +/* + * 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. + */ + +#pragma once + +#define WIN32_LEAN_AND_MEAN +#include <intrin.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <uchar.h> +#include <windows.h> + +#pragma warning(disable : 4133) +#pragma warning(disable : 4090) + +#define InterlockedExchangeAdd8 _InterlockedExchangeAdd8 +#define InterlockedCompareExchange8 _InterlockedCompareExchange8 + +#pragma intrinsic(_InterlockedCompareExchange8, _InterlockedExchangeAdd8) + +#include <isc/util.h> + +#ifndef __ATOMIC_RELAXED +#define __ATOMIC_RELAXED 0 +#endif /* ifndef __ATOMIC_RELAXED */ +#ifndef __ATOMIC_CONSUME +#define __ATOMIC_CONSUME 1 +#endif /* ifndef __ATOMIC_CONSUME */ +#ifndef __ATOMIC_ACQUIRE +#define __ATOMIC_ACQUIRE 2 +#endif /* ifndef __ATOMIC_ACQUIRE */ +#ifndef __ATOMIC_RELEASE +#define __ATOMIC_RELEASE 3 +#endif /* ifndef __ATOMIC_RELEASE */ +#ifndef __ATOMIC_ACQ_REL +#define __ATOMIC_ACQ_REL 4 +#endif /* ifndef __ATOMIC_ACQ_REL */ +#ifndef __ATOMIC_SEQ_CST +#define __ATOMIC_SEQ_CST 5 +#endif /* ifndef __ATOMIC_SEQ_CST */ + +enum memory_order { + memory_order_relaxed = __ATOMIC_RELAXED, + memory_order_consume = __ATOMIC_CONSUME, + memory_order_acquire = __ATOMIC_ACQUIRE, + memory_order_release = __ATOMIC_RELEASE, + memory_order_acq_rel = __ATOMIC_ACQ_REL, + memory_order_seq_cst = __ATOMIC_SEQ_CST +}; + +typedef enum memory_order memory_order; + +/* + * If you add a type with different sizeof() length, + * you need to implement atomic_<foo>_explicitNN macros. + */ + +typedef bool volatile atomic_bool; +typedef char volatile atomic_char; +typedef signed char volatile atomic_schar; +typedef unsigned char volatile atomic_uchar; +typedef short volatile atomic_short; +typedef unsigned short volatile atomic_ushort; +typedef int volatile atomic_int; +typedef unsigned int volatile atomic_uint; +typedef long volatile atomic_long; +typedef unsigned long volatile atomic_ulong; +typedef long long volatile atomic_llong; +typedef unsigned long long volatile atomic_ullong; +typedef char16_t volatile atomic_char16_t; +typedef char32_t volatile atomic_char32_t; +typedef wchar_t volatile atomic_wchar_t; +typedef int_least8_t volatile atomic_int_least8_t; +typedef uint_least8_t volatile atomic_uint_least8_t; +typedef int_least16_t volatile atomic_int_least16_t; +typedef uint_least16_t volatile atomic_uint_least16_t; +typedef int_least32_t volatile atomic_int_least32_t; +typedef uint_least32_t volatile atomic_uint_least32_t; +typedef int_least64_t volatile atomic_int_least64_t; +typedef uint_least64_t volatile atomic_uint_least64_t; +typedef int_fast8_t volatile atomic_int_fast8_t; +typedef uint_fast8_t volatile atomic_uint_fast8_t; +typedef int_fast16_t volatile atomic_int_fast16_t; +typedef uint_fast16_t volatile atomic_uint_fast16_t; +typedef int_fast32_t volatile atomic_int_fast32_t; +typedef uint_fast32_t volatile atomic_uint_fast32_t; +typedef int_fast64_t volatile atomic_int_fast64_t; +typedef uint_fast64_t volatile atomic_uint_fast64_t; +typedef intptr_t volatile atomic_intptr_t; +typedef uintptr_t volatile atomic_uintptr_t; +typedef size_t volatile atomic_size_t; +typedef ptrdiff_t volatile atomic_ptrdiff_t; +typedef intmax_t volatile atomic_intmax_t; +typedef uintmax_t volatile atomic_uintmax_t; + +#define atomic_init(obj, desired) (*(obj) = (desired)) + +#define atomic_store_explicit8(obj, desired, order) \ + (void)InterlockedExchange8((atomic_int_fast8_t *)obj, desired) + +#define atomic_store_explicit16(obj, desired, order) \ + (order == memory_order_relaxed \ + ? (void)InterlockedExchangeNoFence16((atomic_short *)obj, \ + desired) \ + : (order == memory_order_acquire \ + ? (void)InterlockedExchangeAcquire16( \ + (atomic_short *)obj, desired) \ + : (void)InterlockedExchange16((atomic_short *)obj, \ + desired))) + +#define atomic_store_explicit32(obj, desired, order) \ + (order == memory_order_relaxed \ + ? (void)InterlockedExchangeNoFence( \ + (atomic_int_fast32_t *)obj, desired) \ + : (order == memory_order_acquire \ + ? (void)InterlockedExchangeAcquire( \ + (atomic_int_fast32_t *)obj, desired) \ + : (void)InterlockedExchange( \ + (atomic_int_fast32_t *)obj, desired))) + +#ifdef _WIN64 +#define atomic_store_explicit64(obj, desired, order) \ + (order == memory_order_relaxed \ + ? (void)InterlockedExchangeNoFence64( \ + (atomic_int_fast64_t *)obj, desired) \ + : (order == memory_order_acquire \ + ? (void)InterlockedExchangeAcquire64( \ + (atomic_int_fast64_t *)obj, desired) \ + : (void)InterlockedExchange64( \ + (atomic_int_fast64_t *)obj, desired))) +#else /* ifdef _WIN64 */ +#define atomic_store_explicit64(obj, desired, order) \ + (void)InterlockedExchange64((atomic_int_fast64_t *)obj, desired) +#endif /* ifdef _WIN64 */ + +static inline void +atomic_store_abort() { + UNREACHABLE(); +} + +#define atomic_store_explicit(obj, desired, order) \ + (sizeof(*(obj)) == 8 \ + ? atomic_store_explicit64(obj, desired, order) \ + : (sizeof(*(obj)) == 4 \ + ? atomic_store_explicit32(obj, desired, order) \ + : (sizeof(*(obj)) == 2 \ + ? atomic_store_explicit16(obj, desired, \ + order) \ + : (sizeof(*(obj)) == 1 \ + ? atomic_store_explicit8( \ + obj, desired, \ + order) \ + : atomic_store_abort())))) + +#define atomic_store(obj, desired) \ + atomic_store_explicit(obj, desired, memory_order_seq_cst) + +#define atomic_load_explicit8(obj, order) \ + (int8_t) InterlockedOr8((atomic_int_fast8_t *)obj, 0) + +#define atomic_load_explicit16(obj, order) \ + (short)InterlockedOr16((atomic_short *)obj, 0) + +#define atomic_load_explicit32(obj, order) \ + (order == memory_order_relaxed \ + ? (int32_t)InterlockedOrNoFence((atomic_int_fast32_t *)obj, \ + 0) \ + : (order == memory_order_acquire \ + ? (int32_t)InterlockedOrAcquire( \ + (atomic_int_fast32_t *)obj, 0) \ + : (order == memory_order_release \ + ? (int32_t)InterlockedOrRelease( \ + (atomic_int_fast32_t *)obj, 0) \ + : (int32_t)InterlockedOr( \ + (atomic_int_fast32_t *)obj, \ + 0)))) + +#ifdef _WIN64 +#define atomic_load_explicit64(obj, order) \ + (order == memory_order_relaxed \ + ? InterlockedOr64NoFence((atomic_int_fast64_t *)obj, 0) \ + : (order == memory_order_acquire \ + ? InterlockedOr64Acquire( \ + (atomic_int_fast64_t *)obj, 0) \ + : (order == memory_order_release \ + ? InterlockedOr64Release( \ + (atomic_int_fast64_t *)obj, 0) \ + : InterlockedOr64( \ + (atomic_int_fast64_t *)obj, \ + 0)))) +#else /* ifdef _WIN64 */ +#define atomic_load_explicit64(obj, order) \ + InterlockedOr64((atomic_int_fast64_t *)obj, 0) +#endif /* ifdef _WIN64 */ + +static inline int8_t +atomic_load_abort() { + UNREACHABLE(); +} + +#define atomic_load_explicit(obj, order) \ + (((sizeof(*(obj)) == 8) \ + ? atomic_load_explicit64(obj, order) \ + : ((sizeof(*(obj)) == 4) \ + ? atomic_load_explicit32(obj, order) \ + : ((sizeof(*(obj)) == 2) \ + ? atomic_load_explicit16(obj, order) \ + : ((sizeof(*(obj)) == 1) \ + ? atomic_load_explicit8( \ + obj, order) \ + : atomic_load_abort())))) & \ + ((sizeof(*(obj)) == 8) \ + ? 0xffffffffffffffffULL \ + : ((sizeof(*(obj)) == 4) \ + ? 0xffffffffULL \ + : ((sizeof(*(obj)) == 2) \ + ? 0xffffULL \ + : ((sizeof(*(obj)) == 1) \ + ? 0xffULL \ + : atomic_load_abort()))))) + +#define atomic_load(obj) atomic_load_explicit(obj, memory_order_seq_cst) + +#define atomic_fetch_add_explicit8(obj, arg, order) \ + InterlockedExchangeAdd8((atomic_int_fast8_t *)obj, arg) + +#define atomic_fetch_add_explicit16(obj, arg, order) \ + InterlockedExchangeAdd16((atomic_short *)obj, arg) + +#define atomic_fetch_add_explicit32(obj, arg, order) \ + (order == memory_order_relaxed \ + ? InterlockedExchangeAddNoFence((atomic_int_fast32_t *)obj, \ + arg) \ + : (order == memory_order_acquire \ + ? InterlockedExchangeAddAcquire( \ + (atomic_int_fast32_t *)obj, arg) \ + : (order == memory_order_release \ + ? InterlockedExchangeAddRelease( \ + (atomic_int_fast32_t *)obj, \ + arg) \ + : InterlockedExchangeAdd( \ + (atomic_int_fast32_t *)obj, \ + arg)))) + +#ifdef _WIN64 +#define atomic_fetch_add_explicit64(obj, arg, order) \ + (order == memory_order_relaxed \ + ? InterlockedExchangeAddNoFence64((atomic_int_fast64_t *)obj, \ + arg) \ + : (order == memory_order_acquire \ + ? InterlockedExchangeAddAcquire64( \ + (atomic_int_fast64_t *)obj, arg) \ + : (order == memory_order_release \ + ? InterlockedExchangeAddRelease64( \ + (atomic_int_fast64_t *)obj, \ + arg) \ + : InterlockedExchangeAdd64( \ + (atomic_int_fast64_t *)obj, \ + arg)))) +#else /* ifdef _WIN64 */ +#define atomic_fetch_add_explicit64(obj, arg, order) \ + InterlockedExchangeAdd64((atomic_int_fast64_t *)obj, arg) +#endif /* ifdef _WIN64 */ + +static inline int8_t +atomic_add_abort() { + UNREACHABLE(); +} + +#define atomic_fetch_add_explicit(obj, arg, order) \ + (sizeof(*(obj)) == 8 \ + ? atomic_fetch_add_explicit64(obj, arg, order) \ + : (sizeof(*(obj)) == 4 \ + ? atomic_fetch_add_explicit32(obj, arg, order) \ + : (sizeof(*(obj)) == 2 \ + ? atomic_fetch_add_explicit16(obj, arg, \ + order) \ + : (sizeof(*(obj)) == 1 \ + ? atomic_fetch_add_explicit8( \ + obj, arg, order) \ + : atomic_add_abort())))) + +#define atomic_fetch_add(obj, arg) \ + atomic_fetch_add_explicit(obj, arg, memory_order_seq_cst) + +#define atomic_fetch_sub_explicit(obj, arg, order) \ + atomic_fetch_add_explicit(obj, -arg, order) + +#define atomic_fetch_sub(obj, arg) \ + atomic_fetch_sub_explicit(obj, arg, memory_order_seq_cst) + +#define atomic_fetch_and_explicit8(obj, arg, order) \ + InterlockedAnd8((atomic_int_fast8_t *)obj, arg) + +#define atomic_fetch_and_explicit16(obj, arg, order) \ + InterlockedAnd16((atomic_short *)obj, arg) + +#define atomic_fetch_and_explicit32(obj, arg, order) \ + (order == memory_order_relaxed \ + ? InterlockedAndNoFence((atomic_int_fast32_t *)obj, arg) \ + : (order == memory_order_acquire \ + ? InterlockedAndAcquire( \ + (atomic_int_fast32_t *)obj, arg) \ + : (order == memory_order_release \ + ? InterlockedAndRelease( \ + (atomic_int_fast32_t *)obj, \ + arg) \ + : InterlockedAnd( \ + (atomic_int_fast32_t *)obj, \ + arg)))) + +#ifdef _WIN64 +#define atomic_fetch_and_explicit64(obj, arg, order) \ + (order == memory_order_relaxed \ + ? InterlockedAnd64NoFence((atomic_int_fast64_t *)obj, arg) \ + : (order == memory_order_acquire \ + ? InterlockedAnd64Acquire( \ + (atomic_int_fast64_t *)obj, arg) \ + : (order == memory_order_release \ + ? InterlockedAnd64Release( \ + (atomic_int_fast64_t *)obj, \ + arg) \ + : InterlockedAnd64( \ + (atomic_int_fast64_t *)obj, \ + arg)))) +#else /* ifdef _WIN64 */ +#define atomic_fetch_and_explicit64(obj, arg, order) \ + InterlockedAnd64((atomic_int_fast64_t *)obj, arg) +#endif /* ifdef _WIN64 */ + +static inline int8_t +atomic_and_abort() { + UNREACHABLE(); +} + +#define atomic_fetch_and_explicit(obj, arg, order) \ + (sizeof(*(obj)) == 8 \ + ? atomic_fetch_and_explicit64(obj, arg, order) \ + : (sizeof(*(obj)) == 4 \ + ? atomic_fetch_and_explicit32(obj, arg, order) \ + : (sizeof(*(obj)) == 2 \ + ? atomic_fetch_and_explicit16(obj, arg, \ + order) \ + : (sizeof(*(obj)) == 1 \ + ? atomic_fetch_and_explicit8( \ + obj, arg, order) \ + : atomic_and_abort())))) + +#define atomic_fetch_and(obj, arg) \ + atomic_fetch_and_explicit(obj, arg, memory_order_seq_cst) + +#define atomic_fetch_or_explicit8(obj, arg, order) \ + InterlockedOr8((atomic_int_fast8_t *)obj, arg) + +#define atomic_fetch_or_explicit16(obj, arg, order) \ + InterlockedOr16((atomic_short *)obj, arg) + +#define atomic_fetch_or_explicit32(obj, arg, order) \ + (order == memory_order_relaxed \ + ? InterlockedOrNoFence((atomic_int_fast32_t *)obj, arg) \ + : (order == memory_order_acquire \ + ? InterlockedOrAcquire((atomic_int_fast32_t *)obj, \ + arg) \ + : (order == memory_order_release \ + ? InterlockedOrRelease( \ + (atomic_int_fast32_t *)obj, \ + arg) \ + : InterlockedOr( \ + (atomic_int_fast32_t *)obj, \ + arg)))) + +#ifdef _WIN64 +#define atomic_fetch_or_explicit64(obj, arg, order) \ + (order == memory_order_relaxed \ + ? InterlockedOr64NoFence((atomic_int_fast64_t *)obj, arg) \ + : (order == memory_order_acquire \ + ? InterlockedOr64Acquire( \ + (atomic_int_fast64_t *)obj, arg) \ + : (order == memory_order_release \ + ? InterlockedOr64Release( \ + (atomic_int_fast64_t *)obj, \ + arg) \ + : InterlockedOr64( \ + (atomic_int_fast64_t *)obj, \ + arg)))) +#else /* ifdef _WIN64 */ +#define atomic_fetch_or_explicit64(obj, arg, order) \ + InterlockedOr64((atomic_int_fast64_t *)obj, arg) +#endif /* ifdef _WIN64 */ + +static inline int8_t +atomic_or_abort() { + UNREACHABLE(); +} + +#define atomic_fetch_or_explicit(obj, arg, order) \ + (sizeof(*(obj)) == 8 \ + ? atomic_fetch_or_explicit64(obj, arg, order) \ + : (sizeof(*(obj)) == 4 \ + ? atomic_fetch_or_explicit32(obj, arg, order) \ + : (sizeof(*(obj)) == 2 \ + ? atomic_fetch_or_explicit16(obj, arg, \ + order) \ + : (sizeof(*(obj)) == 1 \ + ? atomic_fetch_or_explicit8( \ + obj, arg, order) \ + : atomic_or_abort())))) + +#define atomic_fetch_or(obj, arg) \ + atomic_fetch_or_explicit(obj, arg, memory_order_seq_cst) + +static inline bool +atomic_compare_exchange_strong_explicit8(atomic_int_fast8_t *obj, + int8_t *expected, int8_t desired, + memory_order succ, memory_order fail) { + bool __r; + int8_t __v; + + UNUSED(succ); + UNUSED(fail); + + __v = InterlockedCompareExchange8((atomic_int_fast8_t *)obj, desired, + *expected); + __r = (*(expected) == __v); + if (!__r) { + *(expected) = __v; + } + return (__r); +} + +static inline bool +atomic_compare_exchange_strong_explicit16(atomic_short *obj, short *expected, + short desired, memory_order succ, + memory_order fail) { + bool __r; + short __v; + + UNUSED(succ); + UNUSED(fail); + + __v = InterlockedCompareExchange16((atomic_short *)obj, desired, + *expected); + __r = (*(expected) == __v); + if (!__r) { + *(expected) = __v; + } + return (__r); +} + +static inline bool +atomic_compare_exchange_strong_explicit32(atomic_int_fast32_t *obj, + int32_t *expected, int32_t desired, + memory_order succ, + memory_order fail) { + bool __r; + int32_t __v; + + UNUSED(succ); + UNUSED(fail); + + switch (succ) { + case memory_order_relaxed: + __v = InterlockedCompareExchangeNoFence( + (atomic_int_fast32_t *)obj, desired, *expected); + break; + case memory_order_acquire: + __v = InterlockedCompareExchangeAcquire( + (atomic_int_fast32_t *)obj, desired, *expected); + break; + case memory_order_release: + __v = InterlockedCompareExchangeRelease( + (atomic_int_fast32_t *)obj, desired, *expected); + break; + default: + __v = InterlockedCompareExchange((atomic_int_fast32_t *)obj, + desired, *expected); + break; + } + __r = (*(expected) == __v); + if (!__r) { + *(expected) = __v; + } + return (__r); +} + +static inline bool +atomic_compare_exchange_strong_explicit64(atomic_int_fast64_t *obj, + int64_t *expected, int64_t desired, + memory_order succ, + memory_order fail) { + bool __r; + int64_t __v; + + UNUSED(succ); + UNUSED(fail); + +#ifdef _WIN64 + switch (succ) { + case memory_order_relaxed: + __v = InterlockedCompareExchangeNoFence64( + (atomic_int_fast64_t *)obj, desired, *expected); + break; + case memory_order_acquire: + __v = InterlockedCompareExchangeAcquire64( + (atomic_int_fast64_t *)obj, desired, *expected); + break; + case memory_order_release: + __v = InterlockedCompareExchangeRelease64( + (atomic_int_fast64_t *)obj, desired, *expected); + break; + default: + __v = InterlockedCompareExchange64((atomic_int_fast64_t *)obj, + desired, *expected); + break; + } +#else /* ifdef _WIN64 */ + __v = InterlockedCompareExchange64((atomic_int_fast64_t *)obj, desired, + *expected); +#endif /* ifdef _WIN64 */ + __r = (*(expected) == __v); + if (!__r) { + *(expected) = __v; + } + return (__r); +} + +static inline bool +atomic_compare_exchange_abort() { + UNREACHABLE(); +} + +#define atomic_compare_exchange_strong_explicit(obj, expected, desired, succ, \ + fail) \ + (sizeof(*(obj)) == 8 \ + ? atomic_compare_exchange_strong_explicit64( \ + obj, expected, desired, succ, fail) \ + : (sizeof(*(obj)) == 4 \ + ? atomic_compare_exchange_strong_explicit32( \ + obj, expected, desired, succ, fail) \ + : (sizeof(*(obj)) == 2 \ + ? atomic_compare_exchange_strong_explicit16( \ + obj, expected, desired, succ, \ + fail) \ + : (sizeof(*(obj)) == 1 \ + ? atomic_compare_exchange_strong_explicit8( \ + obj, expected, \ + desired, succ, \ + fail) \ + : atomic_compare_exchange_abort())))) + +#define atomic_compare_exchange_strong(obj, expected, desired) \ + atomic_compare_exchange_strong_explicit(obj, expected, desired, \ + memory_order_seq_cst, \ + memory_order_seq_cst) + +#define atomic_compare_exchange_weak_explicit(obj, expected, desired, succ, \ + fail) \ + atomic_compare_exchange_strong_explicit(obj, expected, desired, succ, \ + fail) + +#define atomic_compare_exchange_weak(obj, expected, desired) \ + atomic_compare_exchange_weak_explicit(obj, expected, desired, \ + memory_order_seq_cst, \ + memory_order_seq_cst) + +static inline bool +atomic_exchange_abort() { + UNREACHABLE(); +} + +#define atomic_exchange_explicit(obj, desired, order) \ + (sizeof(*(obj)) == 8 \ + ? InterlockedExchange64(obj, desired) \ + : (sizeof(*(obj)) == 4 \ + ? InterlockedExchange(obj, desired) \ + : (sizeof(*(obj)) == 2 \ + ? InterlockedExchange16(obj, desired) \ + : (sizeof(*(obj)) == 1 \ + ? InterlockedExchange8( \ + obj, desired) \ + : atomic_exchange_abort())))) + +#define atomic_exchange(obj, desired) \ + atomic_exchange_explicit(obj, desired, memory_order_seq_cst) diff --git a/lib/isc/win32/include/isc/stdtime.h b/lib/isc/win32/include/isc/stdtime.h new file mode 100644 index 0000000..12e990c --- /dev/null +++ b/lib/isc/win32/include/isc/stdtime.h @@ -0,0 +1,62 @@ +/* + * 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. + */ + +#ifndef ISC_STDTIME_H +#define ISC_STDTIME_H 1 + +#include <inttypes.h> +#include <stdlib.h> + +#include <isc/lang.h> + +/* + * It's public information that 'isc_stdtime_t' is an unsigned integral type. + * Applications that want maximum portability should not assume anything + * about its size. + */ +typedef uint32_t isc_stdtime_t; + +ISC_LANG_BEGINDECLS + +void +isc_stdtime_get(isc_stdtime_t *t); +/* + * Set 't' to the number of seconds since 00:00:00 UTC, January 1, 1970. + * + * Requires: + * + * 't' is a valid pointer. + */ + +void +isc_stdtime_tostring(isc_stdtime_t t, char *out, size_t outlen); +/* + * Convert 't' into a null-terminated string of the form + * "Wed Jun 30 21:49:08 1993". Store the string in the 'out' + * buffer. + * + * Requires: + * + * 't' is a valid time. + * 'out' is a valid pointer. + * 'outlen' is at least 26. + */ + +#define isc_stdtime_convert32(t, t32p) (*(t32p) = t) +/* + * Convert the standard time to its 32-bit version. + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_STDTIME_H */ diff --git a/lib/isc/win32/include/isc/syslog.h b/lib/isc/win32/include/isc/syslog.h new file mode 100644 index 0000000..2638ad2 --- /dev/null +++ b/lib/isc/win32/include/isc/syslog.h @@ -0,0 +1,39 @@ +/* + * 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. + */ + +#ifndef ISC_SYSLOG_H +#define ISC_SYSLOG_H 1 + +#include <isc/lang.h> +#include <isc/types.h> + +ISC_LANG_BEGINDECLS + +isc_result_t +isc_syslog_facilityfromstring(const char *str, int *facilityp); +/* + * Convert 'str' to the appropriate syslog facility constant. + * + * Requires: + * + * 'str' is not NULL + * 'facilityp' is not NULL + * + * Returns: + * ISC_R_SUCCESS + * ISC_R_NOTFOUND + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_SYSLOG_H */ diff --git a/lib/isc/win32/include/isc/thread.h b/lib/isc/win32/include/isc/thread.h new file mode 100644 index 0000000..5e30c63 --- /dev/null +++ b/lib/isc/win32/include/isc/thread.h @@ -0,0 +1,103 @@ +/* + * 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. + */ + +#ifndef ISC_THREAD_H +#define ISC_THREAD_H 1 + +#include <inttypes.h> +#include <windows.h> + +#include <isc/lang.h> +#include <isc/result.h> + +extern __declspec(thread) size_t isc_tid_v; + +/* + * Inlines to help with wait return checking + */ + +/* check handle for NULL and INVALID_HANDLE */ +inline BOOL +IsValidHandle(HANDLE hHandle) { + return ((hHandle != NULL) && (hHandle != INVALID_HANDLE_VALUE)); +} + +/* validate wait return codes... */ +inline BOOL +WaitSucceeded(DWORD dwWaitResult, DWORD dwHandleCount) { + return ((dwWaitResult >= WAIT_OBJECT_0) && + (dwWaitResult < WAIT_OBJECT_0 + dwHandleCount)); +} + +inline BOOL +WaitAbandoned(DWORD dwWaitResult, DWORD dwHandleCount) { + return ((dwWaitResult >= WAIT_ABANDONED_0) && + (dwWaitResult < WAIT_ABANDONED_0 + dwHandleCount)); +} + +inline BOOL +WaitTimeout(DWORD dwWaitResult) { + return (dwWaitResult == WAIT_TIMEOUT); +} + +inline BOOL +WaitFailed(DWORD dwWaitResult) { + return (dwWaitResult == WAIT_FAILED); +} + +/* compute object indices for waits... */ +inline DWORD +WaitSucceededIndex(DWORD dwWaitResult) { + return (dwWaitResult - WAIT_OBJECT_0); +} + +inline DWORD +WaitAbandonedIndex(DWORD dwWaitResult) { + return (dwWaitResult - WAIT_ABANDONED_0); +} + +typedef HANDLE isc_thread_t; +typedef DWORD isc_threadresult_t; +typedef void *isc_threadarg_t; +typedef isc_threadresult_t(WINAPI *isc_threadfunc_t)(isc_threadarg_t); + +#define isc_thread_self (uintptr_t) GetCurrentThreadId + +ISC_LANG_BEGINDECLS + +void +isc_thread_create(isc_threadfunc_t, isc_threadarg_t, isc_thread_t *); + +void +isc_thread_join(isc_thread_t, isc_threadresult_t *); + +void +isc_thread_setconcurrency(unsigned int level); + +void +isc_thread_setname(isc_thread_t, const char *); + +isc_result_t +isc_thread_setaffinity(int cpu); + +#define isc_thread_yield() Sleep(0) + +#if HAVE___DECLSPEC_THREAD +#define ISC_THREAD_LOCAL static __declspec(thread) +#else /* if HAVE___DECLSPEC_THREAD */ +#error "Thread-local storage support is required!" +#endif /* if HAVE___DECLSPEC_THREAD */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_THREAD_H */ diff --git a/lib/isc/win32/include/isc/time.h b/lib/isc/win32/include/isc/time.h new file mode 100644 index 0000000..baed464 --- /dev/null +++ b/lib/isc/win32/include/isc/time.h @@ -0,0 +1,468 @@ +/* + * 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. + */ + +#ifndef ISC_TIME_H +#define ISC_TIME_H 1 + +#include <errno.h> +#include <inttypes.h> +#include <stdbool.h> +#include <time.h> +#include <windows.h> + +#include <isc/lang.h> +#include <isc/types.h> + +/*** + *** POSIX Shims + ***/ + +struct tm * +gmtime_r(const time_t *clock, struct tm *result); + +struct tm * +localtime_r(const time_t *clock, struct tm *result); + +int +nanosleep(const struct timespec *req, struct timespec *rem); + +typedef uint32_t useconds_t; + +int +usleep(useconds_t usec); + +/*** + *** Intervals + ***/ + +/* + * The contents of this structure are private, and MUST NOT be accessed + * directly by callers. + * + * The contents are exposed only to allow callers to avoid dynamic allocation. + */ +struct isc_interval { + int64_t interval; +}; + +LIBISC_EXTERNAL_DATA extern const isc_interval_t *const isc_interval_zero; + +/* + * ISC_FORMATHTTPTIMESTAMP_SIZE needs to be 30 in C locale and potentially + * more for other locales to handle longer national abbreviations when + * expanding strftime's %a and %b. + */ +#define ISC_FORMATHTTPTIMESTAMP_SIZE 50 + +ISC_LANG_BEGINDECLS + +void +isc_interval_set(isc_interval_t *i, unsigned int seconds, + unsigned int nanoseconds); +/* + * Set 'i' to a value representing an interval of 'seconds' seconds and + * 'nanoseconds' nanoseconds, suitable for use in isc_time_add() and + * isc_time_subtract(). + * + * Requires: + * + * 't' is a valid pointer. + * nanoseconds < 1000000000. + */ + +bool +isc_interval_iszero(const isc_interval_t *i); +/* + * Returns true iff. 'i' is the zero interval. + * + * Requires: + * + * 'i' is a valid pointer. + */ + +/*** + *** Absolute Times + ***/ + +/* + * The contents of this structure are private, and MUST NOT be accessed + * directly by callers. + * + * The contents are exposed only to allow callers to avoid dynamic allocation. + */ + +struct isc_time { + FILETIME absolute; +}; + +LIBISC_EXTERNAL_DATA extern const isc_time_t *const isc_time_epoch; + +void +isc_time_set(isc_time_t *t, unsigned int seconds, unsigned int nanoseconds); +/*%< + * Set 't' to a value which represents the given number of seconds and + * nanoseconds since 00:00:00 January 1, 1970, UTC. + * + * Requires: + *\li 't' is a valid pointer. + *\li nanoseconds < 1000000000. + */ + +void +isc_time_settoepoch(isc_time_t *t); +/* + * Set 't' to the time of the epoch. + * + * Notes: + * The date of the epoch is platform-dependent. + * + * Requires: + * + * 't' is a valid pointer. + */ + +bool +isc_time_isepoch(const isc_time_t *t); +/* + * Returns true iff. 't' is the epoch ("time zero"). + * + * Requires: + * + * 't' is a valid pointer. + */ + +isc_result_t +isc_time_now(isc_time_t *t); +/* + * Set 't' to the current absolute time. + * + * Requires: + * + * 't' is a valid pointer. + * + * Returns: + * + * Success + * Unexpected error + * Getting the time from the system failed. + * Out of range + * The time from the system is too large to be represented + * in the current definition of isc_time_t. + */ + +isc_result_t +isc_time_now_hires(isc_time_t *t); +/*%< + * Set 't' to the current absolute time. Uses higher resolution clocks + * recommended when microsecond accuracy is required. + * + * Requires: + * + *\li 't' is a valid pointer. + * + * Returns: + * + *\li Success + *\li Unexpected error + * Getting the time from the system failed. + *\li Out of range + * The time from the system is too large to be represented + * in the current definition of isc_time_t. + */ + +isc_result_t +isc_time_nowplusinterval(isc_time_t *t, const isc_interval_t *i); +/* + * Set *t to the current absolute time + i. + * + * Note: + * This call is equivalent to: + * + * isc_time_now(t); + * isc_time_add(t, i, t); + * + * Requires: + * + * 't' and 'i' are valid pointers. + * + * Returns: + * + * Success + * Unexpected error + * Getting the time from the system failed. + * Out of range + * The interval added to the time from the system is too large to + * be represented in the current definition of isc_time_t. + */ + +int +isc_time_compare(const isc_time_t *t1, const isc_time_t *t2); +/* + * Compare the times referenced by 't1' and 't2' + * + * Requires: + * + * 't1' and 't2' are valid pointers. + * + * Returns: + * + * -1 t1 < t2 (comparing times, not pointers) + * 0 t1 = t2 + * 1 t1 > t2 + */ + +isc_result_t +isc_time_add(const isc_time_t *t, const isc_interval_t *i, isc_time_t *result); +/* + * Add 'i' to 't', storing the result in 'result'. + * + * Requires: + * + * 't', 'i', and 'result' are valid pointers. + * + * Returns: + * Success + * Out of range + * The interval added to the time is too large to + * be represented in the current definition of isc_time_t. + */ + +isc_result_t +isc_time_subtract(const isc_time_t *t, const isc_interval_t *i, + isc_time_t *result); +/* + * Subtract 'i' from 't', storing the result in 'result'. + * + * Requires: + * + * 't', 'i', and 'result' are valid pointers. + * + * Returns: + * Success + * Out of range + * The interval is larger than the time since the epoch. + */ + +uint64_t +isc_time_microdiff(const isc_time_t *t1, const isc_time_t *t2); +/* + * Find the difference in milliseconds between time t1 and time t2. + * t2 is the subtrahend of t1; ie, difference = t1 - t2. + * + * Requires: + * + * 't1' and 't2' are valid pointers. + * + * Returns: + * The difference of t1 - t2, or 0 if t1 <= t2. + */ + +isc_result_t +isc_time_parsehttptimestamp(char *input, isc_time_t *t); +/*%< + * Parse the time in 'input' into the isc_time_t pointed to by 't', + * expecting a format like "Mon, 30 Aug 2000 04:06:47 GMT" + * + * Requires: + *\li 'buf' and 't' are not NULL. + */ + +uint32_t +isc_time_nanoseconds(const isc_time_t *t); +/* + * Return the number of nanoseconds stored in a time structure. + * + * Notes: + * This is the number of nanoseconds in excess of the number + * of seconds since the epoch; it will always be less than one + * full second. + * + * Requires: + * 't' is a valid pointer. + * + * Ensures: + * The returned value is less than 1*10^9. + */ + +void +isc_time_formattimestamp(const isc_time_t *t, char *buf, unsigned int len); +/* + * Format the time 't' into the buffer 'buf' of length 'len', + * using a format like "30-Aug-2000 04:06:47.997" and the local time zone. + * If the text does not fit in the buffer, the result is indeterminate, + * but is always guaranteed to be null terminated. + * + * Requires: + * 'len' > 0 + * 'buf' points to an array of at least len chars + * + */ + +void +isc_time_formathttptimestamp(const isc_time_t *t, char *buf, unsigned int len); +/* + * Format the time 't' into the buffer 'buf' of length 'len', + * using a format like "Mon, 30 Aug 2000 04:06:47 GMT" + * If the text does not fit in the buffer, the result is indeterminate, + * but is always guaranteed to be null terminated. + * + * Requires: + * 'len' > 0 + * 'buf' points to an array of at least len chars + * + */ + +isc_result_t +isc_time_parsehttptimestamp(char *input, isc_time_t *t); +/*%< + * Parse the time in 'input' into the isc_time_t pointed to by 't', + * expecting a format like "Mon, 30 Aug 2000 04:06:47 GMT" + * + * Requires: + *\li 'buf' and 't' are not NULL. + */ + +void +isc_time_formatISO8601L(const isc_time_t *t, char *buf, unsigned int len); +/*%< + * Format the time 't' into the buffer 'buf' of length 'len', + * using the ISO8601 format: "yyyy-mm-ddThh:mm:ss" + * If the text does not fit in the buffer, the result is indeterminate, + * but is always guaranteed to be null terminated. + * + * Requires: + *\li 'len' > 0 + *\li 'buf' points to an array of at least len chars + * + */ + +void +isc_time_formatISO8601Lms(const isc_time_t *t, char *buf, unsigned int len); +/*%< + * Format the time 't' into the buffer 'buf' of length 'len', + * using the ISO8601 format: "yyyy-mm-ddThh:mm:ss.sss" + * If the text does not fit in the buffer, the result is indeterminate, + * but is always guaranteed to be null terminated. + * + * Requires: + *\li 'len' > 0 + *\li 'buf' points to an array of at least len chars + * + */ + +void +isc_time_formatISO8601Lus(const isc_time_t *t, char *buf, unsigned int len); +/*%< + * Format the time 't' into the buffer 'buf' of length 'len', + * using the ISO8601 format: "yyyy-mm-ddThh:mm:ss.ssssss" + * If the text does not fit in the buffer, the result is indeterminate, + * but is always guaranteed to be null terminated. + * + * Requires: + *\li 'len' > 0 + *\li 'buf' points to an array of at least len chars + * + */ + +void +isc_time_formatISO8601(const isc_time_t *t, char *buf, unsigned int len); +/*%< + * Format the time 't' into the buffer 'buf' of length 'len', + * using the ISO8601 format: "yyyy-mm-ddThh:mm:ssZ" + * If the text does not fit in the buffer, the result is indeterminate, + * but is always guaranteed to be null terminated. + * + * Requires: + *\li 'len' > 0 + *\li 'buf' points to an array of at least len chars + * + */ + +void +isc_time_formatISO8601ms(const isc_time_t *t, char *buf, unsigned int len); +/*%< + * Format the time 't' into the buffer 'buf' of length 'len', + * using the ISO8601 format: "yyyy-mm-ddThh:mm:ss.sssZ" + * If the text does not fit in the buffer, the result is indeterminate, + * but is always guaranteed to be null terminated. + * + * Requires: + *\li 'len' > 0 + *\li 'buf' points to an array of at least len chars + * + */ + +void +isc_time_formatISO8601us(const isc_time_t *t, char *buf, unsigned int len); +/*%< + * Format the time 't' into the buffer 'buf' of length 'len', + * using the ISO8601 format: "yyyy-mm-ddThh:mm:ss.ssssssZ" + * If the text does not fit in the buffer, the result is indeterminate, + * but is always guaranteed to be null terminated. + * + * Requires: + *\li 'len' > 0 + *\li 'buf' points to an array of at least len chars + * + */ + +void +isc_time_formatshorttimestamp(const isc_time_t *t, char *buf, unsigned int len); +/*%< + * Format the time 't' into the buffer 'buf' of length 'len', + * using the format "yyyymmddhhmmsssss" useful for file timestamping. + * If the text does not fit in the buffer, the result is indeterminate, + * but is always guaranteed to be null terminated. + * + * Requires: + *\li 'len' > 0 + *\li 'buf' points to an array of at least len chars + * + */ + +uint32_t +isc_time_seconds(const isc_time_t *t); +/*%< + * Return the number of seconds since the epoch stored in a time structure. + * + * Requires: + * + *\li 't' is a valid pointer. + */ + +isc_result_t +isc_time_secondsastimet(const isc_time_t *t, time_t *secondsp); +/*%< + * Ensure the number of seconds in an isc_time_t is representable by a time_t. + * + * Notes: + *\li The number of seconds stored in an isc_time_t might be larger + * than the number of seconds a time_t is able to handle. Since + * time_t is mostly opaque according to the ANSI/ISO standard + * (essentially, all you can be sure of is that it is an arithmetic type, + * not even necessarily integral), it can be tricky to ensure that + * the isc_time_t is in the range a time_t can handle. Use this + * function in place of isc_time_seconds() any time you need to set a + * time_t from an isc_time_t. + * + * Requires: + *\li 't' is a valid pointer. + * + * Returns: + *\li Success + *\li Out of range + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_TIME_H */ diff --git a/lib/isc/win32/include/isc/win32os.h b/lib/isc/win32/include/isc/win32os.h new file mode 100644 index 0000000..22def42 --- /dev/null +++ b/lib/isc/win32/include/isc/win32os.h @@ -0,0 +1,41 @@ +/* + * 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. + */ + +#ifndef ISC_WIN32OS_H +#define ISC_WIN32OS_H 1 + +#include <isc/lang.h> + +ISC_LANG_BEGINDECLS + +/* + * Return the number of CPUs available on the system, or 1 if this cannot + * be determined. + */ + +int +isc_win32os_versioncheck(unsigned int major, unsigned int minor, + unsigned int updatemajor, unsigned int updateminor); + +/* + * Checks the current version of the operating system with the + * supplied version information. + * Returns: + * -1 if less than the version information supplied + * 0 if equal to all of the version information supplied + * +1 if greater than the version information supplied + */ + +ISC_LANG_ENDDECLS + +#endif /* ISC_WIN32OS_H */ diff --git a/lib/isc/win32/interfaceiter.c b/lib/isc/win32/interfaceiter.c new file mode 100644 index 0000000..db82e20 --- /dev/null +++ b/lib/isc/win32/interfaceiter.c @@ -0,0 +1,550 @@ +/* + * 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. + */ + +/* + * Note that this code will need to be revisited to support IPv6 Interfaces. + * For now we just iterate through IPv4 interfaces. + */ +#include <errno.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <winsock2.h> +#include <ws2tcpip.h> + +#include <isc/interfaceiter.h> +#include <isc/mem.h> +#include <isc/print.h> +#include <isc/result.h> +#include <isc/strerr.h> +#include <isc/string.h> +#include <isc/types.h> +#include <isc/util.h> + +void +InitSockets(void); + +/* Common utility functions */ + +/* + * Extract the network address part from a "struct sockaddr". + * + * The address family is given explicitly + * instead of using src->sa_family, because the latter does not work + * for copying a network mask obtained by SIOCGIFNETMASK (it does + * not have a valid address family). + */ + +#define IFITER_MAGIC 0x49464954U /* IFIT. */ +#define VALID_IFITER(t) ((t) != NULL && (t)->magic == IFITER_MAGIC) + +struct isc_interfaceiter { + unsigned int magic; /* Magic number. */ + isc_mem_t *mctx; + SOCKET socket; + INTERFACE_INFO IFData; /* Current Interface Info. */ + int numIF; /* Current Interface count. */ + int v4IF; /* Number of IPv4 Interfaces */ + INTERFACE_INFO *buf4; /* Buffer for WSAIoctl data. */ + unsigned int buf4size; /* Bytes allocated. */ + INTERFACE_INFO *pos4; /* Current offset in IF List */ + SOCKET_ADDRESS_LIST *buf6; /* Buffer for WSAIoctl data. */ + unsigned int buf6size; /* Bytes allocated. */ + unsigned int pos6; /* Which entry to process. */ + bool v6loop; /* See IPv6 loop address. */ + bool pos6zero; /* Done pos6 == 0. */ + isc_interface_t current; /* Current interface data. */ + isc_result_t result; /* Last result code. */ +}; + +/* + * Size of buffer for SIO_GET_INTERFACE_LIST, in number of interfaces. + * We assume no sane system will have more than than 1K of IP addresses on + * all of its adapters. + */ +#define IFCONF_SIZE_INITIAL 16 +#define IFCONF_SIZE_INCREMENT 64 +#define IFCONF_SIZE_MAX 1040 + +static void +get_addr(unsigned int family, isc_netaddr_t *dst, struct sockaddr *src) { + dst->family = family; + switch (family) { + case AF_INET: + memmove(&dst->type.in, &((struct sockaddr_in *)src)->sin_addr, + sizeof(struct in_addr)); + break; + case AF_INET6: + memmove(&dst->type.in6, + &((struct sockaddr_in6 *)src)->sin6_addr, + sizeof(struct in6_addr)); + dst->zone = ((struct sockaddr_in6 *)src)->sin6_scope_id; + break; + default: + UNREACHABLE(); + } +} + +isc_result_t +isc_interfaceiter_create(isc_mem_t *mctx, isc_interfaceiter_t **iterp) { + char strbuf[ISC_STRERRORSIZE]; + isc_interfaceiter_t *iter; + isc_result_t result; + int error; + unsigned long bytesReturned = 0; + + REQUIRE(mctx != NULL); + REQUIRE(iterp != NULL); + REQUIRE(*iterp == NULL); + + iter = isc_mem_get(mctx, sizeof(*iter)); + + InitSockets(); + + iter->mctx = mctx; + iter->buf4 = NULL; + iter->buf6 = NULL; + iter->pos4 = NULL; + iter->pos6 = 0; + iter->v6loop = true; + iter->pos6zero = true; + iter->buf6size = 0; + iter->buf4size = 0; + iter->result = ISC_R_FAILURE; + iter->numIF = 0; + iter->v4IF = 0; + + /* + * Create an unbound datagram socket to do the + * SIO_GET_INTERFACE_LIST WSAIoctl on. + */ + iter->socket = socket(AF_INET, SOCK_DGRAM, 0); + if (iter->socket == INVALID_SOCKET) { + error = WSAGetLastError(); + if (error == WSAEAFNOSUPPORT) { + goto inet6_only; + } + strerror_r(error, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "making interface scan socket: %s", strbuf); + result = ISC_R_UNEXPECTED; + goto socket_failure; + } + + /* + * Get the interface configuration, allocating more memory if + * necessary. + */ + iter->buf4size = IFCONF_SIZE_INITIAL * sizeof(INTERFACE_INFO); + + for (;;) { + iter->buf4 = isc_mem_get(mctx, iter->buf4size); + + if (WSAIoctl(iter->socket, SIO_GET_INTERFACE_LIST, 0, 0, + iter->buf4, iter->buf4size, &bytesReturned, 0, + 0) == SOCKET_ERROR) + { + error = WSAGetLastError(); + if (error != WSAEFAULT && error != WSAENOBUFS) { + errno = error; + strerror_r(error, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "get interface configuration: " + "%s", + strbuf); + result = ISC_R_UNEXPECTED; + goto ioctl_failure; + } + /* + * EINVAL. Retry with a bigger buffer. + */ + } else { + /* + * The WSAIoctl succeeded. + * If the number of the returned bytes is the same + * as the buffer size, we will grow it just in + * case and retry. + */ + if (bytesReturned > 0 && + (bytesReturned < iter->buf4size)) + { + break; + } + } + if (iter->buf4size >= IFCONF_SIZE_MAX * sizeof(INTERFACE_INFO)) + { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "get interface configuration: " + "maximum buffer size exceeded"); + result = ISC_R_UNEXPECTED; + goto ioctl_failure; + } + isc_mem_put(mctx, iter->buf4, iter->buf4size); + + iter->buf4size += IFCONF_SIZE_INCREMENT * + sizeof(INTERFACE_INFO); + } + + /* + * A newly created iterator has an undefined position + * until isc_interfaceiter_first() is called. + */ + iter->v4IF = bytesReturned / sizeof(INTERFACE_INFO); + + /* We don't need the socket any more, so close it */ + closesocket(iter->socket); + +inet6_only: + /* + * Create an unbound datagram socket to do the + * SIO_ADDRESS_LIST_QUERY WSAIoctl on. + */ + iter->socket = socket(AF_INET6, SOCK_DGRAM, 0); + if (iter->socket == INVALID_SOCKET) { + error = WSAGetLastError(); + if (error == WSAEAFNOSUPPORT) { + goto inet_only; + } + strerror_r(error, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "making interface scan socket: %s", strbuf); + result = ISC_R_UNEXPECTED; + goto ioctl_failure; + } + + /* + * Get the interface configuration, allocating more memory if + * necessary. + */ + iter->buf6size = sizeof(SOCKET_ADDRESS_LIST) + + IFCONF_SIZE_INITIAL * sizeof(SOCKET_ADDRESS); + + for (;;) { + iter->buf6 = isc_mem_get(mctx, iter->buf6size); + + if (WSAIoctl(iter->socket, SIO_ADDRESS_LIST_QUERY, 0, 0, + iter->buf6, iter->buf6size, &bytesReturned, 0, + 0) == SOCKET_ERROR) + { + error = WSAGetLastError(); + if (error != WSAEFAULT && error != WSAENOBUFS) { + errno = error; + strerror_r(error, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "sio address list query: %s", + strbuf); + result = ISC_R_UNEXPECTED; + goto ioctl6_failure; + } + /* + * EINVAL. Retry with a bigger buffer. + */ + } else { + break; + } + + if (iter->buf6size >= IFCONF_SIZE_MAX * sizeof(SOCKET_ADDRESS)) + { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "get interface configuration: " + "maximum buffer size exceeded"); + result = ISC_R_UNEXPECTED; + goto ioctl6_failure; + } + isc_mem_put(mctx, iter->buf6, iter->buf6size); + + iter->buf6size += IFCONF_SIZE_INCREMENT * + sizeof(SOCKET_ADDRESS); + } + + closesocket(iter->socket); + +inet_only: + iter->magic = IFITER_MAGIC; + *iterp = iter; + return (ISC_R_SUCCESS); + +ioctl6_failure: + isc_mem_put(mctx, iter->buf6, iter->buf6size); + +ioctl_failure: + if (iter->buf4 != NULL) { + isc_mem_put(mctx, iter->buf4, iter->buf4size); + } + if (iter->socket != INVALID_SOCKET) { + (void)closesocket(iter->socket); + } + +socket_failure: + isc_mem_put(mctx, iter, sizeof(*iter)); + return (result); +} + +/* + * Get information about the current interface to iter->current. + * If successful, return ISC_R_SUCCESS. + * If the interface has an unsupported address family, or if + * some operation on it fails, return ISC_R_IGNORE to make + * the higher-level iterator code ignore it. + */ + +static isc_result_t +internal_current(isc_interfaceiter_t *iter) { + BOOL ifNamed = FALSE; + unsigned long flags; + + REQUIRE(VALID_IFITER(iter)); + REQUIRE(iter->numIF >= 0); + + memset(&iter->current, 0, sizeof(iter->current)); + iter->current.af = AF_INET; + + get_addr(AF_INET, &iter->current.address, + (struct sockaddr *)&(iter->IFData.iiAddress)); + + /* + * Get interface flags. + */ + + iter->current.flags = 0; + flags = iter->IFData.iiFlags; + + if ((flags & IFF_UP) != 0) { + iter->current.flags |= INTERFACE_F_UP; + } + + if ((flags & IFF_POINTTOPOINT) != 0) { + iter->current.flags |= INTERFACE_F_POINTTOPOINT; + snprintf(iter->current.name, sizeof(iter->current.name), + "PPP Interface %d", iter->numIF); + ifNamed = TRUE; + } + + if ((flags & IFF_LOOPBACK) != 0) { + iter->current.flags |= INTERFACE_F_LOOPBACK; + snprintf(iter->current.name, sizeof(iter->current.name), + "Loopback Interface %d", iter->numIF); + ifNamed = TRUE; + } + + /* + * If the interface is point-to-point, get the destination address. + */ + if ((iter->current.flags & INTERFACE_F_POINTTOPOINT) != 0) { + get_addr(AF_INET, &iter->current.dstaddress, + (struct sockaddr *)&(iter->IFData.iiBroadcastAddress)); + } + + if (ifNamed == FALSE) { + snprintf(iter->current.name, sizeof(iter->current.name), + "TCP/IP Interface %d", iter->numIF); + } + + /* + * Get the network mask. + */ + get_addr(AF_INET, &iter->current.netmask, + (struct sockaddr *)&(iter->IFData.iiNetmask)); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +internal_current6(isc_interfaceiter_t *iter) { + SOCKET fd; + int i; + + REQUIRE(VALID_IFITER(iter)); + REQUIRE(iter->buf6 != NULL); + + memset(&iter->current, 0, sizeof(iter->current)); + iter->current.af = AF_INET6; + + if (!iter->pos6zero) { + if (iter->pos6 == 0U) { + iter->pos6zero = true; + } + get_addr(AF_INET6, &iter->current.address, + iter->buf6->Address[iter->pos6].lpSockaddr); + + /* + * Set interface flags. + */ + + iter->current.flags = INTERFACE_F_UP; + + snprintf(iter->current.name, sizeof(iter->current.name), + "TCP/IPv6 Interface %u", iter->pos6 + 1); + + for (i = 0; i < 16; i++) { + iter->current.netmask.type.in6.s6_addr[i] = 0xff; + } + iter->current.netmask.family = AF_INET6; + if (IN6_IS_ADDR_LOOPBACK(&iter->current.address.type.in6)) { + iter->v6loop = true; + } + } else { + /* + * See if we can bind to the ::1 and if so return ::1. + */ + struct sockaddr_in6 sin6; + + iter->v6loop = true; /* So we don't loop forever. */ + + fd = socket(AF_INET6, SOCK_DGRAM, 0); + if (fd == INVALID_SOCKET) { + return (ISC_R_IGNORE); + } + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_addr.s6_addr[15] = 1; + if (bind(fd, (struct sockaddr *)&sin6, sizeof(sin6)) < 0) { + closesocket(fd); + return (ISC_R_IGNORE); + } + closesocket(fd); + + iter->current.flags = INTERFACE_F_UP | INTERFACE_F_LOOPBACK; + snprintf(iter->current.name, sizeof(iter->current.name), + "TCP/IPv6 Loopback Interface"); + for (i = 0; i < 16; i++) { + if (i != 15) { + iter->current.address.type.in6.s6_addr[i] = 0; + } else { + iter->current.address.type.in6.s6_addr[i] = 1; + } + iter->current.netmask.type.in6.s6_addr[i] = 0xff; + } + iter->current.address.family = AF_INET6; + iter->current.netmask.family = AF_INET6; + } + return (ISC_R_SUCCESS); +} + +/* + * Step the iterator to the next interface. Unlike + * isc_interfaceiter_next(), this may leave the iterator + * positioned on an interface that will ultimately + * be ignored. Return ISC_R_NOMORE if there are no more + * interfaces, otherwise ISC_R_SUCCESS. + */ +static isc_result_t +internal_next(isc_interfaceiter_t *iter) { + if (iter->numIF >= iter->v4IF) { + return (ISC_R_NOMORE); + } + + /* + * The first one needs to be set up to point to the last + * Element of the array. Go to the end and back up + * Microsoft's implementation is peculiar for returning + * the list in reverse order + */ + + if (iter->numIF == 0) { + iter->pos4 = (INTERFACE_INFO *)(iter->buf4 + (iter->v4IF)); + } + + iter->pos4--; + if (&(iter->pos4) < &(iter->buf4)) { + return (ISC_R_NOMORE); + } + + memset(&(iter->IFData), 0, sizeof(INTERFACE_INFO)); + memmove(&(iter->IFData), iter->pos4, sizeof(INTERFACE_INFO)); + iter->numIF++; + + return (ISC_R_SUCCESS); +} + +static isc_result_t +internal_next6(isc_interfaceiter_t *iter) { + if (iter->pos6 == 0U && iter->v6loop) { + return (ISC_R_NOMORE); + } + if (iter->pos6 != 0U) { + iter->pos6--; + } + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_interfaceiter_current(isc_interfaceiter_t *iter, isc_interface_t *ifdata) { + REQUIRE(iter->result == ISC_R_SUCCESS); + memmove(ifdata, &iter->current, sizeof(*ifdata)); + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_interfaceiter_first(isc_interfaceiter_t *iter) { + REQUIRE(VALID_IFITER(iter)); + + if (iter->buf6 != NULL) { + iter->pos6 = iter->buf6->iAddressCount; + iter->v6loop = false; + iter->pos6zero = (iter->pos6 == 0U); + } + iter->result = ISC_R_SUCCESS; + return (isc_interfaceiter_next(iter)); +} + +isc_result_t +isc_interfaceiter_next(isc_interfaceiter_t *iter) { + isc_result_t result; + + REQUIRE(VALID_IFITER(iter)); + REQUIRE(iter->result == ISC_R_SUCCESS); + + for (;;) { + result = internal_next(iter); + if (result == ISC_R_NOMORE) { + result = internal_next6(iter); + if (result != ISC_R_SUCCESS) { + break; + } + result = internal_current6(iter); + if (result == ISC_R_IGNORE) { + continue; + } + break; + } else if (result != ISC_R_SUCCESS) { + break; + } + result = internal_current(iter); + if (result != ISC_R_IGNORE) { + break; + } + } + iter->result = result; + return (result); +} + +void +isc_interfaceiter_destroy(isc_interfaceiter_t **iterp) { + isc_interfaceiter_t *iter; + REQUIRE(iterp != NULL); + iter = *iterp; + *iterp = NULL; + REQUIRE(VALID_IFITER(iter)); + + if (iter->buf4 != NULL) { + isc_mem_put(iter->mctx, iter->buf4, iter->buf4size); + } + if (iter->buf6 != NULL) { + isc_mem_put(iter->mctx, iter->buf6, iter->buf6size); + } + + iter->magic = 0; + isc_mem_put(iter->mctx, iter, sizeof(*iter)); +} diff --git a/lib/isc/win32/ipv6.c b/lib/isc/win32/ipv6.c new file mode 100644 index 0000000..fecec98 --- /dev/null +++ b/lib/isc/win32/ipv6.c @@ -0,0 +1,18 @@ +/* + * 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. + */ + +#include <isc/net.h> +#include <isc/platform.h> + +LIBISC_EXTERNAL_DATA const struct in6_addr isc_in6addr_loopback = + IN6ADDR_LOOPBACK_INIT; diff --git a/lib/isc/win32/libgen.h b/lib/isc/win32/libgen.h new file mode 100644 index 0000000..b04ac37 --- /dev/null +++ b/lib/isc/win32/libgen.h @@ -0,0 +1,22 @@ +/* + * 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. + */ + +#ifndef LIBGEN_H +#define LIBGEN_H 1 + +char * +basename(const char *); +char * +dirname(const char *); + +#endif /* ifndef LIBGEN_H */ diff --git a/lib/isc/win32/libisc.def.exclude b/lib/isc/win32/libisc.def.exclude new file mode 100644 index 0000000..d2fe4ec --- /dev/null +++ b/lib/isc/win32/libisc.def.exclude @@ -0,0 +1,42 @@ +; These symbols are not needed by the WIN32 build, but build-tarballs +; will complain if they aren't present here. +isc_socket_accept +isc_socket_attach +isc_socket_bind +isc_socket_cancel +isc_socket_cleanunix +isc_socket_close +isc_socket_connect +isc_socket_create +isc_socket_detach +isc_socket_dscp +isc_socket_dup +isc_socket_fdwatchcreate +isc_socket_fdwatchpoke +isc_socket_filter +isc_socket_getpeername +isc_socket_getsockname +isc_socket_gettype +isc_socket_ipv6only +isc_socket_listen +isc_socket_open +isc_socket_permunix +isc_socket_recv +isc_socket_recv2 +isc_socket_recvv +isc_socket_register +isc_socket_send +isc_socket_sendto +isc_socket_sendto2 +isc_socket_sendtov +isc_socket_sendtov2 +isc_socket_sendv +isc_socketmgr_create +isc_socketmgr_create2 +isc_socketmgr_destroy +isc_socketmgr_setstats +isc_print_fprintf +isc_print_printf +isc_print_snprintf +isc_print_sprintf +isc_print_vsnprintf diff --git a/lib/isc/win32/libisc.def.in b/lib/isc/win32/libisc.def.in new file mode 100644 index 0000000..0ee055d --- /dev/null +++ b/lib/isc/win32/libisc.def.in @@ -0,0 +1,805 @@ +LIBRARY libisc + +; Exported Functions +EXPORTS + +NTReportError +closelog +@IF PKCS11 +getpassphrase +@END PKCS11 +isc_app_block +isc_app_ctxfinish +isc_app_ctxonrun +isc_app_ctxrun +isc_app_ctxshutdown +isc_app_ctxstart +isc_app_ctxsuspend +isc_app_finish +isc_app_onrun +isc_app_reload +isc_app_run +isc_app_shutdown +isc_app_start +isc_app_unblock +isc_appctx_create +isc_appctx_destroy +isc_astack_destroy +isc_astack_new +isc_astack_pop +isc_astack_trypush +isc__buffer_activeregion +isc__buffer_add +isc__buffer_availableregion +isc__buffer_back +isc__buffer_clear +isc__buffer_consumedregion +isc__buffer_first +isc__buffer_forward +isc__buffer_init +isc__buffer_initnull +isc__buffer_invalidate +isc__buffer_putmem +isc__buffer_putstr +isc__buffer_putuint16 +isc__buffer_putuint24 +isc__buffer_putuint32 +isc__buffer_putuint48 +isc__buffer_putuint8 +isc__buffer_region +isc__buffer_remainingregion +isc__buffer_setactive +isc__buffer_subtract +isc__buffer_usedregion +isc__mem_allocate +isc__mem_free +isc__mem_get +isc__mem_printactive +isc__mem_put +isc__mem_putanddetach +isc__mem_reallocate +isc__mem_strdup +isc__mem_strndup +isc__mempool_get +isc__mempool_put +isc__md_md5 +isc__md_sha1 +isc__md_sha224 +isc__md_sha256 +isc__md_sha384 +isc__md_sha512 +isc_socket_accept +isc_socket_attach +isc_socket_bind +isc_socket_cancel +isc_socket_cleanunix +isc_socket_close +isc_socket_connect +isc_socket_create +isc_socket_detach +isc_socket_dscp +isc_socket_dup +isc_socket_filter +isc_socket_getfd +isc_socket_getname +isc_socket_getpeername +isc_socket_getsockname +isc_socket_gettag +isc_socket_gettype +isc_socket_hasreuseport +isc_socket_ipv6only +isc_socket_listen +isc_socket_open +isc_socket_permunix +isc_socket_recv +isc_socket_recv2 +isc_socket_send +isc_socket_sendto +isc_socket_sendto2 +isc_socket_setname +isc_socketmgr_create +isc_socketmgr_create2 +isc_socketmgr_destroy +isc_socketmgr_getmaxsockets +isc_socketmgr_setreserved +isc_socketmgr_setstats +isc_aes128_crypt +isc_aes192_crypt +isc_aes256_crypt +isc_app_block +isc_app_ctxfinish +isc_app_ctxonrun +isc_app_ctxrun +isc_app_ctxshutdown +isc_app_ctxstart +isc_app_ctxsuspend +isc_app_finish +isc_app_isrunning +isc_app_onrun +isc_app_reload +isc_app_run +isc_app_shutdown +isc_app_start +isc_app_unblock +isc_appctx_create +isc_appctx_destroy +isc_assertion_failed +isc_assertion_setcallback +isc_assertion_typetotext +isc_backtrace_getsymbol +isc_backtrace_getsymbolfromindex +isc_backtrace_gettrace +isc_base32_decoderegion +isc_base32_decodestring +isc_base32_tobuffer +isc_base32_totext +isc_base32hex_decoderegion +isc_base32hex_decodestring +isc_base32hex_tobuffer +isc_base32hex_totext +isc_base32hexnp_decoderegion +isc_base32hexnp_decodestring +isc_base32hexnp_tobuffer +isc_base32hexnp_totext +isc_base64_decodestring +isc_base64_tobuffer +isc_base64_totext +isc_buffer_allocate +isc_buffer_compact +isc_buffer_copyregion +isc_buffer_dup +isc_buffer_free +isc_buffer_getuint16 +isc_buffer_getuint32 +isc_buffer_getuint48 +isc_buffer_getuint8 +isc_buffer_printf +isc_buffer_putdecint +isc_buffer_reinit +isc_buffer_reserve +isc_buffer_setautorealloc +isc_bufferlist_availablecount +isc_bufferlist_usedcount +isc_commandline_parse +isc_commandline_strtoargv +isc_condition_broadcast +isc_condition_destroy +isc_condition_init +isc_condition_signal +isc_condition_wait +isc_condition_waituntil +isc_counter_attach +isc_counter_create +isc_counter_detach +isc_counter_increment +isc_counter_setlimit +isc_counter_used +isc_crc64_final +isc_crc64_init +isc_crc64_update +isc_dir_chdir +isc_dir_chroot +isc_dir_close +isc_dir_createunique +isc_dir_init +isc_dir_open +isc_dir_read +isc_dir_reset +isc_enable_constructors +isc_entropy_get +isc_errno_toresult +isc_error_fatal +isc_error_runtimecheck +isc_error_setfatal +isc_error_setunexpected +isc_error_unexpected +isc_event_allocate +isc_event_constallocate +isc_event_free +isc_file_absolutepath +isc_file_basename +isc_file_bopenunique +isc_file_bopenuniquemode +isc_file_bopenuniqueprivate +isc_file_exists +isc_file_getmodtime +isc_file_getsize +isc_file_getsizefd +isc_file_isabsolute +isc_file_ischdiridempotent +isc_file_iscurrentdir +isc_file_isdirectory +isc_file_isdirwritable +isc_file_isplainfile +isc_file_isplainfilefd +isc_file_mktemplate +isc_file_mmap +isc_file_mode +isc_file_munmap +isc_file_openunique +isc_file_openuniquemode +isc_file_openuniqueprivate +isc_file_progname +isc_file_remove +isc_file_rename +isc_file_renameunique +isc_file_safecreate +isc_file_safemovefile +isc_file_sanitize +isc_file_settime +isc_file_splitpath +isc_file_template +isc_file_truncate +isc_fsaccess_add +isc_fsaccess_changeowner +isc_fsaccess_remove +isc_fsaccess_set +isc_hash32 +isc_hash64 +isc_hash_get_initializer +isc_hash_set_initializer +isc_heap_create +isc_heap_decreased +isc_heap_delete +isc_heap_destroy +isc_heap_foreach +isc_heap_element +isc_heap_increased +isc_heap_insert +isc_hex_decodestring +isc_hex_tobuffer +isc_hex_totext +isc_hmac +isc_hmac_new +isc_hmac_free +isc_hmac_init +isc_hmac_reset +isc_hmac_update +isc_hmac_final +isc_hmac_get_md_type +isc_hmac_get_size +isc_hmac_get_block_size +isc_ht_add +isc_ht_count +isc_ht_delete +isc_ht_destroy +isc_ht_find +isc_ht_init +isc_ht_iter_create +isc_ht_iter_current +isc_ht_iter_currentkey +isc_ht_iter_delcurrent_next +isc_ht_iter_destroy +isc_ht_iter_first +isc_ht_iter_next +isc_httpd_addheader +isc_httpd_addheaderuint +isc_httpd_endheaders +isc_httpd_response +isc_httpd_setfinishhook +isc_httpdmgr_addurl +isc_httpdmgr_addurl2 +isc_httpdmgr_create +isc_httpdmgr_shutdown +isc_interfaceiter_create +isc_interfaceiter_current +isc_interfaceiter_destroy +isc_interfaceiter_first +isc_interfaceiter_next +isc_interval_iszero +isc_interval_set +isc_iterated_hash +isc_lex_close +isc_lex_create +isc_lex_destroy +isc_lex_getcomments +isc_lex_getlasttokentext +isc_lex_getmastertoken +isc_lex_getoctaltoken +isc_lex_getsourceline +isc_lex_getsourcename +isc_lex_getspecials +isc_lex_gettoken +isc_lex_isfile +isc_lex_openbuffer +isc_lex_openfile +isc_lex_openstream +isc_lex_setcomments +isc_lex_setsourceline +isc_lex_setsourcename +isc_lex_setspecials +isc_lex_ungettoken +isc_lfsr_generate +isc_lfsr_generate32 +isc_lfsr_init +isc_lfsr_skip +isc_lib_ntservice +isc_lib_register +isc_log_categorybyname +isc_log_closefilelogs +isc_log_create +isc_log_createchannel +isc_log_destroy +isc_log_getdebuglevel +isc_log_getduplicateinterval +isc_log_gettag +isc_log_modulebyname +isc_log_opensyslog +isc_log_registercategories +isc_log_registermodules +isc_log_setcontext +isc_log_setdebuglevel +isc_log_setduplicateinterval +isc_log_settag +isc_log_usechannel +isc_log_vwrite +isc_log_vwrite1 +isc_log_wouldlog +isc_log_write +isc_log_write1 +isc_logconfig_create +isc_logconfig_destroy +isc_logconfig_use +isc_logfile_roll +isc_managers_create +isc_managers_destroy +isc_md_new +isc_md_init +isc_md_reset +isc_md_update +isc_md_final +isc_md_free +isc_md_get_md_type +isc_md_get_size +isc_md_get_block_size +isc_md_type_get_size +isc_md_type_get_block_size +isc_md +isc__mem_checkdestroyed +isc__mem_initialize +isc__mem_shutdown +isc_mem_attach +isc_mem_checkdestroyed +isc_mem_create +isc_mem_destroy +isc_mem_detach +isc_mem_getname +isc_mem_gettag +isc_mem_inuse +isc_mem_isovermem +isc_mem_maxinuse +isc_mem_references +@IF NOTYET +isc_mem_renderjson +@END NOTYET +@IF LIBXML2 +isc_mem_renderxml +@END LIBXML2 +isc_mem_setdestroycheck +isc_mem_setname +isc_mem_setwater +isc_mem_stats +isc_mem_total +isc_mem_waterack +isc_meminfo_totalphys +isc_mempool_create +isc_mempool_destroy +isc_mempool_getallocated +isc_mempool_getfillcount +isc_mempool_getfreecount +isc_mempool_getfreemax +isc_mempool_getmaxalloc +isc_mempool_setfillcount +isc_mempool_setfreemax +isc_mempool_setmaxalloc +isc_mempool_setname +isc_mutexblock_destroy +isc_mutexblock_init +isc_net_disableipv4 +isc_net_disableipv6 +isc_net_enableipv4 +isc_net_enableipv6 +isc_net_getudpportrange +isc_net_probe_ipv6only +isc_net_probe_ipv6pktinfo +isc_net_probedscp +isc_net_probeipv4 +isc_net_probeipv6 +isc_net_probeunix +isc_netaddr_any +isc_netaddr_any6 +isc_netaddr_eqprefix +isc_netaddr_equal +isc_netaddr_format +isc_netaddr_fromin +isc_netaddr_fromin6 +isc_netaddr_frompath +isc_netaddr_fromsockaddr +isc_netaddr_fromv4mapped +isc_netaddr_getzone +isc_netaddr_isexperimental +isc_netaddr_islinklocal +isc_netaddr_isloopback +isc_netaddr_ismulticast +isc_netaddr_isnetzero +isc_netaddr_issitelocal +isc_netaddr_masktoprefixlen +isc_netaddr_prefixok +isc_netaddr_setzone +isc_netaddr_totext +isc_netaddr_unspec +isc_netscope_pton +isc__nmhandle_attach +isc__nmhandle_detach +isc_nmhandle_cleartimeout +isc_nmhandle_getdata +isc_nmhandle_getextra +isc_nmhandle_is_stream +isc_nmhandle_keepalive +isc_nmhandle_netmgr +isc_nmhandle_localaddr +isc_nmhandle_peeraddr +isc_nmhandle_setdata +isc_nmhandle_settimeout +isc_nmhandle_setwritetimeout +isc_nm_attach +isc_nm_cancelread +isc_nm_detach +isc_nm_getloadbalancesockets +isc_nm_gettimeouts +isc_nm_listentcp +isc_nm_listentcpdns +isc_nm_listenudp +isc_nm_maxudp +isc_nm_pause +isc_nm_pauseread +isc_nm_read +isc_nm_resume +isc_nm_resumeread +isc_nm_send +isc_nm_setloadbalancesockets +isc_nm_setstats +isc_nm_settimeouts +isc_nm_stoplistening +isc_nm_task_enqueue +isc_nm_tcpconnect +isc_nm_tcpdnsconnect +isc_nm_tcpdns_sequential +isc_nm_tid +isc_nm_timer_create +isc_nm_timer_attach +isc_nm_timer_detach +isc_nm_timer_start +isc_nm_timer_stop +isc_nm_udpconnect +isc_nm_work_offload +isc_nmsocket_close +isc__nm_acquire_interlocked +isc__nm_drop_interlocked +isc__nm_acquire_interlocked_force +isc_nonce_buf +isc_ntpaths_get +isc_ntpaths_init +isc_once_do +isc_os_ncpus +isc_parse_uint16 +isc_parse_uint32 +isc_parse_uint8 +isc_pool_count +isc_pool_create +isc_pool_destroy +isc_pool_expand +isc_pool_get +isc_portset_add +isc_portset_addrange +isc_portset_create +isc_portset_destroy +isc_portset_isset +isc_portset_nports +isc_portset_remove +isc_portset_removerange +isc_quota_attach +isc_quota_attach_cb +isc_quota_cb_init +isc_quota_destroy +isc_quota_detach +isc_quota_getmax +isc_quota_getsoft +isc_quota_getused +isc_quota_init +isc_quota_max +isc_quota_soft +isc_radix_create +isc_radix_destroy +isc_radix_insert +isc_radix_process +isc_radix_remove +isc_radix_search +isc_random8 +isc_random16 +isc_random32 +isc_random_buf +isc_random_uniform +isc_ratelimiter_attach +isc_ratelimiter_create +isc_ratelimiter_dequeue +isc_ratelimiter_detach +isc_ratelimiter_enqueue +isc_ratelimiter_release +isc_ratelimiter_setinterval +isc_ratelimiter_setpertic +isc_ratelimiter_setpushpop +isc_ratelimiter_shutdown +isc_ratelimiter_stall +isc_regex_validate +isc_region_compare +isc_resource_getcurlimit +isc_resource_getlimit +isc_resource_setlimit +isc_result_register +isc_result_registerids +isc_result_toid +isc_result_totext +isc_rwlock_destroy +isc_rwlock_downgrade +isc_rwlock_init +isc_rwlock_lock +isc_rwlock_trylock +isc_rwlock_tryupgrade +isc_rwlock_unlock +isc_safe_memequal +isc_safe_memwipe +isc_serial_eq +isc_serial_ge +isc_serial_gt +isc_serial_le +isc_serial_lt +isc_serial_ne +isc_halfsiphash24 +isc_siphash24 +isc_sockaddr_any +isc_sockaddr_any6 +isc_sockaddr_anyofpf +isc_sockaddr_compare +isc_sockaddr_eqaddr +isc_sockaddr_eqaddrprefix +isc_sockaddr_equal +isc_sockaddr_format +isc_sockaddr_fromin +isc_sockaddr_fromin6 +isc_sockaddr_fromnetaddr +isc_sockaddr_frompath +isc_sockaddr_fromsockaddr +isc_sockaddr_getport +isc_sockaddr_hash +isc_sockaddr_isexperimental +isc_sockaddr_islinklocal +isc_sockaddr_ismulticast +isc_sockaddr_isnetzero +isc_sockaddr_issitelocal +isc_sockaddr_pf +isc_sockaddr_setport +isc_sockaddr_totext +isc_sockaddr_v6fromin +isc_socket_socketevent +isc_socketmgr_maxudp +@IF NOTYET +isc_socketmgr_renderjson +@END NOTYET +@IF LIBXML2 +isc_socketmgr_renderxml +@END LIBXML2 +isc_stats_attach +isc_stats_create +isc_stats_decrement +isc_stats_detach +isc_stats_dump +isc_stats_get_counter +isc_stats_increment +isc_stats_ncounters +isc_stats_resize +isc_stats_set +isc_stats_update_if_greater +isc_stdio_close +isc_stdio_flush +isc_stdio_open +isc_stdio_read +isc_stdio_seek +isc_stdio_sync +isc_stdio_tell +isc_stdio_write +isc_stdtime_get +isc_stdtime_tostring +isc_string_strerror_r +isc_symtab_count +isc_symtab_create +isc_symtab_define +isc_symtab_destroy +isc_symtab_lookup +isc_symtab_undefine +isc_syslog_facilityfromstring +isc_task_attach +isc_task_beginexclusive +isc_task_create +isc_task_create_bound +isc_task_destroy +isc_task_detach +isc_task_endexclusive +isc_task_exiting +isc_task_getname +isc_task_unsendrange +isc_task_getcurrenttime +isc_task_getcurrenttimex +isc_task_getnetmgr +isc_task_getprivilege +isc_task_gettag +isc_task_onshutdown +isc_task_pause +isc_task_privileged +isc_task_purge +isc_task_purgeevent +isc_task_purgerange +isc_task_ready +isc_task_run +isc_task_send +isc_task_sendanddetach +isc_task_sendto +isc_task_sendtoanddetach +isc_task_setname +isc_task_setprivilege +isc_task_setquantum +isc_task_shutdown +isc_task_unpause +isc_task_unsend +isc_taskmgr_attach +isc_taskmgr_detach +isc_taskmgr_excltask +isc_taskmgr_mode +@IF NOTYET +isc_taskmgr_renderjson +@END NOTYET +@IF LIBXML2 +isc_taskmgr_renderxml +@END LIBXML2 +isc_taskmgr_setexcltask +isc_taskmgr_setmode +isc_taskpool_create +isc_taskpool_destroy +isc_taskpool_expand +isc_taskpool_gettask +isc_taskpool_size +isc_thread_create +isc_thread_join +isc_thread_setaffinity +isc_thread_setconcurrency +isc_thread_setname +gmtime_r +localtime_r +nanosleep +usleep +isc_time_add +isc_time_compare +isc_time_formatISO8601 +isc_time_formatISO8601L +isc_time_formatISO8601Lms +isc_time_formatISO8601Lus +isc_time_formatISO8601ms +isc_time_formatISO8601us +isc_time_formathttptimestamp +isc_time_formatshorttimestamp +isc_time_formattimestamp +isc_time_isepoch +isc_time_microdiff +isc_time_nanoseconds +isc_time_now +isc_time_now_hires +isc_time_nowplusinterval +isc_time_parsehttptimestamp +isc_time_secondsastimet +isc_time_seconds +isc_time_set +isc_time_settoepoch +isc_time_subtract +isc_timer_create +isc_timer_destroy +isc_timer_gettype +isc_timer_reset +isc_timer_touch +isc_timermgr_create +isc_timermgr_destroy +isc_timermgr_poke +isc__tls_initialize +isc__tls_shutdown +isc__trampoline_initialize +isc__trampoline_shutdown +isc__trampoline_get +isc__trampoline_run +isc_tm_timegm +isc_tm_strptime +isc_url_parse +isc_utf8_bom +isc_utf8_valid +isc_win32os_versioncheck +openlog +@IF PKCS11 +pk11_attribute_bytype +pk11_attribute_first +pk11_attribute_next +pk11_dump_tokens +pk11_error_fatalcheck +pk11_finalize +pk11_get_best_token +pk11_get_lib_name +pk11_get_load_error_message +pk11_get_session +pk11_initialize +pk11_mem_get +pk11_mem_put +pk11_numbits +pk11_parse_uri +pk11_result_register +pk11_result_totext +pk11_return_session +pk11_set_lib_name +pkcs_C_CloseSession +pkcs_C_CreateObject +pkcs_C_DeriveKey +pkcs_C_DestroyObject +pkcs_C_DigestFinal +pkcs_C_DigestInit +pkcs_C_DigestUpdate +pkcs_C_Encrypt +pkcs_C_EncryptInit +pkcs_C_Finalize +pkcs_C_FindObjects +pkcs_C_FindObjectsFinal +pkcs_C_FindObjectsInit +pkcs_C_GenerateKey +pkcs_C_GenerateKeyPair +pkcs_C_GenerateRandom +pkcs_C_GetAttributeValue +pkcs_C_GetMechanismInfo +pkcs_C_GetSlotList +pkcs_C_GetTokenInfo +pkcs_C_Initialize +pkcs_C_Login +pkcs_C_Logout +pkcs_C_OpenSession +pkcs_C_SeedRandom +pkcs_C_SetAttributeValue +pkcs_C_Sign +pkcs_C_SignFinal +pkcs_C_SignInit +pkcs_C_SignUpdate +pkcs_C_Verify +pkcs_C_VerifyFinal +pkcs_C_VerifyInit +pkcs_C_VerifyUpdate +@END PKCS11 +strlcat +strlcpy +strnstr +syslog + +@IF NOLONGER +; Exported Data + +EXPORTS + +isc__backtrace_nsymbols DATA +isc__backtrace_symtable DATA +isc_bind9 DATA +isc_commandline_argument DATA +isc_commandline_errprint DATA +isc_commandline_index DATA +isc_commandline_option DATA +isc_commandline_progname DATA +isc_commandline_reset DATA +isc_dscp_check_value DATA +isc_hashctx DATA +isc_mem_debugging DATA +isc_tid_v DATA +@IF PKCS11 +pk11_verbose_init DATA +@END PKCS11 +@END NOLONGER diff --git a/lib/isc/win32/libisc.vcxproj.filters.in b/lib/isc/win32/libisc.vcxproj.filters.in new file mode 100644 index 0000000..75a4c80 --- /dev/null +++ b/lib/isc/win32/libisc.vcxproj.filters.in @@ -0,0 +1,671 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <Filter Include="Library Header Files"> + <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier> + <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions> + </Filter> + <Filter Include="Win32 Source Files"> + <UniqueIdentifier>{289562c2-1bdd-4582-b6bd-3f598ee23cbd}</UniqueIdentifier> + <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions> + </Filter> + <Filter Include="Win32 Header Files"> + <UniqueIdentifier>{d03c3e6a-e78e-4a01-bd77-64c839b1adfe}</UniqueIdentifier> + <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions> + </Filter> + <Filter Include="Library Source Files"> + <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier> + <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions> + </Filter> + <Filter Include="Resource Files"> + <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier> + <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions> + </Filter> + </ItemGroup> + <ItemGroup> + <None Include="libisc.def" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="..\include\isc\aes.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\app.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\assertions.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\astack.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\atomic.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\backtrace.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\barrier.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\base32.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\base64.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\bind9.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\boolean.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\buffer.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\bufferlist.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\commandline.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\counter.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\crc64.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\endian.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\errno.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\error.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\event.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\eventclass.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\file.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\formatcheck.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\fsaccess.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\hash.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\heap.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\hex.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\hmac.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\ht.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\httpd.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\interfaceiter.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\iterated_hash.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\json.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\lang.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\lex.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\lfsr.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\lib.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\list.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\log.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\magic.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\managers.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\mem.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\meminfo.h"> + <Filter>Win32 Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\mutexblock.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\netaddr.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\netscope.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\nonce.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\os.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\parseint.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\pool.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\portset.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\print.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\quota.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\radix.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\random.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\ratelimiter.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\refcount.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\regex.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\region.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\resource.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\result.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\resultclass.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\rwlock.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\safe.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\serial.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\siphash.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\sockaddr.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\socket.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\stats.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\stdio.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\stdlib.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\strerr.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\string.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\symtab.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\task.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\taskpool.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\timer.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\tm.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\types.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\url.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\utf8.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\util.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\isc\version.h"> + <Filter>Library Header Files</Filter> + </ClInclude> +@IF PKCS11 + <ClInclude Include="..\include\pk11\constants.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\pk11\internal.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\pk11\pk11.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\pk11\result.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\include\pkcs11\pkcs11.h"> + <Filter>Pkcs11 Header Files</Filter> + </ClInclude> +@END PKCS11 + <ClInclude Include="include\isc\bind_registry.h"> + <Filter>Win32 Header Files</Filter> + </ClInclude> + <ClInclude Include="include\isc\bindevt.h"> + <Filter>Win32 Header Files</Filter> + </ClInclude> + <ClInclude Include="include\isc\condition.h"> + <Filter>Win32 Header Files</Filter> + </ClInclude> + <ClInclude Include="include\isc\dir.h"> + <Filter>Win32 Header Files</Filter> + </ClInclude> + <ClInclude Include="include\isc\ipv6.h"> + <Filter>Win32 Header Files</Filter> + </ClInclude> + <ClInclude Include="include\isc\mutex.h"> + <Filter>Win32 Header Files</Filter> + </ClInclude> + <ClInclude Include="include\isc\net.h"> + <Filter>Win32 Header Files</Filter> + </ClInclude> + <ClInclude Include="include\isc\netdb.h"> + <Filter>Win32 Header Files</Filter> + </ClInclude> + <ClInclude Include="include\isc\ntgroups.h"> + <Filter>Win32 Header Files</Filter> + </ClInclude> + <ClInclude Include="include\isc\ntpaths.h"> + <Filter>Win32 Header Files</Filter> + </ClInclude> + <ClInclude Include="include\isc\offset.h"> + <Filter>Win32 Header Files</Filter> + </ClInclude> + <ClInclude Include="include\isc\once.h"> + <Filter>Win32 Header Files</Filter> + </ClInclude> + <ClInclude Include="include\isc\platform.h"> + <Filter>Win32 Header Files</Filter> + </ClInclude> + <ClInclude Include="include\isc\stat.h"> + <Filter>Win32 Header Files</Filter> + </ClInclude> + <ClInclude Include="include\isc\stdatomic.h"> + <Filter>Win32 Header Files</Filter> + </ClInclude> + <ClInclude Include="include\isc\stdtime.h"> + <Filter>Win32 Header Files</Filter> + </ClInclude> + <ClInclude Include="include\isc\strerror.h"> + <Filter>Win32 Header Files</Filter> + </ClInclude> + <ClInclude Include="include\isc\syslog.h"> + <Filter>Win32 Header Files</Filter> + </ClInclude> + <ClInclude Include="include\isc\thread.h"> + <Filter>Win32 Header Files</Filter> + </ClInclude> + <ClInclude Include="include\isc\time.h"> + <Filter>Win32 Header Files</Filter> + </ClInclude> + <ClInclude Include="include\isc\win32os.h"> + <Filter>Win32 Header Files</Filter> + </ClInclude> + <ClInclude Include="..\entropy_private.h"> + <Filter>Win32 Header Files</Filter> + </ClInclude> + <ClInclude Include="..\netmgr_p.h"> + <Filter>Win32 Header Files</Filter> + </ClInclude> + <ClInclude Include="..\openssl_shim.h"> + <Filter>Win32 Header Files</Filter> + </ClInclude> + <ClInclude Include="..\trampoline_p.h"> + <Filter>Win32 Header Files</Filter> + </ClInclude> + <ClInclude Include="errno2result.h"> + <Filter>Win32 Header Files</Filter> + </ClInclude> + <ClInclude Include="syslog.h"> + <Filter>Win32 Header Files</Filter> + </ClInclude> + <ClInclude Include="unistd.h"> + <Filter>Win32 Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\config.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\versions.h"> + <Filter>Library Header Files</Filter> + </ClInclude> + </ItemGroup> + <ItemGroup> + <ClCompile Include="condition.c"> + <Filter>Win32 Source Files</Filter> + </ClCompile> + <ClCompile Include="dir.c"> + <Filter>Win32 Source Files</Filter> + </ClCompile> + <ClCompile Include="DLLMain.c"> + <Filter>Win32 Source Files</Filter> + </ClCompile> + <ClCompile Include="errno.c"> + <Filter>Win32 Source Files</Filter> + </ClCompile> + <ClCompile Include="errno2result.c"> + <Filter>Win32 Source Files</Filter> + </ClCompile> + <ClCompile Include="file.c"> + <Filter>Win32 Source Files</Filter> + </ClCompile> + <ClCompile Include="fsaccess.c"> + <Filter>Win32 Source Files</Filter> + </ClCompile> + <ClCompile Include="interfaceiter.c"> + <Filter>Win32 Source Files</Filter> + </ClCompile> + <ClCompile Include="ipv6.c"> + <Filter>Win32 Source Files</Filter> + </ClCompile> + <ClCompile Include="meminfo.c"> + <Filter>Win32 Source Files</Filter> + </ClCompile> + <ClCompile Include="net.c"> + <Filter>Win32 Source Files</Filter> + </ClCompile> + <ClCompile Include="ntpaths.c"> + <Filter>Win32 Source Files</Filter> + </ClCompile> + <ClCompile Include="once.c"> + <Filter>Win32 Source Files</Filter> + </ClCompile> + <ClCompile Include="os.c"> + <Filter>Win32 Source Files</Filter> + </ClCompile> + <ClCompile Include="resource.c"> + <Filter>Win32 Source Files</Filter> + </ClCompile> + <ClCompile Include="socket.c"> + <Filter>Win32 Source Files</Filter> + </ClCompile> + <ClCompile Include="stdio.c"> + <Filter>Win32 Source Files</Filter> + </ClCompile> + <ClCompile Include="stdtime.c"> + <Filter>Win32 Source Files</Filter> + </ClCompile> + <ClCompile Include="syslog.c"> + <Filter>Win32 Source Files</Filter> + </ClCompile> + <ClCompile Include="thread.c"> + <Filter>Win32 Source Files</Filter> + </ClCompile> + <ClCompile Include="time.c"> + <Filter>Win32 Source Files</Filter> + </ClCompile> + <ClCompile Include="version.c"> + <Filter>Win32 Source Files</Filter> + </ClCompile> + <ClCompile Include="win32os.c"> + <Filter>Win32 Source Files</Filter> + </ClCompile> +@IF PKCS11 + <ClCompile Include="pk11_api.c"> + <Filter>Win32 Source Files</Filter> + </ClCompile> +@END PKCS11 + <ClCompile Include="..\aes.c"> + <Filter>Win32 Source Files</Filter> + </ClCompile> + <ClCompile Include="..\app.c"> + <Filter>Win32 Source Files</Filter> + </ClCompile> + <ClCompile Include="..\assertions.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\astack.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\backtrace.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\backtrace-emptytbl.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\base32.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\base64.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\bind9.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\buffer.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\bufferlist.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\commandline.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\counter.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\crc64.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\error.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\event.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\entropy.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\hash.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\heap.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\hex.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\hmac.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\ht.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\httpd.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\iterated_hash.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\lex.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\lfsr.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\lib.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\log.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\managers.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\mem.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\mutexblock.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\netaddr.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\netmgr\netmgr.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\netmgr\tcp.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\netmgr\udp.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\netmgr\uverr2result.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\netmgr\uv-compat.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\netmgr\tcpdns.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\netscope.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\nonce.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\openssl_shim.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\parseint.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\pool.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\portset.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\quota.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\radix.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\random.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\ratelimiter.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\regex.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\region.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\result.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\rwlock.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\safe.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\serial.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\siphash.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\sockaddr.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\stats.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\string.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\symtab.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\task.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\taskpool.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\timer.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\tls.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\trampoline.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\tm.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\url.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\utf8.c"> + <Filter>Library Source Files</Filter> + </ClCompile> +@IF PKCS11 + <ClCompile Include="..\pk11.c"> + <Filter>Library Source Files</Filter> + </ClCompile> + <ClCompile Include="..\pk11_result.c"> + <Filter>Library Source Files</Filter> + </ClCompile> +@END PKCS11 + </ItemGroup> +</Project> diff --git a/lib/isc/win32/libisc.vcxproj.in b/lib/isc/win32/libisc.vcxproj.in new file mode 100644 index 0000000..8fd9381 --- /dev/null +++ b/lib/isc/win32/libisc.vcxproj.in @@ -0,0 +1,483 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="@TOOLS_VERSION@" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|@PLATFORM@"> + <Configuration>Debug</Configuration> + <Platform>@PLATFORM@</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|@PLATFORM@"> + <Configuration>Release</Configuration> + <Platform>@PLATFORM@</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Globals"> + <ProjectGuid>{3840E563-D180-4761-AA9C-E6155F02EAFF}</ProjectGuid> + <Keyword>Win32Proj</Keyword> + <RootNamespace>libisc</RootNamespace> + @WINDOWS_TARGET_PLATFORM_VERSION@ + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <UseDebugLibraries>true</UseDebugLibraries> + <CharacterSet>MultiByte</CharacterSet> + @PLATFORM_TOOLSET@ + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <WholeProgramOptimization>true</WholeProgramOptimization> + <CharacterSet>MultiByte</CharacterSet> + @PLATFORM_TOOLSET@ + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <ImportGroup Label="ExtensionSettings"> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'"> + <LinkIncremental>true</LinkIncremental> + <OutDir>..\..\..\Build\$(Configuration)\</OutDir> + <IntDir>.\$(Configuration)\</IntDir> + <IntDirSharingDetected>None</IntDirSharingDetected> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'"> + <LinkIncremental>false</LinkIncremental> + <OutDir>..\..\..\Build\$(Configuration)\</OutDir> + <IntDir>.\$(Configuration)\</IntDir> + <IntDirSharingDetected>None</IntDirSharingDetected> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|@PLATFORM@'"> + <ClCompile> + <PrecompiledHeader> + </PrecompiledHeader> + <WarningLevel>Level4</WarningLevel> + <TreatWarningAsError>false</TreatWarningAsError> + <Optimization>Disabled</Optimization> +@IF PKCS11 + <PreprocessorDefinitions>BIND9;@PK11_LIB_LOCATION@WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBISC_EXPORTS;%(PreprocessorDefinitions);%(PreprocessorDefinitions)</PreprocessorDefinitions> + <ForcedIncludeFiles>..\..\..\config.h</ForcedIncludeFiles> + <AdditionalIncludeDirectories>.\;..\..\..\;@LIBXML2_INC@@LIBUV_INC@@OPENSSL_INC@@ZLIB_INC@include;..\include;..\;win32;..\..\isccfg\include;..\..\dns\win32\include;..\..\dns\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> +@ELSE PKCS11 + <PreprocessorDefinitions>BIND9;WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBISC_EXPORTS;%(PreprocessorDefinitions);%(PreprocessorDefinitions)</PreprocessorDefinitions> + <ForcedIncludeFiles>..\..\..\config.h</ForcedIncludeFiles> + <AdditionalIncludeDirectories>.\;..\..\..\;@LIBXML2_INC@@LIBUV_INC@@OPENSSL_INC@@ZLIB_INC@include;..\include;..\;win32;..\..\isccfg\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> +@END PKCS11 + <FunctionLevelLinking>true</FunctionLevelLinking> + <PrecompiledHeaderOutputFile>.\$(Configuration)\$(TargetName).pch</PrecompiledHeaderOutputFile> + <AssemblerListingLocation>.\$(Configuration)\</AssemblerListingLocation> + <ObjectFileName>.\$(Configuration)\</ObjectFileName> + <ProgramDataBaseFileName>$(OutDir)$(TargetName).pdb</ProgramDataBaseFileName> + <BrowseInformation>true</BrowseInformation> + <CompileAs>CompileAsC</CompileAs> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <GenerateDebugInformation>true</GenerateDebugInformation> + <OutputFile>..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt)</OutputFile> + <AdditionalDependencies>@OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@@LIBUV_LIB@@LIBXML2_LIB@@ZLIB_LIB@ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies> + <ModuleDefinitionFile>$(ProjectName).def</ModuleDefinitionFile> + <ImportLibrary>.\$(Configuration)\$(ProjectName).lib</ImportLibrary> + </Link> + <PreBuildEvent> + <Command>cd ..\..\..\win32utils + +if NOT Exist ..\Build mkdir ..\Build +if NOT Exist ..\Build\Debug mkdir ..\Build\Debug + +echo Copying documentation. + +copy ..\*.md ..\Build\Debug +copy ..\CHANGES* ..\Build\Debug +copy ..\COPYRIGHT ..\Build\Debug +copy ..\LICENSE ..\Build\Debug + +echo Copying COPYRIGHT notice. + +copy ..\COPYRIGHT ..\Build\Debug + +echo Copying the OpenSSL DLL and LICENSE. + +copy @OPENSSL_DLLCRYPTO@ ..\Build\Debug\ +copy @OPENSSL_DLLSSL@ ..\Build\Debug\ +copy @OPENSSL_PATH@\LICENSE ..\Build\Debug\OpenSSL-LICENSE + +echo Copying libuv DLL. +copy @LIBUV_DLL@ ..\Build\Debug\ + +@IF LIBXML2 +echo Copying the libxml DLL. + +copy @LIBXML2_DLL@ ..\Build\Debug\ +@END LIBXML2 + +@IF GSSAPI +echo Copying the GSSAPI and KRB5 DLLs. + +copy @GSSAPI_DLL@ ..\Build\Debug\ +copy @KRB5_DLL@ ..\Build\Debug\ +copy @COMERR_DLL@ ..\Build\Debug\ +copy @K5SPRT_DLL@ ..\Build\Debug\ +copy @WSHELP_DLL@ ..\Build\Debug\ +@END GSSAPI + +@IF IDNKIT +echo Copying the IDN kit DLL. + +copy @IDN_DLL@ ..\Build\Debug\ +copy @ICONV_DLL@ ..\Build\Debug\ +@END IDNKIT + +@IF ZLIB +echo Copying the zlib DLL. + +copy @ZLIB_DLL@ ..\Build\Debug\ +@END ZLIB + +echo Copying Visual C x86 Redistributable Installer. + +copy /Y @VCREDIST_PATH@ ..\Build\Debug\ + +echo Copying install files (flags and file list). + +copy InstallFlags ..\Build\Debug\ +copy InstallFiles ..\Build\Debug\ + +</Command> + </PreBuildEvent> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|@PLATFORM@'"> + <ClCompile> + <WarningLevel>Level1</WarningLevel> + <TreatWarningAsError>true</TreatWarningAsError> + <PrecompiledHeader> + </PrecompiledHeader> + <Optimization>MaxSpeed</Optimization> + <FunctionLevelLinking>true</FunctionLevelLinking> + <IntrinsicFunctions>@INTRINSIC@</IntrinsicFunctions> +@IF PKCS11 + <PreprocessorDefinitions>BIND9;@PK11_LIB_LOCATION@WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBISC_EXPORTS;%(PreprocessorDefinitions);%(PreprocessorDefinitions)</PreprocessorDefinitions> + <ForcedIncludeFiles>..\..\..\config.h</ForcedIncludeFiles> + <AdditionalIncludeDirectories>.\;..\..\..\;@LIBXML2_INC@@LIBUV_INC@@OPENSSL_INC@@ZLIB_INC@include;..\include;..\;win32;..\..\isccfg\include;..\..\dns\win32\include;..\..\dns\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> +@ELSE PKCS11 + <PreprocessorDefinitions>BIND9;WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBISC_EXPORTS;%(PreprocessorDefinitions);%(PreprocessorDefinitions)</PreprocessorDefinitions> + <ForcedIncludeFiles>..\..\..\config.h</ForcedIncludeFiles> + <AdditionalIncludeDirectories>.\;..\..\..\;@LIBXML2_INC@@LIBUV_INC@@OPENSSL_INC@@ZLIB_INC@include;..\include;..\;win32;..\..\isccfg\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> +@END PKCS11 + <InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion> + <WholeProgramOptimization>false</WholeProgramOptimization> + <StringPooling>true</StringPooling> + <PrecompiledHeaderOutputFile>.\$(Configuration)\$(TargetName).pch</PrecompiledHeaderOutputFile> + <AssemblerListingLocation>.\$(Configuration)\</AssemblerListingLocation> + <ObjectFileName>.\$(Configuration)\</ObjectFileName> + <ProgramDataBaseFileName>$(OutDir)$(TargetName).pdb</ProgramDataBaseFileName> + <CompileAs>CompileAsC</CompileAs> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <GenerateDebugInformation>false</GenerateDebugInformation> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <OptimizeReferences>true</OptimizeReferences> + <OutputFile>..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt)</OutputFile> + <AdditionalDependencies>@OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@@LIBUV_LIB@@LIBXML2_LIB@@ZLIB_LIB@ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies> + <ModuleDefinitionFile>$(ProjectName).def</ModuleDefinitionFile> + <ImportLibrary>.\$(Configuration)\$(ProjectName).lib</ImportLibrary> + <LinkTimeCodeGeneration>Default</LinkTimeCodeGeneration> + </Link> + <PreBuildEvent> + <Command>cd ..\..\..\win32utils + +if NOT Exist ..\Build mkdir ..\Build +if NOT Exist ..\Build\Release mkdir ..\Build\Release + +echo Copying documentation. + +copy ..\*.md ..\Build\Release +copy ..\CHANGES* ..\Build\Release +copy ..\COPYRIGHT ..\Build\Release +copy ..\LICENSE ..\Build\Release + +echo Copying the OpenSSL DLL and LICENSE. + +copy @OPENSSL_DLLCRYPTO@ ..\Build\Release\ +copy @OPENSSL_DLLSSL@ ..\Build\Release\ +copy @OPENSSL_PATH@\LICENSE ..\Build\Release\OpenSSL-LICENSE + +echo Copying libuv DLL. +copy @LIBUV_DLL@ ..\Build\Release\ + +@IF LIBXML2 +echo Copying the libxml DLL. + +copy @LIBXML2_DLL@ ..\Build\Release\ +@END LIBXML2 + +@IF GSSAPI +echo Copying the GSSAPI and KRB5 DLLs. + +copy @GSSAPI_DLL@ ..\Build\Release\ +copy @KRB5_DLL@ ..\Build\Release\ +copy @COMERR_DLL@ ..\Build\Release\ +copy @K5SPRT_DLL@ ..\Build\Release\ +copy @WSHELP_DLL@ ..\Build\Release\ +@END GSSAPI + +@IF IDNKIT +echo Copying the IDN kit DLL. + +copy @IDN_DLL@ ..\Build\Release\ +copy @ICONV_DLL@ ..\Build\Release\ +@END IDNKIT + +@IF ZLIB +echo Copying the zlib DLL. + +copy @ZLIB_DLL@ ..\Build\Release\ +@END ZLIB + +echo Copying Visual C x86 Redistributable Installer. + +copy /Y @VCREDIST_PATH@ ..\Build\Release\ + +echo Copying install files (flags and file list). + +copy InstallFlags ..\Build\Release\ +copy InstallFiles ..\Build\Release\ + +</Command> + </PreBuildEvent> + </ItemDefinitionGroup> + <ItemGroup> + <None Include="libisc.def" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="..\..\..\config.h" /> + <ClInclude Include="..\include\isc\aes.h" /> + <ClInclude Include="..\include\isc\app.h" /> + <ClInclude Include="..\include\isc\assertions.h" /> + <ClInclude Include="..\include\isc\astack.h" /> + <ClInclude Include="..\include\isc\atomic.h" /> + <ClInclude Include="..\include\isc\backtrace.h" /> + <ClInclude Include="..\include\isc\barrier.h" /> + <ClInclude Include="..\include\isc\base32.h" /> + <ClInclude Include="..\include\isc\base64.h" /> + <ClInclude Include="..\include\isc\bind9.h" /> + <ClInclude Include="..\include\isc\boolean.h" /> + <ClInclude Include="..\include\isc\buffer.h" /> + <ClInclude Include="..\include\isc\bufferlist.h" /> + <ClInclude Include="..\include\isc\commandline.h" /> + <ClInclude Include="..\include\isc\counter.h" /> + <ClInclude Include="..\include\isc\crc64.h" /> + <ClInclude Include="..\include\isc\endian.h" /> + <ClInclude Include="..\include\isc\errno.h" /> + <ClInclude Include="..\include\isc\error.h" /> + <ClInclude Include="..\include\isc\event.h" /> + <ClInclude Include="..\include\isc\eventclass.h" /> + <ClInclude Include="..\include\isc\file.h" /> + <ClInclude Include="..\include\isc\formatcheck.h" /> + <ClInclude Include="..\include\isc\fsaccess.h" /> + <ClInclude Include="..\include\isc\hash.h" /> + <ClInclude Include="..\include\isc\heap.h" /> + <ClInclude Include="..\include\isc\hex.h" /> + <ClInclude Include="..\include\isc\hmac.h" /> + <ClInclude Include="..\include\isc\ht.h" /> + <ClInclude Include="..\include\isc\httpd.h" /> + <ClInclude Include="..\include\isc\interfaceiter.h" /> + <ClInclude Include="..\include\isc\iterated_hash.h" /> + <ClInclude Include="..\include\isc\json.h" /> + <ClInclude Include="..\include\isc\lang.h" /> + <ClInclude Include="..\include\isc\lex.h" /> + <ClInclude Include="..\include\isc\lfsr.h" /> + <ClInclude Include="..\include\isc\lib.h" /> + <ClInclude Include="..\include\isc\list.h" /> + <ClInclude Include="..\include\isc\log.h" /> + <ClInclude Include="..\include\isc\magic.h" /> + <ClInclude Include="..\include\isc\managers.h" /> + <ClInclude Include="..\include\isc\md.h" /> + <ClInclude Include="..\include\isc\mem.h" /> + <ClInclude Include="..\include\isc\meminfo.h" /> + <ClInclude Include="..\include\isc\mutexblock.h" /> + <ClInclude Include="..\include\isc\netaddr.h" /> + <ClInclude Include="..\include\isc\netscope.h" /> + <ClInclude Include="..\include\isc\nonce.h" /> + <ClInclude Include="..\include\isc\os.h" /> + <ClInclude Include="..\include\isc\parseint.h" /> + <ClInclude Include="..\include\isc\pool.h" /> + <ClInclude Include="..\include\isc\portset.h" /> + <ClInclude Include="..\include\isc\print.h" /> + <ClInclude Include="..\include\isc\quota.h" /> + <ClInclude Include="..\include\isc\radix.h" /> + <ClInclude Include="..\include\isc\random.h" /> + <ClInclude Include="..\include\isc\ratelimiter.h" /> + <ClInclude Include="..\include\isc\refcount.h" /> + <ClInclude Include="..\include\isc\regex.h" /> + <ClInclude Include="..\include\isc\region.h" /> + <ClInclude Include="..\include\isc\resource.h" /> + <ClInclude Include="..\include\isc\result.h" /> + <ClInclude Include="..\include\isc\resultclass.h" /> + <ClInclude Include="..\include\isc\rwlock.h" /> + <ClInclude Include="..\include\isc\safe.h" /> + <ClInclude Include="..\include\isc\serial.h" /> + <ClInclude Include="..\include\isc\siphash.h" /> + <ClInclude Include="..\include\isc\sockaddr.h" /> + <ClInclude Include="..\include\isc\socket.h" /> + <ClInclude Include="..\include\isc\stats.h" /> + <ClInclude Include="..\include\isc\stdio.h" /> + <ClInclude Include="..\include\isc\stdlib.h" /> + <ClInclude Include="..\include\isc\strerr.h" /> + <ClInclude Include="..\include\isc\string.h" /> + <ClInclude Include="..\include\isc\symtab.h" /> + <ClInclude Include="..\include\isc\task.h" /> + <ClInclude Include="..\include\isc\taskpool.h" /> + <ClInclude Include="..\include\isc\timer.h" /> + <ClInclude Include="..\include\isc\tm.h" /> + <ClInclude Include="..\include\isc\types.h" /> + <ClInclude Include="..\include\isc\utf8.h" /> + <ClInclude Include="..\include\isc\util.h" /> + <ClInclude Include="..\include\isc\version.h" /> +@IF PKCS11 + <ClInclude Include="..\include\pk11\constants.h" /> + <ClInclude Include="..\include\pk11\internal.h" /> + <ClInclude Include="..\include\pk11\pk11.h" /> + <ClInclude Include="..\include\pk11\result.h" /> + <ClInclude Include="..\include\pkcs11\pkcs11.h" /> +@END PKCS11 + <ClInclude Include="errno2result.h" /> + <ClInclude Include="include\isc\bindevt.h" /> + <ClInclude Include="include\isc\bind_registry.h" /> + <ClInclude Include="include\isc\condition.h" /> + <ClInclude Include="include\isc\dir.h" /> + <ClInclude Include="include\isc\ipv6.h" /> + <ClInclude Include="include\isc\mutex.h" /> + <ClInclude Include="include\isc\net.h" /> + <ClInclude Include="include\isc\netdb.h" /> + <ClInclude Include="include\isc\ntgroups.h" /> + <ClInclude Include="include\isc\ntpaths.h" /> + <ClInclude Include="include\isc\offset.h" /> + <ClInclude Include="include\isc\once.h" /> + <ClInclude Include="include\isc\platform.h" /> + <ClInclude Include="include\isc\stat.h" /> + <ClInclude Include="include\isc\stdatomic.h" /> + <ClInclude Include="include\isc\stdtime.h" /> + <ClInclude Include="include\isc\strerror.h" /> + <ClInclude Include="include\isc\syslog.h" /> + <ClInclude Include="include\isc\thread.h" /> + <ClInclude Include="include\isc\time.h" /> + <ClInclude Include="include\isc\win32os.h" /> + <ClInclude Include="..\entropy_private.h" /> + <ClInclude Include="..\netmgr_p.h" /> + <ClInclude Include="..\openssl_shim.h" /> + <ClInclude Include="..\trampoline_p.h" /> + <ClInclude Include="syslog.h" /> + <ClInclude Include="unistd.h" /> + <ClInclude Include="..\..\versions.h" /> + </ItemGroup> + <ItemGroup> + <ClCompile Include="..\aes.c" /> + <ClCompile Include="..\app.c" /> + <ClCompile Include="..\assertions.c" /> + <ClCompile Include="..\astack.c" /> + <ClCompile Include="..\backtrace-emptytbl.c" /> + <ClCompile Include="..\backtrace.c" /> + <ClCompile Include="..\base32.c" /> + <ClCompile Include="..\base64.c" /> + <ClCompile Include="..\bind9.c" /> + <ClCompile Include="..\buffer.c" /> + <ClCompile Include="..\bufferlist.c" /> + <ClCompile Include="..\commandline.c" /> + <ClCompile Include="..\counter.c" /> + <ClCompile Include="..\crc64.c" /> + <ClCompile Include="..\entropy.c" /> + <ClCompile Include="..\error.c" /> + <ClCompile Include="..\event.c" /> + <ClCompile Include="..\hash.c" /> + <ClCompile Include="..\heap.c" /> + <ClCompile Include="..\hex.c" /> + <ClCompile Include="..\hmac.c" /> + <ClCompile Include="..\ht.c" /> + <ClCompile Include="..\httpd.c" /> + <ClCompile Include="..\iterated_hash.c" /> + <ClCompile Include="..\lex.c" /> + <ClCompile Include="..\lfsr.c" /> + <ClCompile Include="..\lib.c" /> + <ClCompile Include="..\log.c" /> + <ClCompile Include="..\managers.c" /> + <ClCompile Include="..\md.c" /> + <ClCompile Include="..\mem.c" /> + <ClCompile Include="..\mutexblock.c" /> + <ClCompile Include="..\netaddr.c" /> + <ClCompile Include="..\netmgr\netmgr.c" /> + <ClCompile Include="..\netmgr\tcp.c" /> + <ClCompile Include="..\netmgr\udp.c" /> + <ClCompile Include="..\netmgr\uverr2result.c" /> + <ClCompile Include="..\netmgr\uv-compat.c" /> + <ClCompile Include="..\netmgr\tcpdns.c" /> + <ClCompile Include="..\netscope.c" /> + <ClCompile Include="..\nonce.c" /> + <ClCompile Include="..\openssl_shim.c" /> + <ClCompile Include="..\parseint.c" /> + <ClCompile Include="..\pool.c" /> + <ClCompile Include="..\portset.c" /> + <ClCompile Include="..\quota.c" /> + <ClCompile Include="..\radix.c" /> + <ClCompile Include="..\random.c" /> + <ClCompile Include="..\ratelimiter.c" /> + <ClCompile Include="..\regex.c" /> + <ClCompile Include="..\region.c" /> + <ClCompile Include="..\result.c" /> + <ClCompile Include="..\rwlock.c" /> + <ClCompile Include="..\safe.c" /> + <ClCompile Include="..\serial.c" /> + <ClCompile Include="..\siphash.c" /> + <ClCompile Include="..\sockaddr.c" /> + <ClCompile Include="..\stats.c" /> + <ClCompile Include="..\string.c" /> + <ClCompile Include="..\symtab.c" /> + <ClCompile Include="..\task.c" /> + <ClCompile Include="..\taskpool.c" /> + <ClCompile Include="..\timer.c" /> + <ClCompile Include="..\trampoline.c" /> + <ClCompile Include="..\tls.c" /> + <ClCompile Include="..\tm.c" /> + <ClCompile Include="..\url.c" /> + <ClCompile Include="..\utf8.c" /> +@IF PKCS11 + <ClCompile Include="..\pk11.c" /> + <ClCompile Include="..\pk11_result.c" /> +@END PKCS11 + <ClCompile Include="condition.c" /> + <ClCompile Include="dir.c" /> + <ClCompile Include="DLLMain.c" /> + <ClCompile Include="errno.c" /> + <ClCompile Include="errno2result.c" /> + <ClCompile Include="file.c" /> + <ClCompile Include="fsaccess.c" /> + <ClCompile Include="interfaceiter.c" /> + <ClCompile Include="ipv6.c" /> + <ClCompile Include="meminfo.c" /> + <ClCompile Include="net.c" /> + <ClCompile Include="ntpaths.c" /> + <ClCompile Include="once.c" /> + <ClCompile Include="os.c" /> + <ClCompile Include="resource.c" /> + <ClCompile Include="socket.c" /> + <ClCompile Include="stdio.c" /> + <ClCompile Include="stdtime.c" /> + <ClCompile Include="syslog.c" /> + <ClCompile Include="thread.c" /> + <ClCompile Include="time.c" /> + <ClCompile Include="version.c" /> + <ClCompile Include="win32os.c" /> +@IF PKCS11 + <ClCompile Include="pk11_api.c" /> +@END PKCS11 + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project> diff --git a/lib/isc/win32/libisc.vcxproj.user b/lib/isc/win32/libisc.vcxproj.user new file mode 100644 index 0000000..ace9a86 --- /dev/null +++ b/lib/isc/win32/libisc.vcxproj.user @@ -0,0 +1,3 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> +</Project>
\ No newline at end of file diff --git a/lib/isc/win32/meminfo.c b/lib/isc/win32/meminfo.c new file mode 100644 index 0000000..ab10b89 --- /dev/null +++ b/lib/isc/win32/meminfo.c @@ -0,0 +1,26 @@ +/* + * 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. + */ + +#include <inttypes.h> +#include <windows.h> + +#include <isc/meminfo.h> + +uint64_t +isc_meminfo_totalphys(void) { + MEMORYSTATUSEX statex; + + statex.dwLength = sizeof(statex); + GlobalMemoryStatusEx(&statex); + return ((uint64_t)statex.ullTotalPhys); +} diff --git a/lib/isc/win32/net.c b/lib/isc/win32/net.c new file mode 100644 index 0000000..ea4656d --- /dev/null +++ b/lib/isc/win32/net.c @@ -0,0 +1,301 @@ +/* + * 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. + */ + +#include <errno.h> +#include <unistd.h> + +#include <isc/log.h> +#include <isc/net.h> +#include <isc/once.h> +#include <isc/strerr.h> +#include <isc/string.h> +#include <isc/util.h> + +/*% + * Definitions about UDP port range specification. This is a total mess of + * portability variants: some use sysctl (but the sysctl names vary), some use + * system-specific interfaces, some have the same interface for IPv4 and IPv6, + * some separate them, etc... + */ + +/*% + * The last resort defaults: use all non well known port space + */ +#ifndef ISC_NET_PORTRANGELOW +#define ISC_NET_PORTRANGELOW 32768 +#endif /* ISC_NET_PORTRANGELOW */ +#ifndef ISC_NET_PORTRANGEHIGH +#define ISC_NET_PORTRANGEHIGH 65535 +#endif /* ISC_NET_PORTRANGEHIGH */ + +static isc_once_t once = ISC_ONCE_INIT; +static isc_once_t once_ipv6only = ISC_ONCE_INIT; +static isc_once_t once_ipv6pktinfo = ISC_ONCE_INIT; +static isc_result_t ipv4_result = ISC_R_NOTFOUND; +static isc_result_t ipv6_result = ISC_R_NOTFOUND; +static isc_result_t ipv6only_result = ISC_R_NOTFOUND; +static isc_result_t ipv6pktinfo_result = ISC_R_NOTFOUND; + +void +InitSockets(void); + +static isc_result_t +try_proto(int domain) { + SOCKET s; + char strbuf[ISC_STRERRORSIZE]; + int errval; + + s = socket(domain, SOCK_STREAM, IPPROTO_TCP); + if (s == INVALID_SOCKET) { + errval = WSAGetLastError(); + switch (errval) { + case WSAEAFNOSUPPORT: + case WSAEPROTONOSUPPORT: + case WSAEINVAL: + return (ISC_R_NOTFOUND); + default: + strerror_r(errval, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "socket() failed: %s", strbuf); + return (ISC_R_UNEXPECTED); + } + } + + closesocket(s); + + return (ISC_R_SUCCESS); +} + +static void +initialize_action(void) { + InitSockets(); + ipv4_result = try_proto(PF_INET); + ipv6_result = try_proto(PF_INET6); +} + +static void +initialize(void) { + RUNTIME_CHECK(isc_once_do(&once, initialize_action) == ISC_R_SUCCESS); +} + +isc_result_t +isc_net_probeipv4(void) { + initialize(); + return (ipv4_result); +} + +isc_result_t +isc_net_probeipv6(void) { + initialize(); + return (ipv6_result); +} + +isc_result_t +isc_net_probeunix(void) { + return (ISC_R_NOTFOUND); +} + +static void +try_ipv6only(void) { +#ifdef IPV6_V6ONLY + SOCKET s; + int on; + char strbuf[ISC_STRERRORSIZE]; +#endif /* ifdef IPV6_V6ONLY */ + isc_result_t result; + + result = isc_net_probeipv6(); + if (result != ISC_R_SUCCESS) { + ipv6only_result = result; + return; + } + +#ifndef IPV6_V6ONLY + ipv6only_result = ISC_R_NOTFOUND; + return; +#else /* ifndef IPV6_V6ONLY */ + /* check for TCP sockets */ + s = socket(PF_INET6, SOCK_STREAM, 0); + if (s == INVALID_SOCKET) { + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, "socket() failed: %s", + strbuf); + ipv6only_result = ISC_R_UNEXPECTED; + return; + } + + on = 1; + if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, (const char *)&on, + sizeof(on)) < 0) + { + ipv6only_result = ISC_R_NOTFOUND; + goto close; + } + + closesocket(s); + + /* check for UDP sockets */ + s = socket(PF_INET6, SOCK_DGRAM, 0); + if (s == INVALID_SOCKET) { + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, "socket() failed: %s", + strbuf); + ipv6only_result = ISC_R_UNEXPECTED; + return; + } + + on = 1; + if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, (const char *)&on, + sizeof(on)) < 0) + { + ipv6only_result = ISC_R_NOTFOUND; + goto close; + } + + ipv6only_result = ISC_R_SUCCESS; + +close: + closesocket(s); + return; +#endif /* IPV6_V6ONLY */ +} + +static void +initialize_ipv6only(void) { + RUNTIME_CHECK(isc_once_do(&once_ipv6only, try_ipv6only) == + ISC_R_SUCCESS); +} + +#ifdef __notyet__ +/* + * XXXMPA requires win32/socket.c to be updated to support + * WSASendMsg and WSARecvMsg which are themselves Winsock + * and compiler version dependent. + */ +static void +try_ipv6pktinfo(void) { + SOCKET s; + int on; + char strbuf[ISC_STRERRORSIZE]; + isc_result_t result; + int optname; + + result = isc_net_probeipv6(); + if (result != ISC_R_SUCCESS) { + ipv6pktinfo_result = result; + return; + } + + /* we only use this for UDP sockets */ + s = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP); + if (s == INVALID_SOCKET) { + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, "socket() failed: %s", + strbuf); + ipv6pktinfo_result = ISC_R_UNEXPECTED; + return; + } + +#ifdef IPV6_RECVPKTINFO + optname = IPV6_RECVPKTINFO; +#else /* ifdef IPV6_RECVPKTINFO */ + optname = IPV6_PKTINFO; +#endif /* ifdef IPV6_RECVPKTINFO */ + on = 1; + if (setsockopt(s, IPPROTO_IPV6, optname, (const char *)&on, + sizeof(on)) < 0) + { + ipv6pktinfo_result = ISC_R_NOTFOUND; + goto close; + } + + ipv6pktinfo_result = ISC_R_SUCCESS; + +close: + closesocket(s); + return; +} + +static void +initialize_ipv6pktinfo(void) { + RUNTIME_CHECK(isc_once_do(&once_ipv6pktinfo, try_ipv6pktinfo) == + ISC_R_SUCCESS); +} +#endif /* __notyet__ */ + +isc_result_t +isc_net_probe_ipv6only(void) { + initialize_ipv6only(); + return (ipv6only_result); +} + +isc_result_t +isc_net_probe_ipv6pktinfo(void) { +#ifdef __notyet__ + initialize_ipv6pktinfo(); +#endif /* __notyet__ */ + return (ipv6pktinfo_result); +} + +isc_result_t +isc_net_getudpportrange(int af, in_port_t *low, in_port_t *high) { + int result = ISC_R_FAILURE; + + REQUIRE(low != NULL && high != NULL); + + UNUSED(af); + + if (result != ISC_R_SUCCESS) { + *low = ISC_NET_PORTRANGELOW; + *high = ISC_NET_PORTRANGEHIGH; + } + + return (ISC_R_SUCCESS); /* we currently never fail in this function */ +} + +void +isc_net_disableipv4(void) { + initialize(); + if (ipv4_result == ISC_R_SUCCESS) { + ipv4_result = ISC_R_DISABLED; + } +} + +void +isc_net_disableipv6(void) { + initialize(); + if (ipv6_result == ISC_R_SUCCESS) { + ipv6_result = ISC_R_DISABLED; + } +} + +void +isc_net_enableipv4(void) { + initialize(); + if (ipv4_result == ISC_R_DISABLED) { + ipv4_result = ISC_R_SUCCESS; + } +} + +void +isc_net_enableipv6(void) { + initialize(); + if (ipv6_result == ISC_R_DISABLED) { + ipv6_result = ISC_R_SUCCESS; + } +} + +unsigned int +isc_net_probedscp(void) { + return (0); +} diff --git a/lib/isc/win32/netdb.h b/lib/isc/win32/netdb.h new file mode 100644 index 0000000..5a5c122 --- /dev/null +++ b/lib/isc/win32/netdb.h @@ -0,0 +1,182 @@ +/* + * 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. + */ + +#ifndef NETDB_H +#define NETDB_H 1 + +#include <stddef.h> +#include <winsock2.h> + +/* + * Define if <netdb.h> does not declare struct addrinfo. + */ + +#if _MSC_VER < 1600 +struct addrinfo { + int ai_flags; /* AI_PASSIVE, AI_CANONNAME */ + int ai_family; /* PF_xxx */ + int ai_socktype; /* SOCK_xxx */ + int ai_protocol; /* 0 or IPPROTO_xxx for IPv4 and + * IPv6 */ + size_t ai_addrlen; /* Length of ai_addr */ + char *ai_canonname; /* Canonical name for hostname */ + struct sockaddr *ai_addr; /* Binary address */ + struct addrinfo *ai_next; /* Next structure in linked list */ +}; +#endif /* if _MSC_VER < 1600 */ + +/* + * Undefine all \#defines we are interested in as <netdb.h> may or may not have + * defined them. + */ + +/* + * Error return codes from gethostbyname() and gethostbyaddr() + * (left in extern int h_errno). + */ + +#undef NETDB_INTERNAL +#undef NETDB_SUCCESS +#undef HOST_NOT_FOUND +#undef TRY_AGAIN +#undef NO_RECOVERY +#undef NO_DATA +#undef NO_ADDRESS + +#define NETDB_INTERNAL -1 /* see errno */ +#define NETDB_SUCCESS 0 /* no problem */ +#define HOST_NOT_FOUND 1 /* Authoritative Answer Host not found */ +#define TRY_AGAIN 2 /* Non-Authoritative Host not found, or SERVERFAIL */ +#define NO_RECOVERY 3 /* Non recoverable errors, FORMERR, REFUSED, NOTIMP */ +#define NO_DATA 4 /* Valid name, no data record of requested type */ +#define NO_ADDRESS NO_DATA /* no address, look for MX record */ + +/* + * Error return codes from getaddrinfo() + */ + +#undef EAI_ADDRFAMILY +#undef EAI_AGAIN +#undef EAI_BADFLAGS +#undef EAI_FAIL +#undef EAI_FAMILY +#undef EAI_MEMORY +#undef EAI_NODATA +#undef EAI_NONAME +#undef EAI_SERVICE +#undef EAI_SOCKTYPE +#undef EAI_SYSTEM +#undef EAI_BADHINTS +#undef EAI_PROTOCOL +#undef EAI_MAX + +#define EAI_ADDRFAMILY 1 /* address family for hostname not supported */ +#define EAI_AGAIN 2 /* temporary failure in name resolution */ +#define EAI_BADFLAGS 3 /* invalid value for ai_flags */ +#define EAI_FAIL 4 /* non-recoverable failure in name resolution */ +#define EAI_FAMILY 5 /* ai_family not supported */ +#define EAI_MEMORY 6 /* memory allocation failure */ +#define EAI_NODATA 7 /* no address associated with hostname */ +#define EAI_NONAME 8 /* hostname nor servname provided, or not known */ +#define EAI_SERVICE 9 /* servname not supported for ai_socktype */ +#define EAI_SOCKTYPE 10 /* ai_socktype not supported */ +#define EAI_SYSTEM 11 /* system error returned in errno */ +#define EAI_BADHINTS 12 +#define EAI_PROTOCOL 13 +#define EAI_MAX 14 + +/* + * Flag values for getaddrinfo() + */ +#undef AI_PASSIVE +#undef AI_CANONNAME +#undef AI_NUMERICHOST + +#define AI_PASSIVE 0x00000001 +#define AI_CANONNAME 0x00000002 +#define AI_NUMERICHOST 0x00000004 + +/* + * Flag values for getipnodebyname() + */ +#undef AI_V4MAPPED +#undef AI_ALL +#undef AI_ADDRCONFIG +#undef AI_DEFAULT + +#define AI_V4MAPPED 0x00000008 +#define AI_ALL 0x00000010 +#define AI_ADDRCONFIG 0x00000020 +#define AI_DEFAULT (AI_V4MAPPED | AI_ADDRCONFIG) + +/* + * Constants for getnameinfo() + */ +#undef NI_MAXHOST +#undef NI_MAXSERV + +#define NI_MAXHOST 1025 +#define NI_MAXSERV 32 + +/* + * Flag values for getnameinfo() + */ +#undef NI_NOFQDN +#undef NI_NUMERICHOST +#undef NI_NAMEREQD +#undef NI_NUMERICSERV +#undef NI_DGRAM +#undef NI_NUMERICSCOPE + +#define NI_NOFQDN 0x00000001 +#define NI_NUMERICHOST 0x00000002 +#define NI_NAMEREQD 0x00000004 +#define NI_NUMERICSERV 0x00000008 +#define NI_DGRAM 0x00000010 +#define NI_NUMERICSCOPE 0x00000020 /*2553bis-00*/ + +/* + * Structures for getrrsetbyname() + */ +struct rdatainfo { + unsigned int rdi_length; + unsigned char *rdi_data; +}; + +struct rrsetinfo { + unsigned int rri_flags; + int rri_rdclass; + int rri_rdtype; + unsigned int rri_ttl; + unsigned int rri_nrdatas; + unsigned int rri_nsigs; + char *rri_name; + struct rdatainfo *rri_rdatas; + struct rdatainfo *rri_sigs; +}; + +/* + * Flags for getrrsetbyname() + */ +#define RRSET_VALIDATED 0x00000001 +/* Set was dnssec validated */ + +/* + * Return codes for getrrsetbyname() + */ +#define ERRSET_SUCCESS 0 +#define ERRSET_NOMEMORY 1 +#define ERRSET_FAIL 2 +#define ERRSET_INVAL 3 + +#endif /* NETDB_H */ diff --git a/lib/isc/win32/ntgroups.c b/lib/isc/win32/ntgroups.c new file mode 100644 index 0000000..0584d48 --- /dev/null +++ b/lib/isc/win32/ntgroups.c @@ -0,0 +1,215 @@ +/* + * 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. + */ + +/* + * The NT Groups have two groups that are not well documented and are + * not normally seen: None and Everyone. A user account belongs to + * any number of groups, but if it is not a member of any group then + * it is a member of the None Group. The None group is not listed + * anywhere. You cannot remove an account from the none group except + * by making it a member of some other group, The second group is the + * Everyone group. All accounts, no matter how many groups that they + * belong to, also belong to the Everyone group. You cannot remove an + * account from the Everyone group. + */ + +#ifndef UNICODE +#define UNICODE +#endif /* UNICODE */ + +/* + * Silence warnings. + */ +#define _CRT_SECURE_NO_DEPRECATE 1 + +/* clang-format off */ +#include <assert.h> +#include <windows.h> +#include <lm.h> +/* clang-format on */ + +#include <isc/ntgroups.h> +#include <isc/result.h> + +#define MAX_NAME_LENGTH 256 + +#define CHECK(op) \ + do { \ + result = (op); \ + if (result != ISC_R_SUCCESS) { \ + goto cleanup; \ + } \ + } while (0) + +isc_result_t +isc_ntsecurity_getaccountgroups(char *username, char **GroupList, + unsigned int maxgroups, + unsigned int *totalGroups) { + LPGROUP_USERS_INFO_0 pTmpBuf; + LPLOCALGROUP_USERS_INFO_0 pTmpLBuf; + DWORD i; + LPLOCALGROUP_USERS_INFO_0 pBuf = NULL; + LPGROUP_USERS_INFO_0 pgrpBuf = NULL; + DWORD dwLevel = 0; + DWORD dwFlags = LG_INCLUDE_INDIRECT; + DWORD dwPrefMaxLen = MAX_PREFERRED_LENGTH; + DWORD dwEntriesRead = 0; + DWORD dwTotalEntries = 0; + NET_API_STATUS nStatus; + size_t retlen; + wchar_t user[MAX_NAME_LENGTH]; + isc_result_t result; + + *totalGroups = 0; + + retlen = mbstowcs(user, username, MAX_NAME_LENGTH); + if (retlen == (size_t)(-1)) { + return (ISC_R_FAILURE); + } + + /* + * Call the NetUserGetLocalGroups function + * specifying information level 0. + * + * The LG_INCLUDE_INDIRECT flag specifies that the + * function should also return the names of the local + * groups in which the user is indirectly a member. + */ + nStatus = NetUserGetLocalGroups(NULL, user, dwLevel, dwFlags, + (LPBYTE *)&pBuf, dwPrefMaxLen, + &dwEntriesRead, &dwTotalEntries); + /* + * See if the call succeeds, + */ + if (nStatus != NERR_Success) { + if (nStatus == ERROR_ACCESS_DENIED) { + return (ISC_R_NOPERM); + } + if (nStatus == ERROR_MORE_DATA) { + return (ISC_R_NOSPACE); + } + if (nStatus == NERR_UserNotFound) { + dwEntriesRead = 0; + } + } + + if (pBuf != NULL) { + pTmpLBuf = pBuf; + /* + * Loop through the entries + */ + for (i = 0; (i < dwEntriesRead && *totalGroups < maxgroups); + i++) + { + assert(pTmpLBuf != NULL); + if (pTmpLBuf == NULL) { + break; + } + retlen = wcslen(pTmpLBuf->lgrui0_name); + GroupList[*totalGroups] = (char *)malloc(retlen + 1); + if (GroupList[*totalGroups] == NULL) { + CHECK(ISC_R_NOMEMORY); + } + + retlen = wcstombs(GroupList[*totalGroups], + pTmpLBuf->lgrui0_name, retlen); + if (retlen == (size_t)(-1)) { + free(GroupList[*totalGroups]); + CHECK(ISC_R_FAILURE); + } + GroupList[*totalGroups][retlen] = '\0'; + if (strcmp(GroupList[*totalGroups], "None") == 0) { + free(GroupList[*totalGroups]); + } else { + (*totalGroups)++; + } + pTmpLBuf++; + } + } + /* Free the allocated memory. */ + /* cppcheck-suppress duplicateCondition */ + if (pBuf != NULL) { + NetApiBufferFree(pBuf); + } + + /* + * Call the NetUserGetGroups function, specifying level 0. + */ + nStatus = NetUserGetGroups(NULL, user, dwLevel, (LPBYTE *)&pgrpBuf, + dwPrefMaxLen, &dwEntriesRead, + &dwTotalEntries); + /* + * See if the call succeeds, + */ + if (nStatus != NERR_Success) { + if (nStatus == ERROR_ACCESS_DENIED) { + return (ISC_R_NOPERM); + } + if (nStatus == ERROR_MORE_DATA) { + return (ISC_R_NOSPACE); + } + if (nStatus == NERR_UserNotFound) { + dwEntriesRead = 0; + } + } + + if (pgrpBuf != NULL) { + pTmpBuf = pgrpBuf; + /* + * Loop through the entries + */ + for (i = 0; (i < dwEntriesRead && *totalGroups < maxgroups); + i++) + { + assert(pTmpBuf != NULL); + + if (pTmpBuf == NULL) { + break; + } + retlen = wcslen(pTmpBuf->grui0_name); + GroupList[*totalGroups] = (char *)malloc(retlen + 1); + if (GroupList[*totalGroups] == NULL) { + CHECK(ISC_R_NOMEMORY); + } + + retlen = wcstombs(GroupList[*totalGroups], + pTmpBuf->grui0_name, retlen); + if (retlen == (size_t)(-1)) { + free(GroupList[*totalGroups]); + CHECK(ISC_R_FAILURE); + } + GroupList[*totalGroups][retlen] = '\0'; + if (strcmp(GroupList[*totalGroups], "None") == 0) { + free(GroupList[*totalGroups]); + } else { + (*totalGroups)++; + } + pTmpBuf++; + } + } + /* + * Free the allocated memory. + */ + /* cppcheck-suppress duplicateCondition */ + if (pgrpBuf != NULL) { + NetApiBufferFree(pgrpBuf); + } + + return (ISC_R_SUCCESS); + +cleanup: + while (--(*totalGroups) > 0) { + free(GroupList[*totalGroups]); + } + return (result); +} diff --git a/lib/isc/win32/ntpaths.c b/lib/isc/win32/ntpaths.c new file mode 100644 index 0000000..793b72a --- /dev/null +++ b/lib/isc/win32/ntpaths.c @@ -0,0 +1,151 @@ +/* + * 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. + */ + +/* + * This module fetches the required path information that is specific + * to NT systems which can have its configuration and system files + * almost anywhere. It can be used to override whatever the application + * had previously assigned to the pointer. Basic information about the + * file locations are stored in the registry. + */ + +#include <isc/bind_registry.h> +#include <isc/ntpaths.h> +#include <isc/string.h> + +/* + * Module Variables + */ + +static char systemDir[MAX_PATH]; +static char namedBase[MAX_PATH]; +static char ns_confFile[MAX_PATH]; +static char rndc_confFile[MAX_PATH]; +static char ns_defaultpidfile[MAX_PATH]; +static char ns_lockfile[MAX_PATH]; +static char local_state_dir[MAX_PATH]; +static char sys_conf_dir[MAX_PATH]; +static char rndc_keyFile[MAX_PATH]; +static char session_keyFile[MAX_PATH]; +static char resolv_confFile[MAX_PATH]; +static char bind_keysFile[MAX_PATH]; + +static DWORD baseLen = MAX_PATH; +static BOOL Initialized = FALSE; + +void +isc_ntpaths_init(void) { + HKEY hKey; + BOOL keyFound = TRUE; + + memset(namedBase, 0, sizeof(namedBase)); + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, BIND_SUBKEY, 0, KEY_READ, &hKey) != + ERROR_SUCCESS) + { + keyFound = FALSE; + } + + if (keyFound == TRUE) { + /* Get the named directory */ + if (RegQueryValueEx(hKey, "InstallDir", NULL, NULL, + (LPBYTE)namedBase, + &baseLen) != ERROR_SUCCESS) + { + keyFound = FALSE; + } + RegCloseKey(hKey); + } + + GetSystemDirectory(systemDir, MAX_PATH); + + if (keyFound == FALSE) { + /* Use the System Directory as a default */ + strlcpy(namedBase, systemDir, sizeof(namedBase)); + } + + strlcpy(ns_confFile, namedBase, sizeof(ns_confFile)); + strlcat(ns_confFile, "\\etc\\named.conf", sizeof(ns_confFile)); + + strlcpy(rndc_keyFile, namedBase, sizeof(rndc_keyFile)); + strlcat(rndc_keyFile, "\\etc\\rndc.key", sizeof(rndc_keyFile)); + + strlcpy(session_keyFile, namedBase, sizeof(session_keyFile)); + strlcat(session_keyFile, "\\etc\\session.key", sizeof(session_keyFile)); + + strlcpy(rndc_confFile, namedBase, sizeof(rndc_confFile)); + strlcat(rndc_confFile, "\\etc\\rndc.conf", sizeof(rndc_confFile)); + + strlcpy(ns_defaultpidfile, namedBase, sizeof(ns_defaultpidfile)); + strlcat(ns_defaultpidfile, "\\etc\\named.pid", + sizeof(ns_defaultpidfile)); + + strlcpy(ns_lockfile, namedBase, sizeof(ns_lockfile)); + strlcat(ns_lockfile, "\\etc\\named.lock", sizeof(ns_lockfile)); + + strlcpy(local_state_dir, namedBase, sizeof(local_state_dir)); + strlcat(local_state_dir, "\\bin", sizeof(local_state_dir)); + + strlcpy(sys_conf_dir, namedBase, sizeof(sys_conf_dir)); + strlcat(sys_conf_dir, "\\etc", sizeof(sys_conf_dir)); + + /* Added to avoid an assert on NULL value */ + strlcpy(resolv_confFile, namedBase, sizeof(resolv_confFile)); + strlcat(resolv_confFile, "\\etc\\resolv.conf", sizeof(resolv_confFile)); + + strlcpy(bind_keysFile, namedBase, sizeof(bind_keysFile)); + strlcat(bind_keysFile, "\\etc\\bind.keys", sizeof(bind_keysFile)); + + Initialized = TRUE; +} + +char * +isc_ntpaths_get(int ind) { + if (!Initialized) { + isc_ntpaths_init(); + } + + switch (ind) { + case NAMED_CONF_PATH: + return (ns_confFile); + break; + case RESOLV_CONF_PATH: + return (resolv_confFile); + break; + case RNDC_CONF_PATH: + return (rndc_confFile); + break; + case NAMED_PID_PATH: + return (ns_defaultpidfile); + break; + case NAMED_LOCK_PATH: + return (ns_lockfile); + break; + case LOCAL_STATE_DIR: + return (local_state_dir); + break; + case SYS_CONF_DIR: + return (sys_conf_dir); + break; + case RNDC_KEY_PATH: + return (rndc_keyFile); + break; + case SESSION_KEY_PATH: + return (session_keyFile); + break; + case BIND_KEYS_PATH: + return (bind_keysFile); + break; + default: + return (NULL); + } +} diff --git a/lib/isc/win32/once.c b/lib/isc/win32/once.c new file mode 100644 index 0000000..23966cb --- /dev/null +++ b/lib/isc/win32/once.c @@ -0,0 +1,43 @@ +/* + * 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. + */ + +#include <windows.h> + +#include <isc/assertions.h> +#include <isc/once.h> +#include <isc/util.h> + +isc_result_t +isc_once_do(isc_once_t *controller, void (*function)(void)) { + REQUIRE(controller != NULL && function != NULL); + + if (controller->status == ISC_ONCE_INIT_NEEDED) { + if (InterlockedDecrement(&controller->counter) == 0) { + if (controller->status == ISC_ONCE_INIT_NEEDED) { + function(); + controller->status = ISC_ONCE_INIT_DONE; + } + } else { + while (controller->status == ISC_ONCE_INIT_NEEDED) { + /* + * Sleep(0) indicates that this thread + * should be suspended to allow other + * waiting threads to execute. + */ + Sleep(0); + } + } + } + + return (ISC_R_SUCCESS); +} diff --git a/lib/isc/win32/os.c b/lib/isc/win32/os.c new file mode 100644 index 0000000..19a992e --- /dev/null +++ b/lib/isc/win32/os.c @@ -0,0 +1,41 @@ +/* + * 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. + */ + +#include <windows.h> + +#include <isc/os.h> + +static BOOL bInit = FALSE; +static SYSTEM_INFO SystemInfo; + +static void +initialize_action(void) { + if (bInit) { + return; + } + + GetSystemInfo(&SystemInfo); + bInit = TRUE; +} + +unsigned int +isc_os_ncpus(void) { + long ncpus; + initialize_action(); + ncpus = SystemInfo.dwNumberOfProcessors; + if (ncpus <= 0) { + ncpus = 1; + } + + return ((unsigned int)ncpus); +} diff --git a/lib/isc/win32/pk11_api.c b/lib/isc/win32/pk11_api.c new file mode 100644 index 0000000..5ee8e0d --- /dev/null +++ b/lib/isc/win32/pk11_api.c @@ -0,0 +1,706 @@ +/* + * 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. + */ + +/*! \file */ + +/* missing code for WIN32 */ + +#include <string.h> +#include <windows.h> + +#include <isc/log.h> +#include <isc/mem.h> +#include <isc/once.h> +#include <isc/print.h> +#include <isc/stdio.h> +#include <isc/thread.h> +#include <isc/util.h> + +#include <pk11/internal.h> +#include <pk11/pk11.h> + +char * +getpass(const char *prompt) { + static char buf[128]; + HANDLE h; + DWORD cc, mode; + int cnt; + + h = GetStdHandle(STD_INPUT_HANDLE); + fputs(prompt, stderr); + fflush(stderr); + fflush(stdout); + FlushConsoleInputBuffer(h); + GetConsoleMode(h, &mode); + SetConsoleMode(h, ENABLE_PROCESSED_INPUT); + + for (cnt = 0; cnt < sizeof(buf) - 1; cnt++) { + ReadFile(h, buf + cnt, 1, &cc, NULL); + if (buf[cnt] == '\r') { + break; + } + fputc('*', stdout); + fflush(stderr); + fflush(stdout); + } + + SetConsoleMode(h, mode); + buf[cnt] = '\0'; + fputs("\n", stderr); + return (buf); +} + +/* load PKCS11 DLL */ + +static HINSTANCE hPK11 = NULL; +static char loaderrmsg[1024]; + +CK_RV +pkcs_C_Initialize(CK_VOID_PTR pReserved) { + CK_C_Initialize sym; + const char *lib_name = pk11_get_lib_name(); + + if (hPK11 != NULL) { + return (CKR_LIBRARY_ALREADY_INITIALIZED); + } + + if (lib_name == NULL) { + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + /* Visual Studio conversion issue... */ + if (*lib_name == ' ') { + lib_name++; + } + + hPK11 = LoadLibraryA(lib_name); + + if (hPK11 == NULL) { + const DWORD err = GetLastError(); + snprintf(loaderrmsg, sizeof(loaderrmsg), + "LoadLibraryA(\"%s\") failed with 0x%X\n", lib_name, + err); + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + sym = (CK_C_Initialize)GetProcAddress(hPK11, "C_Initialize"); + if (sym == NULL) { + return (CKR_SYMBOL_RESOLUTION_FAILED); + } + return ((*sym)(pReserved)); +} + +char * +pk11_get_load_error_message(void) { + return (loaderrmsg); +} + +CK_RV +pkcs_C_Finalize(CK_VOID_PTR pReserved) { + CK_C_Finalize sym; + CK_RV rv; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + sym = (CK_C_Finalize)GetProcAddress(hPK11, "C_Finalize"); + if (sym == NULL) { + return (CKR_SYMBOL_RESOLUTION_FAILED); + } + rv = (*sym)(pReserved); + if ((rv == CKR_OK) && (FreeLibrary(hPK11) == 0)) { + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + hPK11 = NULL; + return (rv); +} + +CK_RV +pkcs_C_GetSlotList(CK_BBOOL tokenPresent, CK_SLOT_ID_PTR pSlotList, + CK_ULONG_PTR pulCount) { + static CK_C_GetSlotList sym = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + if (sym == NULL) { + sym = (CK_C_GetSlotList)GetProcAddress(hPK11, "C_GetSlotList"); + } + if (sym == NULL) { + return (CKR_SYMBOL_RESOLUTION_FAILED); + } + return ((*sym)(tokenPresent, pSlotList, pulCount)); +} + +CK_RV +pkcs_C_GetTokenInfo(CK_SLOT_ID slotID, CK_TOKEN_INFO_PTR pInfo) { + static CK_C_GetTokenInfo sym = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + if (sym == NULL) { + sym = (CK_C_GetTokenInfo)GetProcAddress(hPK11, "C_" + "GetTokenInfo"); + } + if (sym == NULL) { + return (CKR_SYMBOL_RESOLUTION_FAILED); + } + return ((*sym)(slotID, pInfo)); +} + +CK_RV +pkcs_C_GetMechanismInfo(CK_SLOT_ID slotID, CK_MECHANISM_TYPE type, + CK_MECHANISM_INFO_PTR pInfo) { + static CK_C_GetMechanismInfo sym = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + if (sym == NULL) { + sym = (CK_C_GetMechanismInfo)GetProcAddress(hPK11, "C_" + "GetMechanis" + "mInfo"); + } + if (sym == NULL) { + return (CKR_SYMBOL_RESOLUTION_FAILED); + } + return ((*sym)(slotID, type, pInfo)); +} + +CK_RV +pkcs_C_OpenSession(CK_SLOT_ID slotID, CK_FLAGS flags, CK_VOID_PTR pApplication, + CK_RV (*Notify)(CK_SESSION_HANDLE hSession, + CK_NOTIFICATION event, + CK_VOID_PTR pApplication), + CK_SESSION_HANDLE_PTR phSession) { + static CK_C_OpenSession sym = NULL; + + if (hPK11 == NULL) { + hPK11 = LoadLibraryA(pk11_get_lib_name()); + } + if (hPK11 == NULL) { + const DWORD err = GetLastError(); + snprintf(loaderrmsg, sizeof(loaderrmsg), + "LoadLibraryA(\"%s\") failed with 0x%X\n", + pk11_get_lib_name(), err); + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + if (sym == NULL) { + sym = (CK_C_OpenSession)GetProcAddress(hPK11, "C_OpenSession"); + } + if (sym == NULL) { + return (CKR_SYMBOL_RESOLUTION_FAILED); + } + return ((*sym)(slotID, flags, pApplication, Notify, phSession)); +} + +CK_RV +pkcs_C_CloseSession(CK_SESSION_HANDLE hSession) { + static CK_C_CloseSession sym = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + if (sym == NULL) { + sym = (CK_C_CloseSession)GetProcAddress(hPK11, "C_" + "CloseSession"); + } + if (sym == NULL) { + return (CKR_SYMBOL_RESOLUTION_FAILED); + } + return ((*sym)(hSession)); +} + +CK_RV +pkcs_C_Login(CK_SESSION_HANDLE hSession, CK_USER_TYPE userType, + CK_CHAR_PTR pPin, CK_ULONG usPinLen) { + static CK_C_Login sym = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + if (sym == NULL) { + sym = (CK_C_Login)GetProcAddress(hPK11, "C_Login"); + } + if (sym == NULL) { + return (CKR_SYMBOL_RESOLUTION_FAILED); + } + return ((*sym)(hSession, userType, pPin, usPinLen)); +} + +CK_RV +pkcs_C_Logout(CK_SESSION_HANDLE hSession) { + static CK_C_Logout sym = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + if (sym == NULL) { + sym = (CK_C_Logout)GetProcAddress(hPK11, "C_Logout"); + } + if (sym == NULL) { + return (CKR_SYMBOL_RESOLUTION_FAILED); + } + return ((*sym)(hSession)); +} + +CK_RV +pkcs_C_CreateObject(CK_SESSION_HANDLE hSession, CK_ATTRIBUTE_PTR pTemplate, + CK_ULONG usCount, CK_OBJECT_HANDLE_PTR phObject) { + static CK_C_CreateObject sym = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + if (sym == NULL) { + sym = (CK_C_CreateObject)GetProcAddress(hPK11, "C_" + "CreateObject"); + } + if (sym == NULL) { + return (CKR_SYMBOL_RESOLUTION_FAILED); + } + return ((*sym)(hSession, pTemplate, usCount, phObject)); +} + +CK_RV +pkcs_C_DestroyObject(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject) { + static CK_C_DestroyObject sym = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + if (sym == NULL) { + sym = (CK_C_DestroyObject)GetProcAddress(hPK11, "C_" + "DestroyObjec" + "t"); + } + if (sym == NULL) { + return (CKR_SYMBOL_RESOLUTION_FAILED); + } + return ((*sym)(hSession, hObject)); +} + +CK_RV +pkcs_C_GetAttributeValue(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject, + CK_ATTRIBUTE_PTR pTemplate, CK_ULONG usCount) { + static CK_C_GetAttributeValue sym = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + if (sym == NULL) { + sym = (CK_C_GetAttributeValue)GetProcAddress(hPK11, "C_" + "GetAttribu" + "teValue"); + } + if (sym == NULL) { + return (CKR_SYMBOL_RESOLUTION_FAILED); + } + return ((*sym)(hSession, hObject, pTemplate, usCount)); +} + +CK_RV +pkcs_C_SetAttributeValue(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject, + CK_ATTRIBUTE_PTR pTemplate, CK_ULONG usCount) { + static CK_C_SetAttributeValue sym = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + if (sym == NULL) { + sym = (CK_C_SetAttributeValue)GetProcAddress(hPK11, "C_" + "SetAttribu" + "teValue"); + } + if (sym == NULL) { + return (CKR_SYMBOL_RESOLUTION_FAILED); + } + return ((*sym)(hSession, hObject, pTemplate, usCount)); +} + +CK_RV +pkcs_C_FindObjectsInit(CK_SESSION_HANDLE hSession, CK_ATTRIBUTE_PTR pTemplate, + CK_ULONG usCount) { + static CK_C_FindObjectsInit sym = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + if (sym == NULL) { + sym = (CK_C_FindObjectsInit)GetProcAddress(hPK11, "C_" + "FindObjectsI" + "nit"); + } + if (sym == NULL) { + return (CKR_SYMBOL_RESOLUTION_FAILED); + } + return ((*sym)(hSession, pTemplate, usCount)); +} + +CK_RV +pkcs_C_FindObjects(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE_PTR phObject, + CK_ULONG usMaxObjectCount, CK_ULONG_PTR pusObjectCount) { + static CK_C_FindObjects sym = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + if (sym == NULL) { + sym = (CK_C_FindObjects)GetProcAddress(hPK11, "C_FindObjects"); + } + if (sym == NULL) { + return (CKR_SYMBOL_RESOLUTION_FAILED); + } + return ((*sym)(hSession, phObject, usMaxObjectCount, pusObjectCount)); +} + +CK_RV +pkcs_C_FindObjectsFinal(CK_SESSION_HANDLE hSession) { + static CK_C_FindObjectsFinal sym = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + if (sym == NULL) { + sym = (CK_C_FindObjectsFinal)GetProcAddress(hPK11, "C_" + "FindObjects" + "Final"); + } + if (sym == NULL) { + return (CKR_SYMBOL_RESOLUTION_FAILED); + } + return ((*sym)(hSession)); +} + +CK_RV +pkcs_C_EncryptInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, + CK_OBJECT_HANDLE hKey) { + static CK_C_EncryptInit sym = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + if (sym == NULL) { + sym = (CK_C_EncryptInit)GetProcAddress(hPK11, "C_EncryptInit"); + } + if (sym == NULL) { + return (CKR_SYMBOL_RESOLUTION_FAILED); + } + return ((*sym)(hSession, pMechanism, hKey)); +} + +CK_RV +pkcs_C_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, + CK_ULONG ulDataLen, CK_BYTE_PTR pEncryptedData, + CK_ULONG_PTR pulEncryptedDataLen) { + static CK_C_Encrypt sym = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + if (sym == NULL) { + sym = (CK_C_Encrypt)GetProcAddress(hPK11, "C_Encrypt"); + } + if (sym == NULL) { + return (CKR_SYMBOL_RESOLUTION_FAILED); + } + return ((*sym)(hSession, pData, ulDataLen, pEncryptedData, + pulEncryptedDataLen)); +} + +CK_RV +pkcs_C_DigestInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism) { + static CK_C_DigestInit sym = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + if (sym == NULL) { + sym = (CK_C_DigestInit)GetProcAddress(hPK11, "C_DigestInit"); + } + if (sym == NULL) { + return (CKR_SYMBOL_RESOLUTION_FAILED); + } + return ((*sym)(hSession, pMechanism)); +} + +CK_RV +pkcs_C_DigestUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, + CK_ULONG ulPartLen) { + static CK_C_DigestUpdate sym = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + if (sym == NULL) { + sym = (CK_C_DigestUpdate)GetProcAddress(hPK11, "C_" + "DigestUpdate"); + } + if (sym == NULL) { + return (CKR_SYMBOL_RESOLUTION_FAILED); + } + return ((*sym)(hSession, pPart, ulPartLen)); +} + +CK_RV +pkcs_C_DigestFinal(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pDigest, + CK_ULONG_PTR pulDigestLen) { + static CK_C_DigestFinal sym = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + if (sym == NULL) { + sym = (CK_C_DigestFinal)GetProcAddress(hPK11, "C_DigestFinal"); + } + if (sym == NULL) { + return (CKR_SYMBOL_RESOLUTION_FAILED); + } + return ((*sym)(hSession, pDigest, pulDigestLen)); +} + +CK_RV +pkcs_C_SignInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, + CK_OBJECT_HANDLE hKey) { + static CK_C_SignInit sym = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + if (sym == NULL) { + sym = (CK_C_SignInit)GetProcAddress(hPK11, "C_SignInit"); + } + if (sym == NULL) { + return (CKR_SYMBOL_RESOLUTION_FAILED); + } + return ((*sym)(hSession, pMechanism, hKey)); +} + +CK_RV +pkcs_C_Sign(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, CK_ULONG ulDataLen, + CK_BYTE_PTR pSignature, CK_ULONG_PTR pulSignatureLen) { + static CK_C_Sign sym = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + if (sym == NULL) { + sym = (CK_C_Sign)GetProcAddress(hPK11, "C_Sign"); + } + if (sym == NULL) { + return (CKR_SYMBOL_RESOLUTION_FAILED); + } + return ((*sym)(hSession, pData, ulDataLen, pSignature, + pulSignatureLen)); +} + +CK_RV +pkcs_C_SignUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, + CK_ULONG ulPartLen) { + static CK_C_SignUpdate sym = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + if (sym == NULL) { + sym = (CK_C_SignUpdate)GetProcAddress(hPK11, "C_SignUpdate"); + } + if (sym == NULL) { + return (CKR_SYMBOL_RESOLUTION_FAILED); + } + return ((*sym)(hSession, pPart, ulPartLen)); +} + +CK_RV +pkcs_C_SignFinal(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pSignature, + CK_ULONG_PTR pulSignatureLen) { + static CK_C_SignFinal sym = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + if (sym == NULL) { + sym = (CK_C_SignFinal)GetProcAddress(hPK11, "C_SignFinal"); + } + if (sym == NULL) { + return (CKR_SYMBOL_RESOLUTION_FAILED); + } + return ((*sym)(hSession, pSignature, pulSignatureLen)); +} + +CK_RV +pkcs_C_VerifyInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, + CK_OBJECT_HANDLE hKey) { + static CK_C_VerifyInit sym = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + if (sym == NULL) { + sym = (CK_C_VerifyInit)GetProcAddress(hPK11, "C_VerifyInit"); + } + if (sym == NULL) { + return (CKR_SYMBOL_RESOLUTION_FAILED); + } + return ((*sym)(hSession, pMechanism, hKey)); +} + +CK_RV +pkcs_C_Verify(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData, CK_ULONG ulDataLen, + CK_BYTE_PTR pSignature, CK_ULONG ulSignatureLen) { + static CK_C_Verify sym = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + if (sym == NULL) { + sym = (CK_C_Verify)GetProcAddress(hPK11, "C_Verify"); + } + if (sym == NULL) { + return (CKR_SYMBOL_RESOLUTION_FAILED); + } + return ((*sym)(hSession, pData, ulDataLen, pSignature, ulSignatureLen)); +} + +CK_RV +pkcs_C_VerifyUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart, + CK_ULONG ulPartLen) { + static CK_C_VerifyUpdate sym = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + if (sym == NULL) { + sym = (CK_C_VerifyUpdate)GetProcAddress(hPK11, "C_" + "VerifyUpdate"); + } + if (sym == NULL) { + return (CKR_SYMBOL_RESOLUTION_FAILED); + } + return ((*sym)(hSession, pPart, ulPartLen)); +} + +CK_RV +pkcs_C_VerifyFinal(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pSignature, + CK_ULONG ulSignatureLen) { + static CK_C_VerifyFinal sym = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + if (sym == NULL) { + sym = (CK_C_VerifyFinal)GetProcAddress(hPK11, "C_VerifyFinal"); + } + if (sym == NULL) { + return (CKR_SYMBOL_RESOLUTION_FAILED); + } + return ((*sym)(hSession, pSignature, ulSignatureLen)); +} + +CK_RV +pkcs_C_GenerateKey(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, + CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount, + CK_OBJECT_HANDLE_PTR phKey) { + static CK_C_GenerateKey sym = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + if (sym == NULL) { + sym = (CK_C_GenerateKey)GetProcAddress(hPK11, "C_GenerateKey"); + } + if (sym == NULL) { + return (CKR_SYMBOL_RESOLUTION_FAILED); + } + return ((*sym)(hSession, pMechanism, pTemplate, ulCount, phKey)); +} + +CK_RV +pkcs_C_GenerateKeyPair(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, + CK_ATTRIBUTE_PTR pPublicKeyTemplate, + CK_ULONG usPublicKeyAttributeCount, + CK_ATTRIBUTE_PTR pPrivateKeyTemplate, + CK_ULONG usPrivateKeyAttributeCount, + CK_OBJECT_HANDLE_PTR phPrivateKey, + CK_OBJECT_HANDLE_PTR phPublicKey) { + static CK_C_GenerateKeyPair sym = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + if (sym == NULL) { + sym = (CK_C_GenerateKeyPair)GetProcAddress(hPK11, "C_" + "GenerateKeyP" + "air"); + } + if (sym == NULL) { + return (CKR_SYMBOL_RESOLUTION_FAILED); + } + return ((*sym)(hSession, pMechanism, pPublicKeyTemplate, + usPublicKeyAttributeCount, pPrivateKeyTemplate, + usPrivateKeyAttributeCount, phPrivateKey, phPublicKey)); +} + +CK_RV +pkcs_C_DeriveKey(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism, + CK_OBJECT_HANDLE hBaseKey, CK_ATTRIBUTE_PTR pTemplate, + CK_ULONG ulAttributeCount, CK_OBJECT_HANDLE_PTR phKey) { + static CK_C_DeriveKey sym = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + if (sym == NULL) { + sym = (CK_C_DeriveKey)GetProcAddress(hPK11, "C_DeriveKey"); + } + if (sym == NULL) { + return (CKR_SYMBOL_RESOLUTION_FAILED); + } + return ((*sym)(hSession, pMechanism, hBaseKey, pTemplate, + ulAttributeCount, phKey)); +} + +CK_RV +pkcs_C_SeedRandom(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pSeed, + CK_ULONG ulSeedLen) { + static CK_C_SeedRandom sym = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + if (sym == NULL) { + sym = (CK_C_SeedRandom)GetProcAddress(hPK11, "C_SeedRandom"); + } + if (sym == NULL) { + return (CKR_SYMBOL_RESOLUTION_FAILED); + } + return ((*sym)(hSession, pSeed, ulSeedLen)); +} + +CK_RV +pkcs_C_GenerateRandom(CK_SESSION_HANDLE hSession, CK_BYTE_PTR RandomData, + CK_ULONG ulRandomLen) { + static CK_C_GenerateRandom sym = NULL; + + if (hPK11 == NULL) { + return (CKR_LIBRARY_FAILED_TO_LOAD); + } + if (sym == NULL) { + sym = (CK_C_GenerateRandom)GetProcAddress(hPK11, "C_" + "GenerateRando" + "m"); + } + if (sym == NULL) { + return (CKR_SYMBOL_RESOLUTION_FAILED); + } + return ((*sym)(hSession, RandomData, ulRandomLen)); +} diff --git a/lib/isc/win32/resource.c b/lib/isc/win32/resource.c new file mode 100644 index 0000000..b44b322 --- /dev/null +++ b/lib/isc/win32/resource.c @@ -0,0 +1,66 @@ +/* + * 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. + */ + +#include <stdio.h> + +#include <isc/platform.h> +#include <isc/resource.h> +#include <isc/result.h> +#include <isc/util.h> + +#include "errno2result.h" + +/* + * Windows limits the maximum number of open files to 2048 + */ + +#define WIN32_MAX_OPEN_FILES 2048 + +isc_result_t +isc_resource_setlimit(isc_resource_t resource, isc_resourcevalue_t value) { + isc_resourcevalue_t rlim_value; + int wresult; + + if (resource != isc_resource_openfiles) { + return (ISC_R_NOTIMPLEMENTED); + } + + if (value == ISC_RESOURCE_UNLIMITED) { + rlim_value = WIN32_MAX_OPEN_FILES; + } else { + rlim_value = min(value, WIN32_MAX_OPEN_FILES); + } + + wresult = _setmaxstdio((int)rlim_value); + + if (wresult > 0) { + return (ISC_R_SUCCESS); + } else { + return (isc__errno2result(errno)); + } +} + +isc_result_t +isc_resource_getlimit(isc_resource_t resource, isc_resourcevalue_t *value) { + if (resource != isc_resource_openfiles) { + return (ISC_R_NOTIMPLEMENTED); + } + + *value = WIN32_MAX_OPEN_FILES; + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_resource_getcurlimit(isc_resource_t resource, isc_resourcevalue_t *value) { + return (isc_resource_getlimit(resource, value)); +} diff --git a/lib/isc/win32/socket.c b/lib/isc/win32/socket.c new file mode 100644 index 0000000..c154175 --- /dev/null +++ b/lib/isc/win32/socket.c @@ -0,0 +1,3965 @@ +/* + * 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. + */ + +/* This code uses functions which are only available on Server 2003 and + * higher, and Windows XP and higher. + * + * This code is by nature multithreaded and takes advantage of various + * features to pass on information through the completion port for + * when I/O is completed. All sends, receives, accepts, and connects are + * completed through the completion port. + * + * The number of Completion Port Worker threads used is the total number + * of CPU's + 1. This increases the likelihood that a Worker Thread is + * available for processing a completed request. + * + * XXXPDM 5 August, 2002 + */ + +#define MAKE_EXTERNAL 1 + +#include <sys/types.h> + +#ifndef _WINSOCKAPI_ +#define _WINSOCKAPI_ /* Prevent inclusion of winsock.h in windows.h */ +#endif /* ifndef _WINSOCKAPI_ */ + +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <io.h> +#include <process.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <isc/app.h> +#include <isc/buffer.h> +#include <isc/condition.h> +#include <isc/list.h> +#include <isc/log.h> +#include <isc/mem.h> +#include <isc/mutex.h> +#include <isc/net.h> +#include <isc/once.h> +#include <isc/os.h> +#include <isc/platform.h> +#include <isc/print.h> +#include <isc/refcount.h> +#include <isc/region.h> +#include <isc/socket.h> +#include <isc/stats.h> +#include <isc/strerr.h> +#include <isc/string.h> +#include <isc/syslog.h> +#include <isc/task.h> +#include <isc/thread.h> +#include <isc/util.h> +#include <isc/win32os.h> + +/* clang-format off */ +/* U Can't Touch This */ +#include <mswsock.h> +/* clang-format on */ + +#ifdef HAVE_JSON_C +#include <json_object.h> +#endif /* HAVE_JSON_C */ + +#ifdef HAVE_LIBXML2 +#include <libxml/xmlwriter.h> +#define ISC_XMLCHAR (const xmlChar *) +#endif /* HAVE_LIBXML2 */ + +#include "errno2result.h" + +/* + * Set by the -T dscp option on the command line. If set to a value + * other than -1, we check to make sure DSCP values match it, and + * assert if not. + */ +LIBISC_EXTERNAL_DATA int isc_dscp_check_value = -1; + +/* + * How in the world can Microsoft exist with APIs like this? + * We can't actually call this directly, because it turns out + * no library exports this function. Instead, we need to + * issue a runtime call to get the address. + */ +LPFN_CONNECTEX ISCConnectEx; +LPFN_ACCEPTEX ISCAcceptEx; +LPFN_GETACCEPTEXSOCKADDRS ISCGetAcceptExSockaddrs; + +/* + * Run expensive internal consistency checks. + */ +#ifdef ISC_SOCKET_CONSISTENCY_CHECKS +#define CONSISTENT(sock) consistent(sock) +#else /* ifdef ISC_SOCKET_CONSISTENCY_CHECKS */ +#define CONSISTENT(sock) \ + do { \ + } while (0) +#endif /* ifdef ISC_SOCKET_CONSISTENCY_CHECKS */ +static void +consistent(isc_socket_t *sock); + +/* + * Define this macro to control the behavior of connection + * resets on UDP sockets. See Microsoft KnowledgeBase Article Q263823 + * for details. + * NOTE: This requires that Windows 2000 systems install Service Pack 2 + * or later. + */ +#ifndef SIO_UDP_CONNRESET +#define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR, 12) +#endif /* ifndef SIO_UDP_CONNRESET */ + +/* + * Define what the possible "soft" errors can be. These are non-fatal returns + * of various network related functions, like recv() and so on. + */ +#define SOFT_ERROR(e) \ + ((e) == WSAEINTR || (e) == WSAEWOULDBLOCK || (e) == EWOULDBLOCK || \ + (e) == EINTR || (e) == EAGAIN || (e) == 0) + +/* + * Pending errors are not really errors and should be + * kept separate + */ +#define PENDING_ERROR(e) ((e) == WSA_IO_PENDING || (e) == 0) + +#define DOIO_SUCCESS 0 /* i/o ok, event sent */ +#define DOIO_SOFT 1 /* i/o ok, soft error, no event sent */ +#define DOIO_HARD 2 /* i/o error, event sent */ +#define DOIO_EOF 3 /* EOF, no event sent */ +#define DOIO_PENDING 4 /* status when i/o is in process */ +#define DOIO_NEEDMORE \ + 5 /* IO was processed, but we need more due to minimum \ + */ + +#define DLVL(x) ISC_LOGCATEGORY_GENERAL, ISC_LOGMODULE_SOCKET, ISC_LOG_DEBUG(x) + +/* + * DLVL(90) -- Function entry/exit and other tracing. + * DLVL(70) -- Socket "correctness" -- including returning of events, etc. + * DLVL(60) -- Socket data send/receive + * DLVL(50) -- Event tracing, including receiving/sending completion events. + * DLVL(20) -- Socket creation/destruction. + */ +#define TRACE_LEVEL 90 +#define CORRECTNESS_LEVEL 70 +#define IOEVENT_LEVEL 60 +#define EVENT_LEVEL 50 +#define CREATION_LEVEL 20 + +#define TRACE DLVL(TRACE_LEVEL) +#define CORRECTNESS DLVL(CORRECTNESS_LEVEL) +#define IOEVENT DLVL(IOEVENT_LEVEL) +#define EVENT DLVL(EVENT_LEVEL) +#define CREATION DLVL(CREATION_LEVEL) + +typedef isc_event_t intev_t; + +/* + * Socket State + */ +enum { + SOCK_INITIALIZED, /* Socket Initialized */ + SOCK_OPEN, /* Socket opened but nothing yet to do */ + SOCK_DATA, /* Socket sending or receiving data */ + SOCK_LISTEN, /* TCP Socket listening for connects */ + SOCK_ACCEPT, /* TCP socket is waiting to accept */ + SOCK_CONNECT, /* TCP Socket connecting */ + SOCK_CLOSED, /* Socket has been closed */ +}; + +#define SOCKET_MAGIC ISC_MAGIC('I', 'O', 'i', 'o') +#define VALID_SOCKET(t) ISC_MAGIC_VALID(t, SOCKET_MAGIC) + +/* + * IPv6 control information. If the socket is an IPv6 socket we want + * to collect the destination address and interface so the client can + * set them on outgoing packets. + */ +#ifndef USE_CMSG +#define USE_CMSG 1 +#endif /* ifndef USE_CMSG */ + +/* + * We really don't want to try and use these control messages. Win32 + * doesn't have this mechanism before XP. + */ +#undef USE_CMSG + +/* + * Message header for recvmsg and sendmsg calls. + * Used value-result for recvmsg, value only for sendmsg. + */ +struct msghdr { + SOCKADDR_STORAGE to_addr; /* UDP send/recv address */ + int to_addr_len; /* length of the address */ + WSABUF *msg_iov; /* scatter/gather array */ + u_int msg_iovlen; /* # elements in msg_iov */ + void *msg_control; /* ancillary data, see below */ + u_int msg_controllen; /* ancillary data buffer len */ + u_int msg_totallen; /* total length of this message */ +} msghdr; + +/* + * The size to raise the receive buffer to. + */ +#define RCVBUFSIZE (32 * 1024) + +/* + * The number of times a send operation is repeated if the result + * is WSAEINTR. + */ +#define NRETRIES 10 + +struct isc_socket { + /* Not locked. */ + unsigned int magic; + isc_socketmgr_t *manager; + isc_mutex_t lock; + isc_sockettype_t type; + + /* Pointers to scatter/gather buffers */ + WSABUF iov[ISC_SOCKET_MAXSCATTERGATHER]; + + /* Locked by socket lock. */ + ISC_LINK(isc_socket_t) link; + isc_refcount_t references; /* EXTERNAL references */ + SOCKET fd; /* file handle */ + int pf; /* protocol family */ + char name[16]; + void *tag; + + /* + * Each recv() call uses this buffer. It is a per-socket receive + * buffer that allows us to decouple the system recv() from the + * recv_list done events. This means the items on the recv_list + * can be removed without having to cancel pending system recv() + * calls. It also allows us to read-ahead in some cases. + */ + struct { + SOCKADDR_STORAGE from_addr; /* UDP send/recv address */ + int from_addr_len; /* length of the address */ + char *base; /* the base of the buffer */ + char *consume_position; /* where to start + * copying data from + * next */ + unsigned int len; /* the actual size of this buffer */ + unsigned int remaining; /* the number of bytes + * remaining */ + } recvbuf; + + ISC_LIST(isc_socketevent_t) send_list; + ISC_LIST(isc_socketevent_t) recv_list; + ISC_LIST(isc_socket_newconnev_t) accept_list; + ISC_LIST(isc_socket_connev_t) connect_list; + + isc_sockaddr_t address; /* remote address */ + + unsigned int listener : 1, /* listener socket */ + connected : 1, pending_connect : 1, /* connect + * pending */ + bound : 1, /* bound to local addr */ + dupped : 1; /* created by isc_socket_dup() */ + unsigned int pending_iocp; /* Should equal the counters below. + * Debug. */ + unsigned int pending_recv; /* Number of outstanding recv() calls. + * */ + unsigned int pending_send; /* Number of outstanding send() calls. + * */ + unsigned int pending_accept; /* Number of outstanding accept() + * calls. */ + unsigned int state; /* Socket state. Debugging and consistency + * checking. + */ + int state_lineno; /* line which last touched state */ +}; + +#define _set_state(sock, _state) \ + do { \ + (sock)->state = (_state); \ + (sock)->state_lineno = __LINE__; \ + } while (0) + +/* + * I/O Completion ports Info structures + */ + +static HANDLE hHeapHandle = NULL; +typedef struct IoCompletionInfo { + OVERLAPPED overlapped; + isc_socketevent_t *dev; /* send()/recv() done event */ + isc_socket_connev_t *cdev; /* connect() done event */ + isc_socket_newconnev_t *adev; /* accept() done event */ + void *acceptbuffer; + DWORD received_bytes; + int request_type; + struct msghdr messagehdr; + void *buf; + unsigned int buflen; +} IoCompletionInfo; + +/* + * Define a maximum number of I/O Completion Port worker threads + * to handle the load on the Completion Port. The actual number + * used is the number of CPU's + 1. + */ +#define MAX_IOCPTHREADS 20 + +#define SOCKET_MANAGER_MAGIC ISC_MAGIC('I', 'O', 'm', 'g') +#define VALID_MANAGER(m) ISC_MAGIC_VALID(m, SOCKET_MANAGER_MAGIC) + +struct isc_socketmgr { + /* Not locked. */ + unsigned int magic; + isc_mem_t *mctx; + isc_mutex_t lock; + isc_stats_t *stats; + + /* Locked by manager lock. */ + ISC_LIST(isc_socket_t) socklist; + bool bShutdown; + isc_condition_t shutdown_ok; + HANDLE hIoCompletionPort; + int maxIOCPThreads; + HANDLE hIOCPThreads[MAX_IOCPTHREADS]; + size_t maxudp; + + /* + * Debugging. + * Modified by InterlockedIncrement() and InterlockedDecrement() + */ + LONG totalSockets; + LONG iocp_total; +}; + +enum { SOCKET_RECV, SOCKET_SEND, SOCKET_ACCEPT, SOCKET_CONNECT }; + +/* + * send() and recv() iovec counts + */ +#define MAXSCATTERGATHER_SEND (ISC_SOCKET_MAXSCATTERGATHER) +#define MAXSCATTERGATHER_RECV (ISC_SOCKET_MAXSCATTERGATHER) + +static isc_result_t +socket_create(isc_socketmgr_t *manager0, int pf, isc_sockettype_t type, + isc_socket_t **socketp, isc_socket_t *dup_socket); +static isc_threadresult_t WINAPI +SocketIoThread(LPVOID ThreadContext); +static void +maybe_free_socket(isc_socket_t **, int); +static void +free_socket(isc_socket_t **, int); +static bool +senddone_is_active(isc_socket_t *sock, isc_socketevent_t *dev); +static bool +acceptdone_is_active(isc_socket_t *sock, isc_socket_newconnev_t *dev); +static bool +connectdone_is_active(isc_socket_t *sock, isc_socket_connev_t *dev); +static void +send_recvdone_event(isc_socket_t *sock, isc_socketevent_t **dev); +static void +send_senddone_event(isc_socket_t *sock, isc_socketevent_t **dev); +static void +send_acceptdone_event(isc_socket_t *sock, isc_socket_newconnev_t **adev); +static void +send_connectdone_event(isc_socket_t *sock, isc_socket_connev_t **cdev); +static void +send_recvdone_abort(isc_socket_t *sock, isc_result_t result); +static void +send_connectdone_abort(isc_socket_t *sock, isc_result_t result); +static void +queue_receive_event(isc_socket_t *sock, isc_task_t *task, + isc_socketevent_t *dev); +static void +queue_receive_request(isc_socket_t *sock); + +/* + * This is used to dump the contents of the sock structure + * You should make sure that the sock is locked before + * dumping it. Since the code uses simple printf() statements + * it should only be used interactively. + */ +void +sock_dump(isc_socket_t *sock) { + isc_socketevent_t *ldev; + isc_socket_newconnev_t *ndev; + isc_socket_connev_t *cdev; + +#if 0 + isc_sockaddr_t addr; + char socktext[ISC_SOCKADDR_FORMATSIZE]; + isc_result_t result; + + result = isc_socket_getpeername(sock,&addr); + if (result == ISC_R_SUCCESS) { + isc_sockaddr_format(&addr,socktext,sizeof(socktext)); + printf("Remote Socket: %s\n",socktext); + } + result = isc_socket_getsockname(sock,&addr); + if (result == ISC_R_SUCCESS) { + isc_sockaddr_format(&addr,socktext,sizeof(socktext)); + printf("This Socket: %s\n",socktext); + } +#endif /* if 0 */ + + printf("\n\t\tSock Dump\n"); + printf("\t\tfd: %Iu\n", sock->fd); + printf("\t\treferences: %" PRIuFAST32 "\n", + isc_refcount_current(&sock->references)); + printf("\t\tpending_accept: %u\n", sock->pending_accept); + printf("\t\tconnecting: %u\n", sock->pending_connect); + printf("\t\tconnected: %u\n", sock->connected); + printf("\t\tbound: %u\n", sock->bound); + printf("\t\tpending_iocp: %u\n", sock->pending_iocp); + printf("\t\tsocket type: %d\n", sock->type); + + printf("\n\t\tSock Recv List\n"); + ldev = ISC_LIST_HEAD(sock->recv_list); + while (ldev != NULL) { + printf("\t\tdev: %p\n", ldev); + ldev = ISC_LIST_NEXT(ldev, ev_link); + } + + printf("\n\t\tSock Send List\n"); + ldev = ISC_LIST_HEAD(sock->send_list); + while (ldev != NULL) { + printf("\t\tdev: %p\n", ldev); + ldev = ISC_LIST_NEXT(ldev, ev_link); + } + + printf("\n\t\tSock Accept List\n"); + ndev = ISC_LIST_HEAD(sock->accept_list); + while (ndev != NULL) { + printf("\t\tdev: %p\n", ldev); + ndev = ISC_LIST_NEXT(ndev, ev_link); + } + + printf("\n\t\tSock Connect List\n"); + cdev = ISC_LIST_HEAD(sock->connect_list); + while (cdev != NULL) { + printf("\t\tdev: %p\n", cdev); + cdev = ISC_LIST_NEXT(cdev, ev_link); + } +} + +static void +socket_log(int lineno, isc_socket_t *sock, const isc_sockaddr_t *address, + isc_logcategory_t *category, isc_logmodule_t *module, int level, + const char *fmt, ...) ISC_FORMAT_PRINTF(10, 11); + +/* This function will add an entry to the I/O completion port + * that will signal the I/O thread to exit (gracefully) + */ +static void +signal_iocompletionport_exit(isc_socketmgr_t *manager) { + int i; + int errval; + char strbuf[ISC_STRERRORSIZE]; + + REQUIRE(VALID_MANAGER(manager)); + for (i = 0; i < manager->maxIOCPThreads; i++) { + if (!PostQueuedCompletionStatus(manager->hIoCompletionPort, 0, + 0, 0)) + { + errval = GetLastError(); + strerror_r(errval, strbuf, sizeof(strbuf)); + FATAL_ERROR(__FILE__, __LINE__, + "Can't request service thread to exit: %s", + strbuf); + } + } +} + +/* + * Create the worker threads for the I/O Completion Port + */ +void +iocompletionport_createthreads(int total_threads, isc_socketmgr_t *manager) { + int errval; + char strbuf[ISC_STRERRORSIZE]; + int i; + + INSIST(total_threads > 0); + REQUIRE(VALID_MANAGER(manager)); + /* + * We need at least one + */ + for (i = 0; i < total_threads; i++) { + isc_thread_create(SocketIoThread, manager, + &manager->hIOCPThreads[i]); + } +} + +/* + * Create/initialise the I/O completion port + */ +void +iocompletionport_init(isc_socketmgr_t *manager) { + int errval; + char strbuf[ISC_STRERRORSIZE]; + + REQUIRE(VALID_MANAGER(manager)); + /* + * Create a private heap to handle the socket overlapped structure + * The minimum number of structures is 10, there is no maximum + */ + hHeapHandle = HeapCreate(0, 10 * sizeof(IoCompletionInfo), 0); + if (hHeapHandle == NULL) { + errval = GetLastError(); + strerror_r(errval, strbuf, sizeof(strbuf)); + FATAL_ERROR(__FILE__, __LINE__, + "HeapCreate() failed during initialization: %s", + strbuf); + } + + /* Now Create the Completion Port */ + manager->hIoCompletionPort = CreateIoCompletionPort( + INVALID_HANDLE_VALUE, NULL, 0, manager->maxIOCPThreads); + if (manager->hIoCompletionPort == NULL) { + errval = GetLastError(); + strerror_r(errval, strbuf, sizeof(strbuf)); + FATAL_ERROR(__FILE__, __LINE__, + "CreateIoCompletionPort() failed during " + "initialization: %s", + strbuf); + } + + /* + * Worker threads for servicing the I/O + */ + iocompletionport_createthreads(manager->maxIOCPThreads, manager); +} + +/* + * Associate a socket with an IO Completion Port. This allows us to queue + * events for it and have our worker pool of threads process them. + */ +void +iocompletionport_update(isc_socket_t *sock) { + HANDLE hiocp; + char strbuf[ISC_STRERRORSIZE]; + + REQUIRE(VALID_SOCKET(sock)); + + hiocp = CreateIoCompletionPort((HANDLE)sock->fd, + sock->manager->hIoCompletionPort, + (ULONG_PTR)sock, 0); + + if (hiocp == NULL) { + DWORD errval = GetLastError(); + strerror_r(errval, strbuf, sizeof(strbuf)); + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, + ISC_LOGMODULE_SOCKET, ISC_LOG_ERROR, + "iocompletionport_update: failed to open io " + "completion port: %s", + strbuf); + + /* XXXMLG temporary hack to make failures detected. + * This function should return errors to the caller, not + * exit here. + */ + FATAL_ERROR(__FILE__, __LINE__, + "CreateIoCompletionPort() failed during " + "initialization: %s", + strbuf); + } + + InterlockedIncrement(&sock->manager->iocp_total); +} + +/* + * Routine to cleanup and then close the socket. + * Only close the socket here if it is NOT associated + * with an event, otherwise the WSAWaitForMultipleEvents + * may fail due to the fact that the Wait should not + * be running while closing an event or a socket. + * The socket is locked before calling this function + */ +void +socket_close(isc_socket_t *sock) { + REQUIRE(sock != NULL); + + if (sock->fd != INVALID_SOCKET) { + closesocket(sock->fd); + sock->fd = INVALID_SOCKET; + _set_state(sock, SOCK_CLOSED); + InterlockedDecrement(&sock->manager->totalSockets); + } +} + +static isc_once_t initialise_once = ISC_ONCE_INIT; +static bool initialised = false; + +static void +initialise(void) { + WORD wVersionRequested; + WSADATA wsaData; + int err; + SOCKET sock; + GUID GUIDConnectEx = WSAID_CONNECTEX; + GUID GUIDAcceptEx = WSAID_ACCEPTEX; + GUID GUIDGetAcceptExSockaddrs = WSAID_GETACCEPTEXSOCKADDRS; + DWORD dwBytes; + + /* Need Winsock 2.2 or better */ + wVersionRequested = MAKEWORD(2, 2); + + err = WSAStartup(wVersionRequested, &wsaData); + if (err != 0) { + char strbuf[ISC_STRERRORSIZE]; + strerror_r(err, strbuf, sizeof(strbuf)); + FATAL_ERROR(__FILE__, __LINE__, "WSAStartup() failed: %s", + strbuf); + } + /* + * The following APIs do not exist as functions in a library, but + * we must ask winsock for them. They are "extensions" -- but why + * they cannot be actual functions is beyond me. So, ask winsock + * for the pointers to the functions we need. + */ + sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + INSIST(sock != INVALID_SOCKET); + err = WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &GUIDConnectEx, + sizeof(GUIDConnectEx), &ISCConnectEx, + sizeof(ISCConnectEx), &dwBytes, NULL, NULL); + INSIST(err == 0); + + err = WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, &GUIDAcceptEx, + sizeof(GUIDAcceptEx), &ISCAcceptEx, sizeof(ISCAcceptEx), + &dwBytes, NULL, NULL); + INSIST(err == 0); + + err = WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER, + &GUIDGetAcceptExSockaddrs, + sizeof(GUIDGetAcceptExSockaddrs), + &ISCGetAcceptExSockaddrs, + sizeof(ISCGetAcceptExSockaddrs), &dwBytes, NULL, NULL); + INSIST(err == 0); + + closesocket(sock); + + initialised = true; +} + +/* + * Initialize socket services + */ +void +InitSockets(void) { + RUNTIME_CHECK(isc_once_do(&initialise_once, initialise) == + ISC_R_SUCCESS); + if (!initialised) { + exit(1); + } +} + +int +internal_sendmsg(isc_socket_t *sock, IoCompletionInfo *lpo, + struct msghdr *messagehdr, int flags, int *Error) { + int Result; + DWORD BytesSent; + DWORD Flags = flags; + int total_sent; + + *Error = 0; + Result = WSASendTo(sock->fd, messagehdr->msg_iov, + messagehdr->msg_iovlen, &BytesSent, Flags, + (SOCKADDR *)&messagehdr->to_addr, + messagehdr->to_addr_len, (LPWSAOVERLAPPED)lpo, NULL); + + total_sent = (int)BytesSent; + + /* Check for errors.*/ + if (Result == SOCKET_ERROR) { + *Error = WSAGetLastError(); + + switch (*Error) { + case WSA_IO_INCOMPLETE: + case WSA_WAIT_IO_COMPLETION: + case WSA_IO_PENDING: + case NO_ERROR: /* Strange, but okay */ + sock->pending_iocp++; + sock->pending_send++; + break; + + default: + return (-1); + break; + } + } else { + sock->pending_iocp++; + sock->pending_send++; + } + + if (lpo != NULL) { + return (0); + } else { + return (total_sent); + } +} + +static void +queue_receive_request(isc_socket_t *sock) { + DWORD Flags = 0; + DWORD NumBytes = 0; + int Result; + int Error; + int need_retry; + WSABUF iov[1]; + IoCompletionInfo *lpo = NULL; + isc_result_t isc_result; + +retry: + need_retry = false; + + /* + * If we already have a receive pending, do nothing. + */ + if (sock->pending_recv > 0) { + if (lpo != NULL) { + HeapFree(hHeapHandle, 0, lpo); + } + return; + } + + /* + * If no one is waiting, do nothing. + */ + if (ISC_LIST_EMPTY(sock->recv_list)) { + if (lpo != NULL) { + HeapFree(hHeapHandle, 0, lpo); + } + return; + } + + INSIST(sock->recvbuf.remaining == 0); + INSIST(sock->fd != INVALID_SOCKET); + + iov[0].len = sock->recvbuf.len; + iov[0].buf = sock->recvbuf.base; + + if (lpo == NULL) { + lpo = (IoCompletionInfo *)HeapAlloc(hHeapHandle, + HEAP_ZERO_MEMORY, + sizeof(IoCompletionInfo)); + RUNTIME_CHECK(lpo != NULL); + } else { + ZeroMemory(lpo, sizeof(IoCompletionInfo)); + } + lpo->request_type = SOCKET_RECV; + + sock->recvbuf.from_addr_len = sizeof(sock->recvbuf.from_addr); + + Error = 0; + Result = WSARecvFrom((SOCKET)sock->fd, iov, 1, &NumBytes, &Flags, + (SOCKADDR *)&sock->recvbuf.from_addr, + &sock->recvbuf.from_addr_len, (LPWSAOVERLAPPED)lpo, + NULL); + + /* Check for errors. */ + if (Result == SOCKET_ERROR) { + Error = WSAGetLastError(); + + switch (Error) { + case WSA_IO_PENDING: + sock->pending_iocp++; + sock->pending_recv++; + break; + + /* direct error: no completion event */ + case ERROR_HOST_UNREACHABLE: + case WSAENETRESET: + case WSAECONNRESET: + if (!sock->connected) { + /* soft error */ + need_retry = true; + break; + } + FALLTHROUGH; + + default: + isc_result = isc__errno2result(Error); + if (isc_result == ISC_R_UNEXPECTED) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "WSARecvFrom: Windows error " + "code: %d, isc result %d", + Error, isc_result); + } + send_recvdone_abort(sock, isc_result); + HeapFree(hHeapHandle, 0, lpo); + lpo = NULL; + break; + } + } else { + /* + * The recv() finished immediately, but we will still get + * a completion event. Rather than duplicate code, let + * that thread handle sending the data along its way. + */ + sock->pending_iocp++; + sock->pending_recv++; + } + + socket_log(__LINE__, sock, NULL, IOEVENT, + "queue_io_request: fd %d result %d error %d", sock->fd, + Result, Error); + + CONSISTENT(sock); + + if (need_retry) { + goto retry; + } +} + +static void +manager_log(isc_socketmgr_t *sockmgr, isc_logcategory_t *category, + isc_logmodule_t *module, int level, const char *fmt, ...) { + char msgbuf[2048]; + va_list ap; + + if (!isc_log_wouldlog(isc_lctx, level)) { + return; + } + + va_start(ap, fmt); + vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap); + va_end(ap); + + isc_log_write(isc_lctx, category, module, level, "sockmgr %p: %s", + sockmgr, msgbuf); +} + +static void +socket_log(int lineno, isc_socket_t *sock, const isc_sockaddr_t *address, + isc_logcategory_t *category, isc_logmodule_t *module, int level, + const char *fmt, ...) { + char msgbuf[2048]; + char peerbuf[256]; + va_list ap; + + if (!isc_log_wouldlog(isc_lctx, level)) { + return; + } + + va_start(ap, fmt); + vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap); + va_end(ap); + + if (address == NULL) { + isc_log_write(isc_lctx, category, module, level, + "socket %p line %d: %s", sock, lineno, msgbuf); + } else { + isc_sockaddr_format(address, peerbuf, sizeof(peerbuf)); + isc_log_write(isc_lctx, category, module, level, + "socket %p line %d %s: %s", sock, lineno, peerbuf, + msgbuf); + } +} + +/* + * Make an fd SOCKET non-blocking. + */ +static isc_result_t +make_nonblock(SOCKET fd) { + int ret; + unsigned long flags = 1; + char strbuf[ISC_STRERRORSIZE]; + + /* Set the socket to non-blocking */ + ret = ioctlsocket(fd, FIONBIO, &flags); + + if (ret == -1) { + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "ioctlsocket(%d, FIOBIO, %d): %s", fd, flags, + strbuf); + + return (ISC_R_UNEXPECTED); + } + + return (ISC_R_SUCCESS); +} + +/* + * Windows 2000 systems incorrectly cause UDP sockets using WSARecvFrom + * to not work correctly, returning a WSACONNRESET error when a WSASendTo + * fails with an "ICMP port unreachable" response and preventing the + * socket from using the WSARecvFrom in subsequent operations. + * The function below fixes this, but requires that Windows 2000 + * Service Pack 2 or later be installed on the system. NT 4.0 + * systems are not affected by this and work correctly. + * See Microsoft Knowledge Base Article Q263823 for details of this. + */ +isc_result_t +connection_reset_fix(SOCKET fd) { + DWORD dwBytesReturned = 0; + BOOL bNewBehavior = FALSE; + DWORD status; + + if (isc_win32os_versioncheck(5, 0, 0, 0) < 0) { + return (ISC_R_SUCCESS); /* NT 4.0 has no problem */ + } + /* disable bad behavior using IOCTL: SIO_UDP_CONNRESET */ + status = WSAIoctl(fd, SIO_UDP_CONNRESET, &bNewBehavior, + sizeof(bNewBehavior), NULL, 0, &dwBytesReturned, NULL, + NULL); + if (status != SOCKET_ERROR) { + return (ISC_R_SUCCESS); + } else { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "WSAIoctl(SIO_UDP_CONNRESET, oldBehaviour) " + "failed"); + return (ISC_R_UNEXPECTED); + } +} + +/* + * Construct an iov array and attach it to the msghdr passed in. This is + * the SEND constructor, which will use the used region of the buffer + * (if using a buffer list) or will use the internal region (if a single + * buffer I/O is requested). + * + * Nothing can be NULL, and the done event must list at least one buffer + * on the buffer linked list for this function to be meaningful. + */ +static void +build_msghdr_send(isc_socket_t *sock, isc_socketevent_t *dev, + struct msghdr *msg, char *cmsg, WSABUF *iov, + IoCompletionInfo *lpo) { + unsigned int iovcount; + size_t write_count; + + memset(msg, 0, sizeof(*msg)); + + memmove(&msg->to_addr, &dev->address.type, dev->address.length); + msg->to_addr_len = dev->address.length; + + write_count = 0; + iovcount = 0; + + /* + * Single buffer I/O? Skip what we've done so far in this region. + */ + write_count = dev->region.length - dev->n; + lpo->buf = HeapAlloc(hHeapHandle, HEAP_ZERO_MEMORY, write_count); + RUNTIME_CHECK(lpo->buf != NULL); + + socket_log(__LINE__, sock, NULL, TRACE, "alloc_buffer %p %d", lpo->buf, + write_count); + + memmove(lpo->buf, (dev->region.base + dev->n), write_count); + lpo->buflen = (unsigned int)write_count; + iov[0].buf = lpo->buf; + iov[0].len = (u_long)write_count; + iovcount = 1; + + msg->msg_iov = iov; + msg->msg_iovlen = iovcount; + msg->msg_totallen = (u_int)write_count; +} + +static void +set_dev_address(const isc_sockaddr_t *address, isc_socket_t *sock, + isc_socketevent_t *dev) { + if (sock->type == isc_sockettype_udp) { + if (address != NULL) { + dev->address = *address; + } else { + dev->address = sock->address; + } + } else if (sock->type == isc_sockettype_tcp) { + INSIST(address == NULL); + dev->address = sock->address; + } +} + +static void +destroy_socketevent(isc_event_t *event) { + isc_socketevent_t *ev = (isc_socketevent_t *)event; + + (ev->destroy)(event); +} + +static isc_socketevent_t * +allocate_socketevent(isc_mem_t *mctx, isc_socket_t *sock, + isc_eventtype_t eventtype, isc_taskaction_t action, + void *arg) { + isc_socketevent_t *ev; + + ev = (isc_socketevent_t *)isc_event_allocate(mctx, sock, eventtype, + action, arg, sizeof(*ev)); + + ev->result = ISC_R_IOERROR; /* XXXMLG temporary change to detect failure + */ + /* to set */ + ISC_LINK_INIT(ev, ev_link); + ev->region.base = NULL; + ev->n = 0; + ev->offset = 0; + ev->attributes = 0; + ev->destroy = ev->ev_destroy; + ev->ev_destroy = destroy_socketevent; + ev->dscp = 0; + + return (ev); +} + +#if defined(ISC_SOCKET_DEBUG) +static void +dump_msg(struct msghdr *msg, isc_socket_t *sock) { + unsigned int i; + + printf("MSGHDR %p, Socket #: %Iu\n", msg, sock->fd); + printf("\tname %p, namelen %d\n", msg->msg_name, msg->msg_namelen); + printf("\tiov %p, iovlen %d\n", msg->msg_iov, msg->msg_iovlen); + for (i = 0; i < (unsigned int)msg->msg_iovlen; i++) { + printf("\t\t%u\tbase %p, len %u\n", i, msg->msg_iov[i].buf, + msg->msg_iov[i].len); + } +} +#endif /* if defined(ISC_SOCKET_DEBUG) */ + +/* + * map the error code + */ +int +map_socket_error(isc_socket_t *sock, int windows_errno, int *isc_errno, + char *errorstring, size_t bufsize) { + int doreturn; + switch (windows_errno) { + case WSAECONNREFUSED: + *isc_errno = ISC_R_CONNREFUSED; + if (sock->connected) { + doreturn = DOIO_HARD; + } else { + doreturn = DOIO_SOFT; + } + break; + case WSAENETUNREACH: + case ERROR_NETWORK_UNREACHABLE: + *isc_errno = ISC_R_NETUNREACH; + if (sock->connected) { + doreturn = DOIO_HARD; + } else { + doreturn = DOIO_SOFT; + } + break; + case ERROR_PORT_UNREACHABLE: + case ERROR_HOST_UNREACHABLE: + case WSAEHOSTUNREACH: + *isc_errno = ISC_R_HOSTUNREACH; + if (sock->connected) { + doreturn = DOIO_HARD; + } else { + doreturn = DOIO_SOFT; + } + break; + case WSAENETDOWN: + *isc_errno = ISC_R_NETDOWN; + if (sock->connected) { + doreturn = DOIO_HARD; + } else { + doreturn = DOIO_SOFT; + } + break; + case WSAEHOSTDOWN: + *isc_errno = ISC_R_HOSTDOWN; + if (sock->connected) { + doreturn = DOIO_HARD; + } else { + doreturn = DOIO_SOFT; + } + break; + case WSAEACCES: + *isc_errno = ISC_R_NOPERM; + if (sock->connected) { + doreturn = DOIO_HARD; + } else { + doreturn = DOIO_SOFT; + } + break; + case WSAECONNRESET: + case WSAENETRESET: + case WSAECONNABORTED: + case WSAEDISCON: + *isc_errno = ISC_R_CONNECTIONRESET; + if (sock->connected) { + doreturn = DOIO_HARD; + } else { + doreturn = DOIO_SOFT; + } + break; + case WSAENOTCONN: + *isc_errno = ISC_R_NOTCONNECTED; + if (sock->connected) { + doreturn = DOIO_HARD; + } else { + doreturn = DOIO_SOFT; + } + break; + case ERROR_OPERATION_ABORTED: + case ERROR_CONNECTION_ABORTED: + case ERROR_REQUEST_ABORTED: + *isc_errno = ISC_R_CONNECTIONRESET; + doreturn = DOIO_HARD; + break; + case WSAENOBUFS: + *isc_errno = ISC_R_NORESOURCES; + doreturn = DOIO_HARD; + break; + case WSAEAFNOSUPPORT: + *isc_errno = ISC_R_FAMILYNOSUPPORT; + doreturn = DOIO_HARD; + break; + case WSAEADDRNOTAVAIL: + *isc_errno = ISC_R_ADDRNOTAVAIL; + doreturn = DOIO_HARD; + break; + case WSAEDESTADDRREQ: + *isc_errno = ISC_R_BADADDRESSFORM; + doreturn = DOIO_HARD; + break; + case ERROR_NETNAME_DELETED: + *isc_errno = ISC_R_NETDOWN; + doreturn = DOIO_HARD; + break; + default: + *isc_errno = ISC_R_IOERROR; + doreturn = DOIO_HARD; + break; + } + if (doreturn == DOIO_HARD) { + strerror_r(windows_errno, errorstring, bufsize); + } + return (doreturn); +} + +static void +fill_recv(isc_socket_t *sock, isc_socketevent_t *dev) { + int copylen; + + INSIST(dev->n < dev->minimum); + INSIST(sock->recvbuf.remaining > 0); + INSIST(sock->pending_recv == 0); + + if (sock->type == isc_sockettype_udp) { + dev->address.length = sock->recvbuf.from_addr_len; + memmove(&dev->address.type, &sock->recvbuf.from_addr, + sock->recvbuf.from_addr_len); + if (isc_sockaddr_getport(&dev->address) == 0) { + if (isc_log_wouldlog(isc_lctx, IOEVENT_LEVEL)) { + socket_log(__LINE__, sock, &dev->address, + IOEVENT, + "dropping source port zero packet"); + } + sock->recvbuf.remaining = 0; + return; + } + /* + * Simulate a firewall blocking UDP responses bigger than + * 'maxudp' bytes. + */ + if (sock->manager->maxudp != 0 && + sock->recvbuf.remaining > sock->manager->maxudp) + { + sock->recvbuf.remaining = 0; + return; + } + } else if (sock->type == isc_sockettype_tcp) { + dev->address = sock->address; + } + + copylen = min(dev->region.length - dev->n, sock->recvbuf.remaining); + memmove(dev->region.base + dev->n, sock->recvbuf.consume_position, + copylen); + sock->recvbuf.consume_position += copylen; + sock->recvbuf.remaining -= copylen; + dev->n += copylen; + + /* + * UDP receives are all-consuming. That is, if we have 4k worth of + * data in our receive buffer, and the caller only gave us + * 1k of space, we will toss the remaining 3k of data. TCP + * will keep the extra data around and use it for later requests. + */ + if (sock->type == isc_sockettype_udp) { + sock->recvbuf.remaining = 0; + } +} + +/* + * Copy out as much data from the internal buffer to done events. + * As each done event is filled, send it along its way. + */ +static void +completeio_recv(isc_socket_t *sock) { + isc_socketevent_t *dev; + + /* + * If we are in the process of filling our buffer, we cannot + * touch it yet, so don't. + */ + if (sock->pending_recv > 0) { + return; + } + + while (sock->recvbuf.remaining > 0 && !ISC_LIST_EMPTY(sock->recv_list)) + { + dev = ISC_LIST_HEAD(sock->recv_list); + + /* + * See if we have sufficient data in our receive buffer + * to handle this. If we do, copy out the data. + */ + fill_recv(sock, dev); + + /* + * Did we satisfy it? + */ + if (dev->n >= dev->minimum) { + dev->result = ISC_R_SUCCESS; + send_recvdone_event(sock, &dev); + } + } +} + +/* + * Returns: + * DOIO_SUCCESS The operation succeeded. dev->result contains + * ISC_R_SUCCESS. + * + * DOIO_HARD A hard or unexpected I/O error was encountered. + * dev->result contains the appropriate error. + * + * DOIO_SOFT A soft I/O error was encountered. No senddone + * event was sent. The operation should be retried. + * + * No other return values are possible. + */ +static int +completeio_send(isc_socket_t *sock, isc_socketevent_t *dev, + struct msghdr *messagehdr, int cc, int send_errno) { + char strbuf[ISC_STRERRORSIZE]; + + if (send_errno != 0) { + if (SOFT_ERROR(send_errno)) { + return (DOIO_SOFT); + } + + return (map_socket_error(sock, send_errno, &dev->result, strbuf, + sizeof(strbuf))); + } + + /* + * If we write less than we expected, update counters, poke. + */ + dev->n += cc; + if (cc != messagehdr->msg_totallen) { + return (DOIO_SOFT); + } + + /* + * Exactly what we wanted to write. We're done with this + * entry. Post its completion event. + */ + dev->result = ISC_R_SUCCESS; + return (DOIO_SUCCESS); +} + +static int +startio_send(isc_socket_t *sock, isc_socketevent_t *dev, int *nbytes, + int *send_errno) { + char *cmsg = NULL; + char strbuf[ISC_STRERRORSIZE]; + IoCompletionInfo *lpo; + int status; + struct msghdr *mh; + + /* + * Simulate a firewall blocking UDP responses bigger than + * 'maxudp' bytes. + */ + if (sock->type == isc_sockettype_udp && sock->manager->maxudp != 0 && + dev->region.length - dev->n > sock->manager->maxudp) + { + *nbytes = dev->region.length - dev->n; + return (DOIO_SUCCESS); + } + + lpo = (IoCompletionInfo *)HeapAlloc(hHeapHandle, HEAP_ZERO_MEMORY, + sizeof(IoCompletionInfo)); + RUNTIME_CHECK(lpo != NULL); + lpo->request_type = SOCKET_SEND; + lpo->dev = dev; + mh = &lpo->messagehdr; + memset(mh, 0, sizeof(struct msghdr)); + + build_msghdr_send(sock, dev, mh, cmsg, sock->iov, lpo); + + *nbytes = internal_sendmsg(sock, lpo, mh, 0, send_errno); + + if (*nbytes <= 0) { + /* + * I/O has been initiated + * completion will be through the completion port + */ + if (PENDING_ERROR(*send_errno)) { + status = DOIO_PENDING; + goto done; + } + + if (SOFT_ERROR(*send_errno)) { + status = DOIO_SOFT; + goto done; + } + + /* + * If we got this far then something is wrong + */ + if (isc_log_wouldlog(isc_lctx, IOEVENT_LEVEL)) { + strerror_r(*send_errno, strbuf, sizeof(strbuf)); + socket_log(__LINE__, sock, NULL, IOEVENT, + "startio_send: internal_sendmsg(%d) %d " + "bytes, err %d/%s", + sock->fd, *nbytes, *send_errno, strbuf); + } + status = DOIO_HARD; + goto done; + } + dev->result = ISC_R_SUCCESS; + status = DOIO_SOFT; +done: + _set_state(sock, SOCK_DATA); + return (status); +} + +static void +use_min_mtu(isc_socket_t *sock) { +#ifdef IPV6_USE_MIN_MTU + /* use minimum MTU */ + if (sock->pf == AF_INET6) { + int on = 1; + (void)setsockopt(sock->fd, IPPROTO_IPV6, IPV6_USE_MIN_MTU, + (void *)&on, sizeof(on)); + } +#else /* ifdef IPV6_USE_MIN_MTU */ + UNUSED(sock); +#endif /* ifdef IPV6_USE_MIN_MTU */ +} + +static isc_result_t +allocate_socket(isc_socketmgr_t *manager, isc_sockettype_t type, + isc_socket_t **socketp) { + isc_socket_t *sock; + + sock = isc_mem_get(manager->mctx, sizeof(*sock)); + + sock->magic = 0; + isc_refcount_init(&sock->references, 0); + + sock->manager = manager; + sock->type = type; + sock->fd = INVALID_SOCKET; + + ISC_LINK_INIT(sock, link); + + /* + * Set up list of readers and writers to be initially empty. + */ + ISC_LIST_INIT(sock->recv_list); + ISC_LIST_INIT(sock->send_list); + ISC_LIST_INIT(sock->accept_list); + ISC_LIST_INIT(sock->connect_list); + sock->pending_accept = 0; + sock->pending_recv = 0; + sock->pending_send = 0; + sock->pending_iocp = 0; + sock->listener = 0; + sock->connected = 0; + sock->pending_connect = 0; + sock->bound = 0; + sock->dupped = 0; + memset(sock->name, 0, sizeof(sock->name)); /* zero the name field */ + _set_state(sock, SOCK_INITIALIZED); + + sock->recvbuf.len = 65536; + sock->recvbuf.consume_position = sock->recvbuf.base; + sock->recvbuf.remaining = 0; + sock->recvbuf.base = isc_mem_get(manager->mctx, + sock->recvbuf.len); /* max buffer */ + /* size */ + + /* + * Initialize the lock. + */ + isc_mutex_init(&sock->lock); + + socket_log(__LINE__, sock, NULL, EVENT, "allocated"); + + sock->magic = SOCKET_MAGIC; + *socketp = sock; + + return (ISC_R_SUCCESS); +} + +/* + * Verify that the socket state is consistent. + */ +static void +consistent(isc_socket_t *sock) { + isc_socketevent_t *dev; + isc_socket_newconnev_t *nev; + unsigned int count; + char *crash_reason; + bool crash = false; + + REQUIRE(sock->pending_iocp == sock->pending_recv + sock->pending_send + + sock->pending_accept + + sock->pending_connect); + + dev = ISC_LIST_HEAD(sock->send_list); + count = 0; + while (dev != NULL) { + count++; + dev = ISC_LIST_NEXT(dev, ev_link); + } + if (count > sock->pending_send) { + crash = true; + crash_reason = "send_list > sock->pending_send"; + } + + nev = ISC_LIST_HEAD(sock->accept_list); + count = 0; + while (nev != NULL) { + count++; + nev = ISC_LIST_NEXT(nev, ev_link); + } + if (count > sock->pending_accept) { + crash = true; + crash_reason = "accept_list > sock->pending_accept"; + } + + if (crash) { + socket_log(__LINE__, sock, NULL, CREATION, + "SOCKET INCONSISTENT: %s", crash_reason); + sock_dump(sock); + INSIST(!crash); + } +} + +/* + * Maybe free the socket. + * + * This function will verify that the socket is no longer in use in any way, + * either internally or externally. This is the only place where this + * check is to be made; if some bit of code believes that IT is done with + * the socket (e.g., some reference counter reaches zero), it should call + * this function. + * + * When calling this function, the socket must be locked, and the manager + * must be unlocked. + * + * When this function returns, *socketp will be NULL. No tricks to try + * to hold on to this pointer are allowed. + */ +static void +maybe_free_socket(isc_socket_t **socketp, int lineno) { + isc_socket_t *sock = *socketp; + *socketp = NULL; + + INSIST(VALID_SOCKET(sock)); + CONSISTENT(sock); + + if (sock->pending_iocp > 0 || sock->pending_recv > 0 || + sock->pending_send > 0 || sock->pending_accept > 0 || + isc_refcount_current(&sock->references) > 0 || + sock->pending_connect == 1 || !ISC_LIST_EMPTY(sock->recv_list) || + !ISC_LIST_EMPTY(sock->send_list) || + !ISC_LIST_EMPTY(sock->accept_list) || + !ISC_LIST_EMPTY(sock->connect_list) || sock->fd != INVALID_SOCKET) + { + UNLOCK(&sock->lock); + return; + } + UNLOCK(&sock->lock); + + free_socket(&sock, lineno); +} + +void +free_socket(isc_socket_t **sockp, int lineno) { + isc_socketmgr_t *manager; + isc_socket_t *sock = *sockp; + *sockp = NULL; + + /* + * Seems we can free the socket after all. + */ + manager = sock->manager; + socket_log(__LINE__, sock, NULL, CREATION, + "freeing socket line %d fd %d lock %p semaphore %p", lineno, + sock->fd, &sock->lock, sock->lock.LockSemaphore); + + sock->magic = 0; + isc_mutex_destroy(&sock->lock); + + if (sock->recvbuf.base != NULL) { + isc_mem_put(manager->mctx, sock->recvbuf.base, + sock->recvbuf.len); + } + + LOCK(&manager->lock); + if (ISC_LINK_LINKED(sock, link)) { + ISC_LIST_UNLINK(manager->socklist, sock, link); + } + isc_mem_put(manager->mctx, sock, sizeof(*sock)); + + if (ISC_LIST_EMPTY(manager->socklist)) { + SIGNAL(&manager->shutdown_ok); + } + UNLOCK(&manager->lock); +} + +/* + * Create a new 'type' socket managed by 'manager'. Events + * will be posted to 'task' and when dispatched 'action' will be + * called with 'arg' as the arg value. The new socket is returned + * in 'socketp'. + */ +static isc_result_t +socket_create(isc_socketmgr_t *manager, int pf, isc_sockettype_t type, + isc_socket_t **socketp, isc_socket_t *dup_socket) { + isc_socket_t *sock = NULL; + isc_result_t result; +#if defined(USE_CMSG) + int on = 1; +#endif /* if defined(USE_CMSG) */ +#if defined(SO_RCVBUF) + socklen_t optlen; + int size; +#endif /* if defined(SO_RCVBUF) */ + int socket_errno; + char strbuf[ISC_STRERRORSIZE]; + + REQUIRE(VALID_MANAGER(manager)); + REQUIRE(socketp != NULL && *socketp == NULL); + +#ifndef SOCK_RAW + if (type == isc_sockettype_raw) { + return (ISC_R_NOTIMPLEMENTED); + } +#endif /* ifndef SOCK_RAW */ + + result = allocate_socket(manager, type, &sock); + if (result != ISC_R_SUCCESS) { + return (result); + } + + sock->pf = pf; + switch (type) { + case isc_sockettype_udp: + sock->fd = socket(pf, SOCK_DGRAM, IPPROTO_UDP); + if (sock->fd != INVALID_SOCKET) { + result = connection_reset_fix(sock->fd); + if (result != ISC_R_SUCCESS) { + socket_log(__LINE__, sock, NULL, EVENT, + "closed %d %d %" PRIuFAST32 " " + "con_reset_fix_failed", + sock->pending_recv, + sock->pending_send, + isc_refcount_current( + &sock->references)); + closesocket(sock->fd); + _set_state(sock, SOCK_CLOSED); + sock->fd = INVALID_SOCKET; + free_socket(&sock, __LINE__); + return (result); + } + } + break; + case isc_sockettype_tcp: + sock->fd = socket(pf, SOCK_STREAM, IPPROTO_TCP); + break; +#ifdef SOCK_RAW + case isc_sockettype_raw: + sock->fd = socket(pf, SOCK_RAW, 0); +#ifdef PF_ROUTE + if (pf == PF_ROUTE) { + sock->bound = 1; + } +#endif /* ifdef PF_ROUTE */ + break; +#endif /* ifdef SOCK_RAW */ + } + + if (sock->fd == INVALID_SOCKET) { + socket_errno = WSAGetLastError(); + free_socket(&sock, __LINE__); + + switch (socket_errno) { + case WSAEMFILE: + case WSAENOBUFS: + return (ISC_R_NORESOURCES); + + case WSAEPROTONOSUPPORT: + case WSAEPFNOSUPPORT: + case WSAEAFNOSUPPORT: + return (ISC_R_FAMILYNOSUPPORT); + + default: + strerror_r(socket_errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "socket() failed: %s", strbuf); + return (ISC_R_UNEXPECTED); + } + } + + result = make_nonblock(sock->fd); + if (result != ISC_R_SUCCESS) { + socket_log(__LINE__, sock, NULL, EVENT, + "closed %d %d %" PRIuFAST32 " make_nonblock_failed", + sock->pending_recv, sock->pending_send, + isc_refcount_current(&sock->references)); + closesocket(sock->fd); + sock->fd = INVALID_SOCKET; + free_socket(&sock, __LINE__); + return (result); + } + + /* + * Use minimum mtu if possible. + */ + use_min_mtu(sock); + +#if defined(USE_CMSG) || defined(SO_RCVBUF) + if (type == isc_sockettype_udp) { +#if defined(USE_CMSG) +#ifdef IPV6_RECVPKTINFO + /* 2292bis */ + if ((pf == AF_INET6) && + (setsockopt(sock->fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, + (char *)&on, sizeof(on)) < 0)) + { + strerror_r(WSAGetLastError(), strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "setsockopt(%d, IPV6_RECVPKTINFO) " + "failed: %s", + sock->fd, strbuf); + } +#else /* ifdef IPV6_RECVPKTINFO */ + /* 2292 */ + if ((pf == AF_INET6) && + (setsockopt(sock->fd, IPPROTO_IPV6, IPV6_PKTINFO, + (char *)&on, sizeof(on)) < 0)) + { + strerror_r(WSAGetLastError(), strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "setsockopt(%d, IPV6_PKTINFO) %s: %s", + sock->fd, strbuf); + } +#endif /* IPV6_RECVPKTINFO */ +#endif /* defined(USE_CMSG) */ + +#if defined(SO_RCVBUF) + optlen = sizeof(size); + if (getsockopt(sock->fd, SOL_SOCKET, SO_RCVBUF, (char *)&size, + &optlen) >= 0 && + size < RCVBUFSIZE) + { + size = RCVBUFSIZE; + (void)setsockopt(sock->fd, SOL_SOCKET, SO_RCVBUF, + (char *)&size, sizeof(size)); + } +#endif /* if defined(SO_RCVBUF) */ + } +#endif /* defined(USE_CMSG) || defined(SO_RCVBUF) */ + + _set_state(sock, SOCK_OPEN); + isc_refcount_init(&sock->references, 1); + *socketp = sock; + + iocompletionport_update(sock); + + if (dup_socket) { +#ifndef ISC_ALLOW_MAPPED + isc_socket_ipv6only(sock, true); +#endif /* ifndef ISC_ALLOW_MAPPED */ + + if (dup_socket->bound) { + isc_sockaddr_t local; + + result = isc_socket_getsockname(dup_socket, &local); + if (result != ISC_R_SUCCESS) { + isc_socket_close(sock); + return (result); + } + result = isc_socket_bind(sock, &local, + ISC_SOCKET_REUSEADDRESS); + if (result != ISC_R_SUCCESS) { + isc_socket_close(sock); + return (result); + } + } + sock->dupped = 1; + } + + /* + * Note we don't have to lock the socket like we normally would because + * there are no external references to it yet. + */ + LOCK(&manager->lock); + ISC_LIST_APPEND(manager->socklist, sock, link); + InterlockedIncrement(&manager->totalSockets); + UNLOCK(&manager->lock); + + socket_log(__LINE__, sock, NULL, CREATION, "created %u type %u", + sock->fd, type); + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_socket_create(isc_socketmgr_t *manager, int pf, isc_sockettype_t type, + isc_socket_t **socketp) { + return (socket_create(manager, pf, type, socketp, NULL)); +} + +isc_result_t +isc_socket_dup(isc_socket_t *sock, isc_socket_t **socketp) { + REQUIRE(VALID_SOCKET(sock)); + REQUIRE(socketp != NULL && *socketp == NULL); + + return (socket_create(sock->manager, sock->pf, sock->type, socketp, + sock)); +} + +isc_result_t +isc_socket_open(isc_socket_t *sock) { + REQUIRE(VALID_SOCKET(sock)); + + return (ISC_R_NOTIMPLEMENTED); +} + +/* + * Attach to a socket. Caller must explicitly detach when it is done. + */ +void +isc_socket_attach(isc_socket_t *sock, isc_socket_t **socketp) { + REQUIRE(VALID_SOCKET(sock)); + REQUIRE(socketp != NULL && *socketp == NULL); + + LOCK(&sock->lock); + CONSISTENT(sock); + UNLOCK(&sock->lock); + + isc_refcount_increment0(&sock->references); + + *socketp = sock; +} + +/* + * Dereference a socket. If this is the last reference to it, clean things + * up by destroying the socket. + */ +void +isc_socket_detach(isc_socket_t **socketp) { + isc_socket_t *sock; + uint32_t references; + + REQUIRE(socketp != NULL); + sock = *socketp; + *socketp = NULL; + REQUIRE(VALID_SOCKET(sock)); + + LOCK(&sock->lock); + CONSISTENT(sock); + + references = isc_refcount_decrement(&sock->references); + + socket_log(__LINE__, sock, NULL, EVENT, + "detach_socket %d %d %" PRIuFAST32, sock->pending_recv, + sock->pending_send, isc_refcount_current(&sock->references)); + + if (references == 1 && sock->fd != INVALID_SOCKET) { + closesocket(sock->fd); + sock->fd = INVALID_SOCKET; + _set_state(sock, SOCK_CLOSED); + } + + maybe_free_socket(&sock, __LINE__); /* Also unlocks the socket lock */ +} + +isc_result_t +isc_socket_close(isc_socket_t *sock) { + REQUIRE(VALID_SOCKET(sock)); + + return (ISC_R_NOTIMPLEMENTED); +} + +/* + * Dequeue an item off the given socket's read queue, set the result code + * in the done event to the one provided, and send it to the task it was + * destined for. + * + * If the event to be sent is on a list, remove it before sending. If + * asked to, send and detach from the task as well. + * + * Caller must have the socket locked if the event is attached to the socket. + */ +static void +send_recvdone_event(isc_socket_t *sock, isc_socketevent_t **dev) { + isc_task_t *task; + + task = (*dev)->ev_sender; + (*dev)->ev_sender = sock; + + if (ISC_LINK_LINKED(*dev, ev_link)) { + ISC_LIST_DEQUEUE(sock->recv_list, *dev, ev_link); + } + + if (((*dev)->attributes & ISC_SOCKEVENTATTR_ATTACHED) != 0) { + isc_task_sendanddetach(&task, (isc_event_t **)dev); + } else { + isc_task_send(task, (isc_event_t **)dev); + } + + CONSISTENT(sock); +} + +/* + * See comments for send_recvdone_event() above. + */ +static void +send_senddone_event(isc_socket_t *sock, isc_socketevent_t **dev) { + isc_task_t *task; + + INSIST(dev != NULL && *dev != NULL); + + task = (*dev)->ev_sender; + (*dev)->ev_sender = sock; + + if (ISC_LINK_LINKED(*dev, ev_link)) { + ISC_LIST_DEQUEUE(sock->send_list, *dev, ev_link); + } + + if (((*dev)->attributes & ISC_SOCKEVENTATTR_ATTACHED) != 0) { + isc_task_sendanddetach(&task, (isc_event_t **)dev); + } else { + isc_task_send(task, (isc_event_t **)dev); + } + + CONSISTENT(sock); +} + +/* + * See comments for send_recvdone_event() above. + */ +static void +send_acceptdone_event(isc_socket_t *sock, isc_socket_newconnev_t **adev) { + isc_task_t *task; + + INSIST(adev != NULL && *adev != NULL); + + task = (*adev)->ev_sender; + (*adev)->ev_sender = sock; + + if (ISC_LINK_LINKED(*adev, ev_link)) { + ISC_LIST_DEQUEUE(sock->accept_list, *adev, ev_link); + } + + isc_task_sendanddetach(&task, (isc_event_t **)adev); + + CONSISTENT(sock); +} + +/* + * See comments for send_recvdone_event() above. + */ +static void +send_connectdone_event(isc_socket_t *sock, isc_socket_connev_t **cdev) { + isc_task_t *task; + + INSIST(cdev != NULL && *cdev != NULL); + + task = (*cdev)->ev_sender; + (*cdev)->ev_sender = sock; + + if (ISC_LINK_LINKED(*cdev, ev_link)) { + ISC_LIST_DEQUEUE(sock->connect_list, *cdev, ev_link); + } + + isc_task_sendanddetach(&task, (isc_event_t **)cdev); + + CONSISTENT(sock); +} + +/* + * On entry to this function, the event delivered is the internal + * readable event, and the first item on the accept_list should be + * the done event we want to send. If the list is empty, this is a no-op, + * so just close the new connection, unlock, and return. + * + * Note the socket is locked before entering here + */ +static void +internal_accept(isc_socket_t *sock, IoCompletionInfo *lpo, int accept_errno) { + isc_socket_newconnev_t *adev; + isc_result_t result = ISC_R_SUCCESS; + isc_socket_t *nsock; + struct sockaddr *localaddr; + int localaddr_len = sizeof(*localaddr); + struct sockaddr *remoteaddr; + int remoteaddr_len = sizeof(*remoteaddr); + + INSIST(VALID_SOCKET(sock)); + LOCK(&sock->lock); + CONSISTENT(sock); + + socket_log(__LINE__, sock, NULL, TRACE, "internal_accept called"); + + INSIST(sock->listener); + + INSIST(sock->pending_iocp > 0); + sock->pending_iocp--; + INSIST(sock->pending_accept > 0); + sock->pending_accept--; + + adev = lpo->adev; + + /* + * If the event is no longer in the list we can just return. + */ + if (!acceptdone_is_active(sock, adev)) { + goto done; + } + + nsock = adev->newsocket; + + /* + * Pull off the done event. + */ + ISC_LIST_UNLINK(sock->accept_list, adev, ev_link); + + /* + * Extract the addresses from the socket, copy them into the structure, + * and return the new socket. + */ + ISCGetAcceptExSockaddrs( + lpo->acceptbuffer, 0, sizeof(SOCKADDR_STORAGE) + 16, + sizeof(SOCKADDR_STORAGE) + 16, (LPSOCKADDR *)&localaddr, + &localaddr_len, (LPSOCKADDR *)&remoteaddr, &remoteaddr_len); + memmove(&adev->address.type, remoteaddr, remoteaddr_len); + adev->address.length = remoteaddr_len; + nsock->address = adev->address; + nsock->pf = adev->address.type.sa.sa_family; + + socket_log(__LINE__, nsock, &nsock->address, TRACE, + "internal_accept parent %p", sock); + + result = make_nonblock(adev->newsocket->fd); + INSIST(result == ISC_R_SUCCESS); + + /* + * Use minimum mtu if possible. + */ + use_min_mtu(adev->newsocket); + + INSIST(setsockopt(nsock->fd, SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, + (char *)&sock->fd, sizeof(sock->fd)) == 0); + + /* + * Hook it up into the manager. + */ + nsock->bound = 1; + nsock->connected = 1; + _set_state(nsock, SOCK_OPEN); + + LOCK(&nsock->manager->lock); + ISC_LIST_APPEND(nsock->manager->socklist, nsock, link); + InterlockedIncrement(&nsock->manager->totalSockets); + UNLOCK(&nsock->manager->lock); + + socket_log(__LINE__, sock, &nsock->address, CREATION, + "accepted_connection new_socket %p fd %d", nsock, nsock->fd); + + adev->result = result; + send_acceptdone_event(sock, &adev); + +done: + CONSISTENT(sock); + UNLOCK(&sock->lock); + + HeapFree(hHeapHandle, 0, lpo->acceptbuffer); + lpo->acceptbuffer = NULL; +} + +/* + * Called when a socket with a pending connect() finishes. + * Note that the socket is locked before entering. + */ +static void +internal_connect(isc_socket_t *sock, IoCompletionInfo *lpo, int connect_errno) { + isc_socket_connev_t *cdev; + isc_result_t result; + char strbuf[ISC_STRERRORSIZE]; + + INSIST(VALID_SOCKET(sock)); + + LOCK(&sock->lock); + + INSIST(sock->pending_iocp > 0); + sock->pending_iocp--; + INSIST(sock->pending_connect == 1); + sock->pending_connect = 0; + + /* + * If the event is no longer in the list we can just close and return. + */ + cdev = lpo->cdev; + if (!connectdone_is_active(sock, cdev)) { + sock->pending_connect = 0; + if (sock->fd != INVALID_SOCKET) { + closesocket(sock->fd); + sock->fd = INVALID_SOCKET; + _set_state(sock, SOCK_CLOSED); + } + CONSISTENT(sock); + UNLOCK(&sock->lock); + return; + } + + /* + * Check possible Windows network event error status here. + */ + if (connect_errno != 0) { + /* + * If the error is SOFT, just try again on this + * fd and pretend nothing strange happened. + */ + if (SOFT_ERROR(connect_errno) || + connect_errno == WSAEINPROGRESS) + { + sock->pending_connect = 1; + CONSISTENT(sock); + UNLOCK(&sock->lock); + return; + } + + /* + * Translate other errors into ISC_R_* flavors. + */ + switch (connect_errno) { +#define ERROR_MATCH(a, b) \ + case a: \ + result = b; \ + break; + ERROR_MATCH(WSAEACCES, ISC_R_NOPERM); + ERROR_MATCH(WSAEADDRNOTAVAIL, ISC_R_ADDRNOTAVAIL); + ERROR_MATCH(WSAEAFNOSUPPORT, ISC_R_ADDRNOTAVAIL); + ERROR_MATCH(WSAECONNREFUSED, ISC_R_CONNREFUSED); + ERROR_MATCH(WSAEHOSTUNREACH, ISC_R_HOSTUNREACH); + ERROR_MATCH(WSAEHOSTDOWN, ISC_R_HOSTDOWN); + ERROR_MATCH(WSAENETUNREACH, ISC_R_NETUNREACH); + ERROR_MATCH(WSAENETDOWN, ISC_R_NETDOWN); + ERROR_MATCH(WSAENOBUFS, ISC_R_NORESOURCES); + ERROR_MATCH(WSAECONNRESET, ISC_R_CONNECTIONRESET); + ERROR_MATCH(WSAECONNABORTED, ISC_R_CONNECTIONRESET); + ERROR_MATCH(WSAETIMEDOUT, ISC_R_TIMEDOUT); +#undef ERROR_MATCH + default: + result = ISC_R_UNEXPECTED; + strerror_r(connect_errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "internal_connect: connect() %s", + strbuf); + } + } else { + INSIST(setsockopt(sock->fd, SOL_SOCKET, + SO_UPDATE_CONNECT_CONTEXT, NULL, 0) == 0); + result = ISC_R_SUCCESS; + sock->connected = 1; + socket_log(__LINE__, sock, &sock->address, IOEVENT, + "internal_connect: success"); + } + + do { + cdev->result = result; + send_connectdone_event(sock, &cdev); + cdev = ISC_LIST_HEAD(sock->connect_list); + } while (cdev != NULL); + + UNLOCK(&sock->lock); +} + +/* + * Loop through the socket, returning ISC_R_EOF for each done event pending. + */ +static void +send_recvdone_abort(isc_socket_t *sock, isc_result_t result) { + isc_socketevent_t *dev; + + while (!ISC_LIST_EMPTY(sock->recv_list)) { + dev = ISC_LIST_HEAD(sock->recv_list); + dev->result = result; + send_recvdone_event(sock, &dev); + } +} + +/* + * Loop through the socket, returning result for each done event pending. + */ +static void +send_connectdone_abort(isc_socket_t *sock, isc_result_t result) { + isc_socket_connev_t *dev; + + while (!ISC_LIST_EMPTY(sock->connect_list)) { + dev = ISC_LIST_HEAD(sock->connect_list); + dev->result = result; + send_connectdone_event(sock, &dev); + } +} + +/* + * Take the data we received in our private buffer, and if any recv() calls on + * our list are satisfied, send the corresponding done event. + * + * If we need more data (there are still items on the recv_list after we consume + * all our data) then arrange for another system recv() call to fill our + * buffers. + */ +static void +internal_recv(isc_socket_t *sock, int nbytes) { + INSIST(VALID_SOCKET(sock)); + + LOCK(&sock->lock); + CONSISTENT(sock); + + socket_log(__LINE__, sock, NULL, IOEVENT, + "internal_recv: %d bytes received", nbytes); + + /* + * If we got here, the I/O operation succeeded. However, we might + * still have removed this event from our notification list (or never + * placed it on it due to immediate completion.) + * Handle the reference counting here, and handle the cancellation + * event just after. + */ + INSIST(sock->pending_iocp > 0); + sock->pending_iocp--; + INSIST(sock->pending_recv > 0); + sock->pending_recv--; + + /* + * The only way we could have gotten here is that our I/O has + * successfully completed. Update our pointers, and move on. + * The only odd case here is that we might not have received + * enough data on a TCP stream to satisfy the minimum requirements. + * If this is the case, we will re-issue the recv() call for what + * we need. + * + * We do check for a recv() of 0 bytes on a TCP stream. This + * means the remote end has closed. + */ + if (nbytes == 0 && sock->type == isc_sockettype_tcp) { + send_recvdone_abort(sock, ISC_R_EOF); + maybe_free_socket(&sock, __LINE__); + return; + } + sock->recvbuf.remaining = nbytes; + sock->recvbuf.consume_position = sock->recvbuf.base; + completeio_recv(sock); + + /* + * If there are more receivers waiting for data, queue another receive + * here. + */ + queue_receive_request(sock); + + /* + * Unlock and/or destroy if we are the last thing this socket has left + * to do. + */ + maybe_free_socket(&sock, __LINE__); +} + +static void +internal_send(isc_socket_t *sock, isc_socketevent_t *dev, + struct msghdr *messagehdr, int nbytes, int send_errno, + IoCompletionInfo *lpo) { + /* + * Find out what socket this is and lock it. + */ + INSIST(VALID_SOCKET(sock)); + + LOCK(&sock->lock); + CONSISTENT(sock); + + socket_log(__LINE__, sock, NULL, IOEVENT, + "internal_send: task got socket event %p", dev); + + if (lpo->buf != NULL) { + socket_log(__LINE__, sock, NULL, TRACE, "free_buffer %p", + lpo->buf); + + HeapFree(hHeapHandle, 0, lpo->buf); + lpo->buf = NULL; + lpo->buflen = 0; + } + + INSIST(sock->pending_iocp > 0); + sock->pending_iocp--; + INSIST(sock->pending_send > 0); + sock->pending_send--; + + /* If the event is no longer in the list we can just return */ + if (!senddone_is_active(sock, dev)) { + goto done; + } + + /* + * Set the error code and send things on its way. + */ + switch (completeio_send(sock, dev, messagehdr, nbytes, send_errno)) { + case DOIO_SOFT: + break; + case DOIO_HARD: + case DOIO_SUCCESS: + send_senddone_event(sock, &dev); + break; + } + +done: + maybe_free_socket(&sock, __LINE__); +} + +/* + * These return if the done event passed in is on the list. + * Using these ensures we will not double-send an event. + */ +static bool +senddone_is_active(isc_socket_t *sock, isc_socketevent_t *dev) { + isc_socketevent_t *ldev; + + ldev = ISC_LIST_HEAD(sock->send_list); + while (ldev != NULL && ldev != dev) { + ldev = ISC_LIST_NEXT(ldev, ev_link); + } + + return (ldev == NULL ? false : true); +} + +static bool +acceptdone_is_active(isc_socket_t *sock, isc_socket_newconnev_t *dev) { + isc_socket_newconnev_t *ldev; + + ldev = ISC_LIST_HEAD(sock->accept_list); + while (ldev != NULL && ldev != dev) { + ldev = ISC_LIST_NEXT(ldev, ev_link); + } + + return (ldev == NULL ? false : true); +} + +static bool +connectdone_is_active(isc_socket_t *sock, isc_socket_connev_t *dev) { + isc_socket_connev_t *cdev; + + cdev = ISC_LIST_HEAD(sock->connect_list); + while (cdev != NULL && cdev != dev) { + cdev = ISC_LIST_NEXT(cdev, ev_link); + } + + return (cdev == NULL ? false : true); +} + +/* */ +/* The Windows network stack seems to have two very distinct paths depending */ +/* on what is installed. Specifically, if something is looking at network */ +/* connections (like an anti-virus or anti-malware application, such as */ +/* McAfee products) Windows may return additional error conditions which */ +/* were not previously returned. */ +/* */ +/* One specific one is when a TCP SYN scan is used. In this situation, */ +/* Windows responds with the SYN-ACK, but the scanner never responds with */ +/* the 3rd packet, the ACK. Windows considers this a partially open connection. + */ +/* Most Unix networking stacks, and Windows without McAfee installed, will */ +/* not return this to the caller. However, with this product installed, */ +/* Windows returns this as a failed status on the Accept() call. Here, we */ +/* will just re-issue the ISCAcceptEx() call as if nothing had happened. */ +/* */ +/* This code should only be called when the listening socket has received */ +/* such an error. Additionally, the "parent" socket must be locked. */ +/* Additionally, the lpo argument is re-used here, and must not be freed */ +/* by the caller. */ +/* */ +static isc_result_t +restart_accept(isc_socket_t *parent, IoCompletionInfo *lpo) { + isc_socket_t *nsock = lpo->adev->newsocket; + SOCKET new_fd; + + /* + * AcceptEx() requires we pass in a socket. Note that we carefully + * do not close the previous socket in case of an error message returned + * by our new socket() call. If we return an error here, our caller + * will clean up. + */ + new_fd = socket(parent->pf, SOCK_STREAM, IPPROTO_TCP); + if (nsock->fd == INVALID_SOCKET) { + return (ISC_R_FAILURE); /* parent will ask windows for error */ + /* message */ + } + closesocket(nsock->fd); + nsock->fd = new_fd; + + memset(&lpo->overlapped, 0, sizeof(lpo->overlapped)); + + ISCAcceptEx(parent->fd, nsock->fd, /* Accepted Socket */ + lpo->acceptbuffer, /* Buffer for initial Recv */ + 0, /* Length of Buffer */ + sizeof(SOCKADDR_STORAGE) + 16, /* Local address length + 16 + */ + sizeof(SOCKADDR_STORAGE) + 16, /* Remote address length + 16 + */ + (LPDWORD)&lpo->received_bytes, /* Bytes Recved */ + (LPOVERLAPPED)lpo /* Overlapped structure */ + ); + + InterlockedDecrement(&nsock->manager->iocp_total); + iocompletionport_update(nsock); + + return (ISC_R_SUCCESS); +} + +/* + * This is the I/O Completion Port Worker Function. It loops forever + * waiting for I/O to complete and then forwards them for further + * processing. There are a number of these in separate threads. + */ +static isc_threadresult_t WINAPI +SocketIoThread(LPVOID ThreadContext) { + isc_socketmgr_t *manager = ThreadContext; + DWORD nbytes; + IoCompletionInfo *lpo = NULL; + isc_socket_t *sock = NULL; + int request; + struct msghdr *messagehdr = NULL; + int errval; + char strbuf[ISC_STRERRORSIZE]; + int errstatus; + + REQUIRE(VALID_MANAGER(manager)); + + /* + * Set the thread priority high enough so I/O will + * preempt normal recv packet processing, but not + * higher than the timer sync thread. + */ + if (!SetThreadPriority(GetCurrentThread(), + THREAD_PRIORITY_ABOVE_NORMAL)) + { + errval = GetLastError(); + strerror_r(errval, strbuf, sizeof(strbuf)); + FATAL_ERROR(__FILE__, __LINE__, "Can't set thread priority: %s", + strbuf); + } + + /* + * Loop forever waiting on I/O Completions and then processing them + */ + while (TRUE) { + BOOL bSuccess; + + wait_again: + bSuccess = GetQueuedCompletionStatus( + manager->hIoCompletionPort, &nbytes, (PULONG_PTR)&sock, + (LPWSAOVERLAPPED *)&lpo, INFINITE); + if (lpo == NULL) { /* Received request to exit */ + break; + } + + REQUIRE(VALID_SOCKET(sock)); + + request = lpo->request_type; + + if (!bSuccess) { + errstatus = GetLastError(); + } else { + errstatus = 0; + } + if (!bSuccess && errstatus != ERROR_MORE_DATA) { + isc_result_t isc_result; + + /* + * Did the I/O operation complete? + */ + isc_result = isc__errno2result(errstatus); + + LOCK(&sock->lock); + CONSISTENT(sock); + switch (request) { + case SOCKET_RECV: + INSIST(sock->pending_iocp > 0); + sock->pending_iocp--; + INSIST(sock->pending_recv > 0); + sock->pending_recv--; + if (!sock->connected && + ((errstatus == ERROR_HOST_UNREACHABLE) || + (errstatus == WSAENETRESET) || + (errstatus == WSAECONNRESET))) + { + /* ignore soft errors */ + queue_receive_request(sock); + break; + } + send_recvdone_abort(sock, isc_result); + if (isc_result == ISC_R_UNEXPECTED) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "SOCKET_RECV: Windows " + "error code: %d, " + "returning ISC error " + "%d", + errstatus, isc_result); + } + break; + + case SOCKET_SEND: + INSIST(sock->pending_iocp > 0); + sock->pending_iocp--; + INSIST(sock->pending_send > 0); + sock->pending_send--; + if (senddone_is_active(sock, lpo->dev)) { + lpo->dev->result = isc_result; + socket_log(__LINE__, sock, NULL, EVENT, + "canceled_send"); + send_senddone_event(sock, &lpo->dev); + } + break; + + case SOCKET_ACCEPT: + INSIST(sock->pending_iocp > 0); + INSIST(sock->pending_accept > 0); + + socket_log(__LINE__, sock, NULL, EVENT, + "Accept: errstatus=%d isc_result=%d", + errstatus, isc_result); + + if (acceptdone_is_active(sock, lpo->adev)) { + if (restart_accept(sock, lpo) == + ISC_R_SUCCESS) + { + UNLOCK(&sock->lock); + goto wait_again; + } else { + errstatus = GetLastError(); + isc_result = isc__errno2result( + errstatus); + socket_log(__LINE__, sock, NULL, + EVENT, + "restart_accept() " + "failed: " + "errstatus=%d " + "isc_result=%d", + errstatus, + isc_result); + } + } + + sock->pending_iocp--; + sock->pending_accept--; + if (acceptdone_is_active(sock, lpo->adev)) { + closesocket(lpo->adev->newsocket->fd); + lpo->adev->newsocket->fd = + INVALID_SOCKET; + isc_refcount_decrementz( + &lpo->adev->newsocket + ->references); + free_socket(&lpo->adev->newsocket, + __LINE__); + lpo->adev->result = isc_result; + socket_log(__LINE__, sock, NULL, EVENT, + "canceled_accept"); + send_acceptdone_event(sock, &lpo->adev); + } + break; + + case SOCKET_CONNECT: + INSIST(sock->pending_iocp > 0); + sock->pending_iocp--; + INSIST(sock->pending_connect == 1); + sock->pending_connect = 0; + if (connectdone_is_active(sock, lpo->cdev)) { + socket_log(__LINE__, sock, NULL, EVENT, + "canceled_connect"); + send_connectdone_abort(sock, + isc_result); + } + break; + } + maybe_free_socket(&sock, __LINE__); + + if (lpo != NULL) { + HeapFree(hHeapHandle, 0, lpo); + } + continue; + } + + messagehdr = &lpo->messagehdr; + + switch (request) { + case SOCKET_RECV: + internal_recv(sock, nbytes); + break; + case SOCKET_SEND: + internal_send(sock, lpo->dev, messagehdr, nbytes, + errstatus, lpo); + break; + case SOCKET_ACCEPT: + internal_accept(sock, lpo, errstatus); + break; + case SOCKET_CONNECT: + internal_connect(sock, lpo, errstatus); + break; + } + + if (lpo != NULL) { + HeapFree(hHeapHandle, 0, lpo); + } + } + + /* + * Exit Completion Port Thread + */ + manager_log(manager, TRACE, "SocketIoThread exiting"); + return ((isc_threadresult_t)0); +} + +/* + * Create a new socket manager. + */ +isc_result_t +isc_socketmgr_create(isc_mem_t *mctx, isc_socketmgr_t **managerp) { + return (isc_socketmgr_create2(mctx, managerp, 0, 1)); +} + +isc_result_t +isc_socketmgr_create2(isc_mem_t *mctx, isc_socketmgr_t **managerp, + unsigned int maxsocks, int nthreads) { + isc_socketmgr_t *manager; + + REQUIRE(managerp != NULL && *managerp == NULL); + + if (maxsocks != 0) { + return (ISC_R_NOTIMPLEMENTED); + } + + manager = isc_mem_get(mctx, sizeof(*manager)); + + InitSockets(); + + manager->magic = SOCKET_MANAGER_MAGIC; + manager->mctx = NULL; + manager->stats = NULL; + ISC_LIST_INIT(manager->socklist); + isc_mutex_init(&manager->lock); + isc_condition_init(&manager->shutdown_ok); + + isc_mem_attach(mctx, &manager->mctx); + if (nthreads == 0) { + nthreads = isc_os_ncpus() + 1; + } + manager->maxIOCPThreads = min(nthreads, MAX_IOCPTHREADS); + + iocompletionport_init(manager); /* Create the Completion Ports */ + + manager->bShutdown = false; + manager->totalSockets = 0; + manager->iocp_total = 0; + manager->maxudp = 0; + + *managerp = manager; + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_socketmgr_getmaxsockets(isc_socketmgr_t *manager, unsigned int *nsockp) { + REQUIRE(VALID_MANAGER(manager)); + REQUIRE(nsockp != NULL); + + return (ISC_R_NOTIMPLEMENTED); +} + +void +isc_socketmgr_setstats(isc_socketmgr_t *manager, isc_stats_t *stats) { + REQUIRE(VALID_MANAGER(manager)); + REQUIRE(ISC_LIST_EMPTY(manager->socklist)); + REQUIRE(manager->stats == NULL); + REQUIRE(isc_stats_ncounters(stats) == isc_sockstatscounter_max); + + isc_stats_attach(stats, &manager->stats); +} + +void +isc_socketmgr_destroy(isc_socketmgr_t **managerp) { + isc_socketmgr_t *manager; + + /* + * Destroy a socket manager. + */ + + REQUIRE(managerp != NULL); + manager = *managerp; + *managerp = NULL; + REQUIRE(VALID_MANAGER(manager)); + + LOCK(&manager->lock); + + /* + * Wait for all sockets to be destroyed. + */ + while (!ISC_LIST_EMPTY(manager->socklist)) { + manager_log(manager, CREATION, "sockets exist"); + WAIT(&manager->shutdown_ok, &manager->lock); + } + + UNLOCK(&manager->lock); + + /* + * Here, we need to had some wait code for the completion port + * thread. + */ + signal_iocompletionport_exit(manager); + manager->bShutdown = true; + + /* + * Wait for threads to exit. + */ + for (int i = 0; i < manager->maxIOCPThreads; i++) { + isc_thread_join((isc_thread_t)manager->hIOCPThreads[i], NULL); + } + /* + * Clean up. + */ + + CloseHandle(manager->hIoCompletionPort); + + (void)isc_condition_destroy(&manager->shutdown_ok); + + isc_mutex_destroy(&manager->lock); + if (manager->stats != NULL) { + isc_stats_detach(&manager->stats); + } + manager->magic = 0; + isc_mem_putanddetach(&manager->mctx, manager, sizeof(*manager)); +} + +static void +queue_receive_event(isc_socket_t *sock, isc_task_t *task, + isc_socketevent_t *dev) { + isc_task_t *ntask = NULL; + + isc_task_attach(task, &ntask); + dev->attributes |= ISC_SOCKEVENTATTR_ATTACHED; + + /* + * Enqueue the request. + */ + INSIST(!ISC_LINK_LINKED(dev, ev_link)); + ISC_LIST_ENQUEUE(sock->recv_list, dev, ev_link); + + socket_log(__LINE__, sock, NULL, EVENT, + "queue_receive_event: event %p -> task %p", dev, ntask); +} + +/* + * Check the pending receive queue, and if we have data pending, give it to this + * caller. If we have none, queue an I/O request. If this caller is not the + * first on the list, then we will just queue this event and return. + * + * Caller must have the socket locked. + */ +static isc_result_t +socket_recv(isc_socket_t *sock, isc_socketevent_t *dev, isc_task_t *task, + unsigned int flags) { + isc_result_t result = ISC_R_SUCCESS; + + dev->ev_sender = task; + + if (sock->fd == INVALID_SOCKET) { + return (ISC_R_EOF); + } + + /* + * Queue our event on the list of things to do. Call our function to + * attempt to fill buffers as much as possible, and return done events. + * We are going to lie about our handling of the ISC_SOCKFLAG_IMMEDIATE + * here and tell our caller that we could not satisfy it immediately. + */ + queue_receive_event(sock, task, dev); + if ((flags & ISC_SOCKFLAG_IMMEDIATE) != 0) { + result = ISC_R_INPROGRESS; + } + + completeio_recv(sock); + + /* + * If there are more receivers waiting for data, queue another receive + * here. If the + */ + queue_receive_request(sock); + + return (result); +} + +isc_result_t +isc_socket_recv(isc_socket_t *sock, isc_region_t *region, unsigned int minimum, + isc_task_t *task, isc_taskaction_t action, void *arg) { + isc_socketevent_t *dev; + isc_socketmgr_t *manager; + isc_result_t ret; + + REQUIRE(VALID_SOCKET(sock)); + LOCK(&sock->lock); + CONSISTENT(sock); + + /* + * make sure that the socket's not closed + */ + if (sock->fd == INVALID_SOCKET) { + UNLOCK(&sock->lock); + return (ISC_R_CONNREFUSED); + } + REQUIRE(action != NULL); + + manager = sock->manager; + REQUIRE(VALID_MANAGER(manager)); + + INSIST(sock->bound); + + dev = allocate_socketevent(manager->mctx, sock, ISC_SOCKEVENT_RECVDONE, + action, arg); + if (dev == NULL) { + UNLOCK(&sock->lock); + return (ISC_R_NOMEMORY); + } + + ret = isc_socket_recv2(sock, region, minimum, task, dev, 0); + UNLOCK(&sock->lock); + return (ret); +} + +isc_result_t +isc_socket_recv2(isc_socket_t *sock, isc_region_t *region, unsigned int minimum, + isc_task_t *task, isc_socketevent_t *event, + unsigned int flags) { + isc_result_t ret; + + REQUIRE(VALID_SOCKET(sock)); + LOCK(&sock->lock); + CONSISTENT(sock); + + event->result = ISC_R_UNEXPECTED; + event->ev_sender = sock; + /* + * make sure that the socket's not closed + */ + if (sock->fd == INVALID_SOCKET) { + UNLOCK(&sock->lock); + return (ISC_R_CONNREFUSED); + } + + event->region = *region; + event->n = 0; + event->offset = 0; + event->attributes = 0; + + /* + * UDP sockets are always partial read. + */ + if (sock->type == isc_sockettype_udp) { + event->minimum = 1; + } else { + if (minimum == 0) { + event->minimum = region->length; + } else { + event->minimum = minimum; + } + } + + ret = socket_recv(sock, event, task, flags); + UNLOCK(&sock->lock); + return (ret); +} + +/* + * Caller must have the socket locked. + */ +static isc_result_t +socket_send(isc_socket_t *sock, isc_socketevent_t *dev, isc_task_t *task, + const isc_sockaddr_t *address, struct in6_pktinfo *pktinfo, + unsigned int flags) { + int io_state; + int send_errno = 0; + int cc = 0; + isc_task_t *ntask = NULL; + isc_result_t result = ISC_R_SUCCESS; + + dev->ev_sender = task; + + set_dev_address(address, sock, dev); + if (pktinfo != NULL) { + socket_log(__LINE__, sock, NULL, TRACE, + "pktinfo structure provided, ifindex %u (set to 0)", + pktinfo->ipi6_ifindex); + + dev->attributes |= ISC_SOCKEVENTATTR_PKTINFO; + dev->pktinfo = *pktinfo; + /* + * Set the pktinfo index to 0 here, to let the kernel decide + * what interface it should send on. + */ + dev->pktinfo.ipi6_ifindex = 0; + } + + io_state = startio_send(sock, dev, &cc, &send_errno); + switch (io_state) { + case DOIO_PENDING: /* I/O started. Enqueue completion event. */ + case DOIO_SOFT: + /* + * We couldn't send all or part of the request right now, so + * queue it unless ISC_SOCKFLAG_NORETRY is set. + */ + if ((flags & ISC_SOCKFLAG_NORETRY) == 0 || + io_state == DOIO_PENDING) + { + isc_task_attach(task, &ntask); + dev->attributes |= ISC_SOCKEVENTATTR_ATTACHED; + + /* + * Enqueue the request. + */ + INSIST(!ISC_LINK_LINKED(dev, ev_link)); + ISC_LIST_ENQUEUE(sock->send_list, dev, ev_link); + + socket_log(__LINE__, sock, NULL, EVENT, + "socket_send: event %p -> task %p", dev, + ntask); + + if ((flags & ISC_SOCKFLAG_IMMEDIATE) != 0) { + result = ISC_R_INPROGRESS; + } + break; + } + + case DOIO_SUCCESS: + break; + } + + return (result); +} + +isc_result_t +isc_socket_send(isc_socket_t *sock, isc_region_t *region, isc_task_t *task, + isc_taskaction_t action, void *arg) { + /* + * REQUIRE() checking is performed in isc_socket_sendto(). + */ + return (isc_socket_sendto(sock, region, task, action, arg, NULL, NULL)); +} + +isc_result_t +isc_socket_sendto(isc_socket_t *sock, isc_region_t *region, isc_task_t *task, + isc_taskaction_t action, void *arg, + const isc_sockaddr_t *address, struct in6_pktinfo *pktinfo) { + isc_socketevent_t *dev; + isc_socketmgr_t *manager; + isc_result_t ret; + + REQUIRE(VALID_SOCKET(sock)); + + LOCK(&sock->lock); + CONSISTENT(sock); + + /* + * make sure that the socket's not closed + */ + if (sock->fd == INVALID_SOCKET) { + UNLOCK(&sock->lock); + return (ISC_R_CONNREFUSED); + } + REQUIRE(region != NULL); + REQUIRE(task != NULL); + REQUIRE(action != NULL); + + manager = sock->manager; + REQUIRE(VALID_MANAGER(manager)); + + INSIST(sock->bound); + + dev = allocate_socketevent(manager->mctx, sock, ISC_SOCKEVENT_SENDDONE, + action, arg); + if (dev == NULL) { + UNLOCK(&sock->lock); + return (ISC_R_NOMEMORY); + } + dev->region = *region; + + ret = socket_send(sock, dev, task, address, pktinfo, 0); + UNLOCK(&sock->lock); + return (ret); +} + +isc_result_t +isc_socket_sendto2(isc_socket_t *sock, isc_region_t *region, isc_task_t *task, + const isc_sockaddr_t *address, struct in6_pktinfo *pktinfo, + isc_socketevent_t *event, unsigned int flags) { + isc_result_t ret; + + REQUIRE(VALID_SOCKET(sock)); + LOCK(&sock->lock); + CONSISTENT(sock); + + REQUIRE((flags & ~(ISC_SOCKFLAG_IMMEDIATE | ISC_SOCKFLAG_NORETRY)) == + 0); + if ((flags & ISC_SOCKFLAG_NORETRY) != 0) { + REQUIRE(sock->type == isc_sockettype_udp); + } + event->ev_sender = sock; + event->result = ISC_R_UNEXPECTED; + /* + * make sure that the socket's not closed + */ + if (sock->fd == INVALID_SOCKET) { + UNLOCK(&sock->lock); + return (ISC_R_CONNREFUSED); + } + event->region = *region; + event->n = 0; + event->offset = 0; + event->attributes = 0; + + ret = socket_send(sock, event, task, address, pktinfo, flags); + UNLOCK(&sock->lock); + return (ret); +} + +isc_result_t +isc_socket_bind(isc_socket_t *sock, const isc_sockaddr_t *sockaddr, + isc_socket_options_t options) { + int bind_errno; + char strbuf[ISC_STRERRORSIZE]; + int on = 1; + + REQUIRE(VALID_SOCKET(sock)); + LOCK(&sock->lock); + CONSISTENT(sock); + + /* + * make sure that the socket's not closed + */ + if (sock->fd == INVALID_SOCKET) { + UNLOCK(&sock->lock); + return (ISC_R_CONNREFUSED); + } + + INSIST(!sock->bound); + INSIST(!sock->dupped); + + if (sock->pf != sockaddr->type.sa.sa_family) { + UNLOCK(&sock->lock); + return (ISC_R_FAMILYMISMATCH); + } + /* + * Only set SO_REUSEADDR when we want a specific port. + */ + if ((options & ISC_SOCKET_REUSEADDRESS) != 0 && + isc_sockaddr_getport(sockaddr) != (in_port_t)0 && + setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, + sizeof(on)) < 0) + { + UNEXPECTED_ERROR(__FILE__, __LINE__, "setsockopt(%d) failed", + sock->fd); + /* Press on... */ + } + if (bind(sock->fd, &sockaddr->type.sa, sockaddr->length) < 0) { + bind_errno = WSAGetLastError(); + UNLOCK(&sock->lock); + switch (bind_errno) { + case WSAEACCES: + return (ISC_R_NOPERM); + case WSAEADDRNOTAVAIL: + return (ISC_R_ADDRNOTAVAIL); + case WSAEADDRINUSE: + return (ISC_R_ADDRINUSE); + case WSAEINVAL: + return (ISC_R_BOUND); + default: + strerror_r(bind_errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, "bind: %s", + strbuf); + return (ISC_R_UNEXPECTED); + } + } + + socket_log(__LINE__, sock, sockaddr, TRACE, "bound"); + sock->bound = 1; + + UNLOCK(&sock->lock); + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_socket_filter(isc_socket_t *sock, const char *filter) { + UNUSED(sock); + UNUSED(filter); + + REQUIRE(VALID_SOCKET(sock)); + return (ISC_R_NOTIMPLEMENTED); +} + +/* + * Set up to listen on a given socket. We do this by creating an internal + * event that will be dispatched when the socket has read activity. The + * watcher will send the internal event to the task when there is a new + * connection. + * + * Unlike in read, we don't preallocate a done event here. Every time there + * is a new connection we'll have to allocate a new one anyway, so we might + * as well keep things simple rather than having to track them. + */ +isc_result_t +isc_socket_listen(isc_socket_t *sock, unsigned int backlog) { + char strbuf[ISC_STRERRORSIZE]; +#if defined(ENABLE_TCP_FASTOPEN) && defined(TCP_FASTOPEN) + char on = 1; +#endif /* if defined(ENABLE_TCP_FASTOPEN) && defined(TCP_FASTOPEN) */ + + REQUIRE(VALID_SOCKET(sock)); + + LOCK(&sock->lock); + CONSISTENT(sock); + + /* + * make sure that the socket's not closed + */ + if (sock->fd == INVALID_SOCKET) { + UNLOCK(&sock->lock); + return (ISC_R_CONNREFUSED); + } + + REQUIRE(!sock->listener); + REQUIRE(sock->bound); + REQUIRE(sock->type == isc_sockettype_tcp); + + if (backlog == 0) { + backlog = SOMAXCONN; + } + + if (listen(sock->fd, (int)backlog) < 0) { + UNLOCK(&sock->lock); + strerror_r(WSAGetLastError(), strbuf, sizeof(strbuf)); + + UNEXPECTED_ERROR(__FILE__, __LINE__, "listen: %s", strbuf); + + return (ISC_R_UNEXPECTED); + } + +#if defined(ENABLE_TCP_FASTOPEN) && defined(TCP_FASTOPEN) + if (setsockopt(sock->fd, IPPROTO_TCP, TCP_FASTOPEN, &on, sizeof(on)) < + 0) + { + strerror_r(errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, + "setsockopt(%d, TCP_FASTOPEN) failed with %s", + sock->fd, strbuf); + /* TCP_FASTOPEN is experimental so ignore failures */ + } +#endif /* if defined(ENABLE_TCP_FASTOPEN) && defined(TCP_FASTOPEN) */ + + socket_log(__LINE__, sock, NULL, TRACE, "listening"); + sock->listener = 1; + _set_state(sock, SOCK_LISTEN); + + UNLOCK(&sock->lock); + return (ISC_R_SUCCESS); +} + +/* + * This should try to do aggressive accept() XXXMLG + */ +isc_result_t +isc_socket_accept(isc_socket_t *sock, isc_task_t *task, isc_taskaction_t action, + void *arg) { + isc_socket_newconnev_t *adev; + isc_socketmgr_t *manager; + isc_task_t *ntask = NULL; + isc_socket_t *nsock; + isc_result_t result; + IoCompletionInfo *lpo; + + REQUIRE(VALID_SOCKET(sock)); + + manager = sock->manager; + REQUIRE(VALID_MANAGER(manager)); + + LOCK(&sock->lock); + CONSISTENT(sock); + + /* + * make sure that the socket's not closed + */ + if (sock->fd == INVALID_SOCKET) { + UNLOCK(&sock->lock); + return (ISC_R_CONNREFUSED); + } + + REQUIRE(sock->listener); + + /* + * Sender field is overloaded here with the task we will be sending + * this event to. Just before the actual event is delivered the + * actual ev_sender will be touched up to be the socket. + */ + adev = (isc_socket_newconnev_t *)isc_event_allocate( + manager->mctx, task, ISC_SOCKEVENT_NEWCONN, action, arg, + sizeof(*adev)); + ISC_LINK_INIT(adev, ev_link); + + result = allocate_socket(manager, sock->type, &nsock); + if (result != ISC_R_SUCCESS) { + isc_event_free((isc_event_t **)&adev); + UNLOCK(&sock->lock); + return (result); + } + + /* + * AcceptEx() requires we pass in a socket. + */ + nsock->fd = socket(sock->pf, SOCK_STREAM, IPPROTO_TCP); + if (nsock->fd == INVALID_SOCKET) { + free_socket(&nsock, __LINE__); + isc_event_free((isc_event_t **)&adev); + UNLOCK(&sock->lock); + return (ISC_R_FAILURE); /* XXXMLG need real error message */ + } + + /* + * Attach to socket and to task. + */ + isc_task_attach(task, &ntask); + if (isc_task_exiting(ntask)) { + free_socket(&nsock, __LINE__); + isc_task_detach(&ntask); + isc_event_free(ISC_EVENT_PTR(&adev)); + UNLOCK(&sock->lock); + return (ISC_R_SHUTTINGDOWN); + } + isc_refcount_increment0(&nsock->references); + + adev->ev_sender = ntask; + adev->newsocket = nsock; + _set_state(nsock, SOCK_ACCEPT); + + /* + * Queue io completion for an accept(). + */ + lpo = (IoCompletionInfo *)HeapAlloc(hHeapHandle, HEAP_ZERO_MEMORY, + sizeof(IoCompletionInfo)); + RUNTIME_CHECK(lpo != NULL); + lpo->acceptbuffer = + (void *)HeapAlloc(hHeapHandle, HEAP_ZERO_MEMORY, + (sizeof(SOCKADDR_STORAGE) + 16) * 2); + RUNTIME_CHECK(lpo->acceptbuffer != NULL); + + lpo->adev = adev; + lpo->request_type = SOCKET_ACCEPT; + + ISCAcceptEx(sock->fd, nsock->fd, /* Accepted Socket */ + lpo->acceptbuffer, /* Buffer for initial Recv */ + 0, /* Length of Buffer */ + sizeof(SOCKADDR_STORAGE) + 16, /* Local address length + 16 + */ + sizeof(SOCKADDR_STORAGE) + 16, /* Remote address length + 16 + */ + (LPDWORD)&lpo->received_bytes, /* Bytes Recved */ + (LPOVERLAPPED)lpo /* Overlapped structure */ + ); + iocompletionport_update(nsock); + + socket_log(__LINE__, sock, NULL, TRACE, "accepting for nsock %p fd %d", + nsock, nsock->fd); + + /* + * Enqueue the event + */ + ISC_LIST_ENQUEUE(sock->accept_list, adev, ev_link); + sock->pending_accept++; + sock->pending_iocp++; + + UNLOCK(&sock->lock); + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_socket_connect(isc_socket_t *sock, const isc_sockaddr_t *addr, + isc_task_t *task, isc_taskaction_t action, void *arg) { + char strbuf[ISC_STRERRORSIZE]; + isc_socket_connev_t *cdev; + isc_task_t *ntask = NULL; + isc_socketmgr_t *manager; + IoCompletionInfo *lpo; + int bind_errno; + + REQUIRE(VALID_SOCKET(sock)); + REQUIRE(addr != NULL); + REQUIRE(task != NULL); + REQUIRE(action != NULL); + + manager = sock->manager; + REQUIRE(VALID_MANAGER(manager)); + REQUIRE(addr != NULL); + + if (isc_sockaddr_ismulticast(addr)) { + return (ISC_R_MULTICAST); + } + + LOCK(&sock->lock); + CONSISTENT(sock); + + /* + * make sure that the socket's not closed + */ + if (sock->fd == INVALID_SOCKET) { + UNLOCK(&sock->lock); + return (ISC_R_CONNREFUSED); + } + + /* + * Windows sockets won't connect unless the socket is bound. + */ + if (!sock->bound) { + isc_sockaddr_t any; + + isc_sockaddr_anyofpf(&any, isc_sockaddr_pf(addr)); + if (bind(sock->fd, &any.type.sa, any.length) < 0) { + bind_errno = WSAGetLastError(); + UNLOCK(&sock->lock); + switch (bind_errno) { + case WSAEACCES: + return (ISC_R_NOPERM); + case WSAEADDRNOTAVAIL: + return (ISC_R_ADDRNOTAVAIL); + case WSAEADDRINUSE: + return (ISC_R_ADDRINUSE); + case WSAEINVAL: + return (ISC_R_BOUND); + default: + strerror_r(bind_errno, strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, "bind: %s", + strbuf); + return (ISC_R_UNEXPECTED); + } + } + sock->bound = 1; + } + + cdev = (isc_socket_connev_t *)isc_event_allocate( + manager->mctx, sock, ISC_SOCKEVENT_CONNECT, action, arg, + sizeof(*cdev)); + ISC_LINK_INIT(cdev, ev_link); + + if (sock->connected) { + INSIST(isc_sockaddr_equal(&sock->address, addr)); + cdev->result = ISC_R_SUCCESS; + isc_task_send(task, ISC_EVENT_PTR(&cdev)); + + UNLOCK(&sock->lock); + return (ISC_R_SUCCESS); + } + + if ((sock->type == isc_sockettype_tcp) && !sock->pending_connect) { + /* + * Queue io completion for an accept(). + */ + lpo = (IoCompletionInfo *)HeapAlloc(hHeapHandle, + HEAP_ZERO_MEMORY, + sizeof(IoCompletionInfo)); + lpo->cdev = cdev; + lpo->request_type = SOCKET_CONNECT; + + sock->address = *addr; + ISCConnectEx(sock->fd, &addr->type.sa, addr->length, NULL, 0, + NULL, (LPOVERLAPPED)lpo); + + /* + * Attach to task. + */ + isc_task_attach(task, &ntask); + cdev->ev_sender = ntask; + + sock->pending_connect = 1; + _set_state(sock, SOCK_CONNECT); + + /* + * Enqueue the request. + */ + INSIST(!ISC_LINK_LINKED(cdev, ev_link)); + ISC_LIST_ENQUEUE(sock->connect_list, cdev, ev_link); + sock->pending_iocp++; + } else if (sock->type == isc_sockettype_tcp) { + INSIST(sock->pending_connect); + INSIST(isc_sockaddr_equal(&sock->address, addr)); + isc_task_attach(task, &ntask); + cdev->ev_sender = ntask; + INSIST(!ISC_LINK_LINKED(cdev, ev_link)); + ISC_LIST_ENQUEUE(sock->connect_list, cdev, ev_link); + } else { + REQUIRE(!sock->pending_connect); + WSAConnect(sock->fd, &addr->type.sa, addr->length, NULL, NULL, + NULL, NULL); + cdev->result = ISC_R_SUCCESS; + isc_task_send(task, (isc_event_t **)&cdev); + } + CONSISTENT(sock); + UNLOCK(&sock->lock); + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_socket_getpeername(isc_socket_t *sock, isc_sockaddr_t *addressp) { + isc_result_t result; + + REQUIRE(VALID_SOCKET(sock)); + REQUIRE(addressp != NULL); + + LOCK(&sock->lock); + CONSISTENT(sock); + + /* + * make sure that the socket's not closed + */ + if (sock->fd == INVALID_SOCKET) { + UNLOCK(&sock->lock); + return (ISC_R_CONNREFUSED); + } + + if (sock->connected) { + *addressp = sock->address; + result = ISC_R_SUCCESS; + } else { + result = ISC_R_NOTCONNECTED; + } + + UNLOCK(&sock->lock); + + return (result); +} + +isc_result_t +isc_socket_getsockname(isc_socket_t *sock, isc_sockaddr_t *addressp) { + socklen_t len; + isc_result_t result; + char strbuf[ISC_STRERRORSIZE]; + + REQUIRE(VALID_SOCKET(sock)); + REQUIRE(addressp != NULL); + + LOCK(&sock->lock); + CONSISTENT(sock); + + /* + * make sure that the socket's not closed + */ + if (sock->fd == INVALID_SOCKET) { + UNLOCK(&sock->lock); + return (ISC_R_CONNREFUSED); + } + + if (!sock->bound) { + result = ISC_R_NOTBOUND; + goto out; + } + + result = ISC_R_SUCCESS; + + len = sizeof(addressp->type); + if (getsockname(sock->fd, &addressp->type.sa, (void *)&len) < 0) { + strerror_r(WSAGetLastError(), strbuf, sizeof(strbuf)); + UNEXPECTED_ERROR(__FILE__, __LINE__, "getsockname: %s", strbuf); + result = ISC_R_UNEXPECTED; + goto out; + } + addressp->length = (unsigned int)len; + +out: + UNLOCK(&sock->lock); + + return (result); +} + +/* + * Run through the list of events on this socket, and cancel the ones + * queued for task "task" of type "how". "how" is a bitmask. + */ +void +isc_socket_cancel(isc_socket_t *sock, isc_task_t *task, unsigned int how) { + REQUIRE(VALID_SOCKET(sock)); + + /* + * Quick exit if there is nothing to do. Don't even bother locking + * in this case. + */ + if (how == 0) { + return; + } + + LOCK(&sock->lock); + CONSISTENT(sock); + + /* + * make sure that the socket's not closed + */ + if (sock->fd == INVALID_SOCKET) { + UNLOCK(&sock->lock); + return; + } + + /* + * All of these do the same thing, more or less. + * Each will: + * o If the internal event is marked as "posted" try to + * remove it from the task's queue. If this fails, mark it + * as canceled instead, and let the task clean it up later. + * o For each I/O request for that task of that type, post + * its done event with status of "ISC_R_CANCELED". + * o Reset any state needed. + */ + + if ((how & ISC_SOCKCANCEL_RECV) != 0) { + isc_socketevent_t *dev; + isc_socketevent_t *next; + isc_task_t *current_task; + + dev = ISC_LIST_HEAD(sock->recv_list); + while (dev != NULL) { + current_task = dev->ev_sender; + next = ISC_LIST_NEXT(dev, ev_link); + if ((task == NULL) || (task == current_task)) { + dev->result = ISC_R_CANCELED; + send_recvdone_event(sock, &dev); + } + dev = next; + } + } + how &= ~ISC_SOCKCANCEL_RECV; + + if ((how & ISC_SOCKCANCEL_SEND) != 0) { + isc_socketevent_t *dev; + isc_socketevent_t *next; + isc_task_t *current_task; + + dev = ISC_LIST_HEAD(sock->send_list); + + while (dev != NULL) { + current_task = dev->ev_sender; + next = ISC_LIST_NEXT(dev, ev_link); + if ((task == NULL) || (task == current_task)) { + dev->result = ISC_R_CANCELED; + send_senddone_event(sock, &dev); + } + dev = next; + } + } + how &= ~ISC_SOCKCANCEL_SEND; + + if (((how & ISC_SOCKCANCEL_ACCEPT) != 0) && + !ISC_LIST_EMPTY(sock->accept_list)) + { + isc_socket_newconnev_t *dev; + isc_socket_newconnev_t *next; + isc_task_t *current_task; + + dev = ISC_LIST_HEAD(sock->accept_list); + while (dev != NULL) { + current_task = dev->ev_sender; + next = ISC_LIST_NEXT(dev, ev_link); + + if ((task == NULL) || (task == current_task)) { + isc_refcount_decrementz( + &dev->newsocket->references); + closesocket(dev->newsocket->fd); + dev->newsocket->fd = INVALID_SOCKET; + free_socket(&dev->newsocket, __LINE__); + + dev->result = ISC_R_CANCELED; + send_acceptdone_event(sock, &dev); + } + + dev = next; + } + } + how &= ~ISC_SOCKCANCEL_ACCEPT; + + if (((how & ISC_SOCKCANCEL_CONNECT) != 0) && + !ISC_LIST_EMPTY(sock->connect_list)) + { + isc_socket_connev_t *dev; + isc_socket_connev_t *next; + isc_task_t *current_task; + + INSIST(sock->pending_connect); + + dev = ISC_LIST_HEAD(sock->connect_list); + + while (dev != NULL) { + current_task = dev->ev_sender; + next = ISC_LIST_NEXT(dev, ev_link); + if ((task == NULL) || (task == current_task)) { + dev->result = ISC_R_CANCELED; + send_connectdone_event(sock, &dev); + } + dev = next; + } + closesocket(sock->fd); + sock->fd = INVALID_SOCKET; + _set_state(sock, SOCK_CLOSED); + } + how &= ~ISC_SOCKCANCEL_CONNECT; + UNUSED(how); + + maybe_free_socket(&sock, __LINE__); +} + +isc_sockettype_t +isc_socket_gettype(isc_socket_t *sock) { + isc_sockettype_t type; + + REQUIRE(VALID_SOCKET(sock)); + + LOCK(&sock->lock); + + /* + * make sure that the socket's not closed + */ + if (sock->fd == INVALID_SOCKET) { + UNLOCK(&sock->lock); + return (ISC_R_CONNREFUSED); + } + + type = sock->type; + UNLOCK(&sock->lock); + return (type); +} + +void +isc_socket_ipv6only(isc_socket_t *sock, bool yes) { +#if defined(IPV6_V6ONLY) + int onoff = yes ? 1 : 0; +#else /* if defined(IPV6_V6ONLY) */ + UNUSED(yes); +#endif /* if defined(IPV6_V6ONLY) */ + + REQUIRE(VALID_SOCKET(sock)); + +#ifdef IPV6_V6ONLY + if (sock->pf == AF_INET6) { + (void)setsockopt(sock->fd, IPPROTO_IPV6, IPV6_V6ONLY, + (char *)&onoff, sizeof(onoff)); + } +#endif /* ifdef IPV6_V6ONLY */ +} + +void +isc_socket_dscp(isc_socket_t *sock, isc_dscp_t dscp) { +#if !defined(IP_TOS) && !defined(IPV6_TCLASS) + UNUSED(dscp); +#else /* if !defined(IP_TOS) && !defined(IPV6_TCLASS) */ + if (dscp < 0) { + return; + } + + dscp <<= 2; + dscp &= 0xff; +#endif /* if !defined(IP_TOS) && !defined(IPV6_TCLASS) */ + + REQUIRE(VALID_SOCKET(sock)); + +#ifdef IP_TOS + if (sock->pf == AF_INET) { + (void)setsockopt(sock->fd, IPPROTO_IP, IP_TOS, (char *)&dscp, + sizeof(dscp)); + } +#endif /* ifdef IP_TOS */ +#ifdef IPV6_TCLASS + if (sock->pf == AF_INET6) { + (void)setsockopt(sock->fd, IPPROTO_IPV6, IPV6_TCLASS, + (char *)&dscp, sizeof(dscp)); + } +#endif /* ifdef IPV6_TCLASS */ +} + +void +isc_socket_cleanunix(const isc_sockaddr_t *addr, bool active) { + UNUSED(addr); + UNUSED(active); +} + +isc_result_t +isc_socket_permunix(const isc_sockaddr_t *addr, uint32_t perm, uint32_t owner, + uint32_t group) { + UNUSED(addr); + UNUSED(perm); + UNUSED(owner); + UNUSED(group); + return (ISC_R_NOTIMPLEMENTED); +} + +void +isc_socket_setname(isc_socket_t *socket, const char *name, void *tag) { + /* + * Name 'socket'. + */ + + REQUIRE(VALID_SOCKET(socket)); + + LOCK(&socket->lock); + strlcpy(socket->name, name, sizeof(socket->name)); + socket->tag = tag; + UNLOCK(&socket->lock); +} + +const char * +isc_socket_getname(isc_socket_t *socket) { + return (socket->name); +} + +void * +isc_socket_gettag(isc_socket_t *socket) { + return (socket->tag); +} + +int +isc_socket_getfd(isc_socket_t *socket) { + return ((short)socket->fd); +} + +void +isc_socketmgr_setreserved(isc_socketmgr_t *manager, uint32_t reserved) { + UNUSED(manager); + UNUSED(reserved); +} + +isc_socketevent_t * +isc_socket_socketevent(isc_mem_t *mctx, void *sender, isc_eventtype_t eventtype, + isc_taskaction_t action, void *arg) { + return (allocate_socketevent(mctx, sender, eventtype, action, arg)); +} + +bool +isc_socket_hasreuseport() { + return (false); +} + +#ifdef HAVE_LIBXML2 + +static const char * +_socktype(isc_sockettype_t type) { + switch (type) { + case isc_sockettype_udp: + return ("udp"); + case isc_sockettype_tcp: + return ("tcp"); + case isc_sockettype_unix: + return ("unix"); + default: + return ("not-initialized"); + } +} + +#define TRY0(a) \ + do { \ + xmlrc = (a); \ + if (xmlrc < 0) \ + goto error; \ + } while (0) +int +isc_socketmgr_renderxml(isc_socketmgr_t *mgr, void *writer0) { + isc_socket_t *sock = NULL; + char peerbuf[ISC_SOCKADDR_FORMATSIZE]; + isc_sockaddr_t addr; + socklen_t len; + int xmlrc; + xmlTextWriterPtr writer = (xmlTextWriterPtr)writer0; + + LOCK(&mgr->lock); + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "sockets")); + sock = ISC_LIST_HEAD(mgr->socklist); + while (sock != NULL) { + LOCK(&sock->lock); + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "socket")); + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "id")); + TRY0(xmlTextWriterWriteFormatString(writer, "%p", sock)); + TRY0(xmlTextWriterEndElement(writer)); + + if (sock->name[0] != 0) { + TRY0(xmlTextWriterStartElement(writer, + ISC_XMLCHAR "name")); + TRY0(xmlTextWriterWriteFormatString(writer, "%s", + sock->name)); + TRY0(xmlTextWriterEndElement(writer)); /* name */ + } + + TRY0(xmlTextWriterStartElement(writer, + ISC_XMLCHAR "references")); + TRY0(xmlTextWriterWriteFormatString( + writer, "%" PRIuFAST32, + isc_refcount_current(&sock->references))); + TRY0(xmlTextWriterEndElement(writer)); + + TRY0(xmlTextWriterWriteElement( + writer, ISC_XMLCHAR "type", + ISC_XMLCHAR _socktype(sock->type))); + + if (sock->connected) { + isc_sockaddr_format(&sock->address, peerbuf, + sizeof(peerbuf)); + TRY0(xmlTextWriterWriteElement( + writer, ISC_XMLCHAR "peer-address", + ISC_XMLCHAR peerbuf)); + } + + len = sizeof(addr); + if (getsockname(sock->fd, &addr.type.sa, (void *)&len) == 0) { + isc_sockaddr_format(&addr, peerbuf, sizeof(peerbuf)); + TRY0(xmlTextWriterWriteElement( + writer, ISC_XMLCHAR "local-address", + ISC_XMLCHAR peerbuf)); + } + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "states")); + if (sock->pending_recv) { + TRY0(xmlTextWriterWriteElement( + writer, ISC_XMLCHAR "state", + ISC_XMLCHAR "pending-receive")); + } + if (sock->pending_send) { + TRY0(xmlTextWriterWriteElement( + writer, ISC_XMLCHAR "state", + ISC_XMLCHAR "pending-send")); + } + if (sock->pending_accept) { + TRY0(xmlTextWriterWriteElement( + writer, ISC_XMLCHAR "state", + ISC_XMLCHAR "pending_accept")); + } + if (sock->listener) { + TRY0(xmlTextWriterWriteElement(writer, + ISC_XMLCHAR "state", + ISC_XMLCHAR "listener")); + } + if (sock->connected) { + TRY0(xmlTextWriterWriteElement( + writer, ISC_XMLCHAR "state", + ISC_XMLCHAR "connected")); + } + if (sock->pending_connect) { + TRY0(xmlTextWriterWriteElement( + writer, ISC_XMLCHAR "state", + ISC_XMLCHAR "connecting")); + } + if (sock->bound) { + TRY0(xmlTextWriterWriteElement(writer, + ISC_XMLCHAR "state", + ISC_XMLCHAR "bound")); + } + + TRY0(xmlTextWriterEndElement(writer)); /* states */ + + TRY0(xmlTextWriterEndElement(writer)); /* socket */ + + UNLOCK(&sock->lock); + sock = ISC_LIST_NEXT(sock, link); + } + TRY0(xmlTextWriterEndElement(writer)); /* sockets */ + +error: + if (sock != NULL) { + UNLOCK(&sock->lock); + } + + UNLOCK(&mgr->lock); + + return (xmlrc); +} +#endif /* HAVE_LIBXML2 */ + +#ifdef HAVE_JSON_C +#define CHECKMEM(m) \ + do { \ + if (m == NULL) { \ + result = ISC_R_NOMEMORY; \ + goto error; \ + } \ + } while (0) +isc_result_t +isc_socketmgr_renderjson(isc_socketmgr_t *mgr, void *stats0) { + isc_result_t result = ISC_R_SUCCESS; + isc_socket_t *sock = NULL; + char peerbuf[ISC_SOCKADDR_FORMATSIZE]; + isc_sockaddr_t addr; + socklen_t len; + json_object *obj, *array = json_object_new_array(); + json_object *stats = (json_object *)stats; + + CHECKMEM(array); + + LOCK(&mgr->lock); + +#ifdef USE_SHARED_MANAGER + obj = json_object_new_int(mgr->refs); + CHECKMEM(obj); + json_object_object_add(stats, "references", obj); +#endif /* USE_SHARED_MANAGER */ + + sock = ISC_LIST_HEAD(mgr->socklist); + while (sock != NULL) { + json_object *states, *entry = json_object_new_object(); + char buf[255]; + + CHECKMEM(entry); + json_object_array_add(array, entry); + + LOCK(&sock->lock); + + snprintf(buf, sizeof(buf), "%p", sock); + obj = json_object_new_string(buf); + CHECKMEM(obj); + json_object_object_add(entry, "id", obj); + + if (sock->name[0] != 0) { + obj = json_object_new_string(sock->name); + CHECKMEM(obj); + json_object_object_add(entry, "name", obj); + } + + obj = json_object_new_int( + isc_refcount_current(&sock->references)); + CHECKMEM(obj); + json_object_object_add(entry, "references", obj); + + obj = json_object_new_string(_socktype(sock->type)); + CHECKMEM(obj); + json_object_object_add(entry, "type", obj); + + if (sock->connected) { + isc_sockaddr_format(&sock->address, peerbuf, + sizeof(peerbuf)); + obj = json_object_new_string(peerbuf); + CHECKMEM(obj); + json_object_object_add(entry, "peer-address", obj); + } + + len = sizeof(addr); + if (getsockname(sock->fd, &addr.type.sa, (void *)&len) == 0) { + isc_sockaddr_format(&addr, peerbuf, sizeof(peerbuf)); + obj = json_object_new_string(peerbuf); + CHECKMEM(obj); + json_object_object_add(entry, "local-address", obj); + } + + states = json_object_new_array(); + CHECKMEM(states); + json_object_object_add(entry, "states", states); + + if (sock->pending_recv) { + obj = json_object_new_string("pending-receive"); + CHECKMEM(obj); + json_object_array_add(states, obj); + } + + if (sock->pending_send) { + obj = json_object_new_string("pending-send"); + CHECKMEM(obj); + json_object_array_add(states, obj); + } + + if (sock->pending_accept) { + obj = json_object_new_string("pending-accept"); + CHECKMEM(obj); + json_object_array_add(states, obj); + } + + if (sock->listener) { + obj = json_object_new_string("listener"); + CHECKMEM(obj); + json_object_array_add(states, obj); + } + + if (sock->connected) { + obj = json_object_new_string("connected"); + CHECKMEM(obj); + json_object_array_add(states, obj); + } + + if (sock->pending_connect) { + obj = json_object_new_string("connecting"); + CHECKMEM(obj); + json_object_array_add(states, obj); + } + + if (sock->bound) { + obj = json_object_new_string("bound"); + CHECKMEM(obj); + json_object_array_add(states, obj); + } + + UNLOCK(&sock->lock); + sock = ISC_LIST_NEXT(sock, link); + } + + json_object_object_add(stats, "sockets", array); + array = NULL; + result = ISC_R_SUCCESS; + +error: + if (array != NULL) { + json_object_put(array); + } + + if (sock != NULL) { + UNLOCK(&sock->lock); + } + + UNLOCK(&mgr->lock); + + return (result); +} +#endif /* HAVE_JSON_C */ + +void +isc_socketmgr_maxudp(isc_socketmgr_t *manager, unsigned int maxudp) { + REQUIRE(VALID_MANAGER(manager)); + + manager->maxudp = maxudp; +} diff --git a/lib/isc/win32/stdio.c b/lib/isc/win32/stdio.c new file mode 100644 index 0000000..1e55c30 --- /dev/null +++ b/lib/isc/win32/stdio.c @@ -0,0 +1,158 @@ +/* + * 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. + */ + +#include <errno.h> +#include <io.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <isc/stdio.h> +#include <isc/util.h> + +#include "errno2result.h" + +isc_result_t +isc_stdio_open(const char *filename, const char *mode, FILE **fp) { + FILE *f; + + f = fopen(filename, mode); + if (f == NULL) { + return (isc__errno2result(errno)); + } + *fp = f; + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_stdio_close(FILE *f) { + int r; + + r = fclose(f); + if (r == 0) { + return (ISC_R_SUCCESS); + } else { + return (isc__errno2result(errno)); + } +} + +isc_result_t +isc_stdio_seek(FILE *f, off_t offset, int whence) { + int r; + +#ifndef _WIN64 + r = fseek(f, offset, whence); +#else /* ifndef _WIN64 */ + r = _fseeki64(f, offset, whence); +#endif /* ifndef _WIN64 */ + if (r == 0) { + return (ISC_R_SUCCESS); + } else { + return (isc__errno2result(errno)); + } +} + +isc_result_t +isc_stdio_tell(FILE *f, off_t *offsetp) { +#ifndef _WIN64 + long r; +#else /* ifndef _WIN64 */ + __int64 r; +#endif /* ifndef _WIN64 */ + + REQUIRE(offsetp != NULL); + +#ifndef _WIN64 + r = ftell(f); +#else /* ifndef _WIN64 */ + r = _ftelli64(f); +#endif /* ifndef _WIN64 */ + if (r >= 0) { + *offsetp = r; + return (ISC_R_SUCCESS); + } else { + return (isc__errno2result(errno)); + } +} + +isc_result_t +isc_stdio_read(void *ptr, size_t size, size_t nmemb, FILE *f, size_t *nret) { + isc_result_t result = ISC_R_SUCCESS; + size_t r; + + clearerr(f); + r = fread(ptr, size, nmemb, f); + if (r != nmemb) { + if (feof(f)) { + result = ISC_R_EOF; + } else { + result = isc__errno2result(errno); + } + } + if (nret != NULL) { + *nret = r; + } + return (result); +} + +isc_result_t +isc_stdio_write(const void *ptr, size_t size, size_t nmemb, FILE *f, + size_t *nret) { + isc_result_t result = ISC_R_SUCCESS; + size_t r; + + clearerr(f); + r = fwrite(ptr, size, nmemb, f); + if (r != nmemb) { + result = isc__errno2result(errno); + } + if (nret != NULL) { + *nret = r; + } + return (result); +} + +isc_result_t +isc_stdio_flush(FILE *f) { + int r; + + r = fflush(f); + if (r == 0) { + return (ISC_R_SUCCESS); + } else { + return (isc__errno2result(errno)); + } +} + +isc_result_t +isc_stdio_sync(FILE *f) { + struct _stat buf; + int r; + + if (_fstat(_fileno(f), &buf) != 0) { + return (isc__errno2result(errno)); + } + + /* + * Only call _commit() on regular files. + */ + if ((buf.st_mode & S_IFMT) != S_IFREG) { + return (ISC_R_SUCCESS); + } + + r = _commit(_fileno(f)); + if (r == 0) { + return (ISC_R_SUCCESS); + } else { + return (isc__errno2result(errno)); + } +} diff --git a/lib/isc/win32/stdtime.c b/lib/isc/win32/stdtime.c new file mode 100644 index 0000000..93fcd66 --- /dev/null +++ b/lib/isc/win32/stdtime.c @@ -0,0 +1,43 @@ +/* + * 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. + */ + +#include <time.h> + +#include <isc/assertions.h> +#include <isc/stdtime.h> +#include <isc/util.h> + +void +isc_stdtime_get(isc_stdtime_t *t) { + /* + * Set 't' to the number of seconds past 00:00:00 UTC, January 1, 1970. + */ + + REQUIRE(t != NULL); + + (void)_time32(t); +} + +void +isc_stdtime_tostring(isc_stdtime_t t, char *out, size_t outlen) { + time_t when; + + REQUIRE(out != NULL); + /* Minimum buffer as per ctime_r() specification. */ + REQUIRE(outlen >= 26); + + /* time_t and isc_stdtime_t might be different sizes */ + when = t; + INSIST((ctime_s(out, outlen, &when) == 0)); + *(out + strlen(out) - 1) = '\0'; +} diff --git a/lib/isc/win32/syslog.c b/lib/isc/win32/syslog.c new file mode 100644 index 0000000..ad23a0f --- /dev/null +++ b/lib/isc/win32/syslog.c @@ -0,0 +1,171 @@ +/* + * 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. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <windows.h> + +#include <isc/bindevt.h> +#include <isc/result.h> +#include <isc/syslog.h> +#include <isc/util.h> + +static HANDLE hAppLog = NULL; +static FILE *log_stream; +static int debug_level = 0; + +static struct dsn_c_pvt_sfnt { + int val; + const char *strval; +} facilities[] = { { LOG_KERN, "kern" }, + { LOG_USER, "user" }, + { LOG_MAIL, "mail" }, + { LOG_DAEMON, "daemon" }, + { LOG_AUTH, "auth" }, + { LOG_SYSLOG, "syslog" }, + { LOG_LPR, "lpr" }, +#ifdef LOG_NEWS + { LOG_NEWS, "news" }, +#endif /* ifdef LOG_NEWS */ +#ifdef LOG_UUCP + { LOG_UUCP, "uucp" }, +#endif /* ifdef LOG_UUCP */ +#ifdef LOG_CRON + { LOG_CRON, "cron" }, +#endif /* ifdef LOG_CRON */ +#ifdef LOG_AUTHPRIV + { LOG_AUTHPRIV, "authpriv" }, +#endif /* ifdef LOG_AUTHPRIV */ +#ifdef LOG_FTP + { LOG_FTP, "ftp" }, +#endif /* ifdef LOG_FTP */ + { LOG_LOCAL0, "local0" }, + { LOG_LOCAL1, "local1" }, + { LOG_LOCAL2, "local2" }, + { LOG_LOCAL3, "local3" }, + { LOG_LOCAL4, "local4" }, + { LOG_LOCAL5, "local5" }, + { LOG_LOCAL6, "local6" }, + { LOG_LOCAL7, "local7" }, + { 0, NULL } }; + +isc_result_t +isc_syslog_facilityfromstring(const char *str, int *facilityp) { + int i; + + REQUIRE(str != NULL); + REQUIRE(facilityp != NULL); + + for (i = 0; facilities[i].strval != NULL; i++) { + if (strcasecmp(facilities[i].strval, str) == 0) { + *facilityp = facilities[i].val; + return (ISC_R_SUCCESS); + } + } + return (ISC_R_NOTFOUND); +} + +/* + * Log to the NT Event Log + */ +void +syslog(int level, const char *fmt, ...) { + va_list ap; + char buf[1024]; + char *str[1]; + + str[0] = buf; + + va_start(ap, fmt); + vsprintf(buf, fmt, ap); + va_end(ap); + + /* Make sure that the channel is open to write the event */ + if (hAppLog != NULL) { + switch (level) { + case LOG_INFO: + case LOG_NOTICE: + case LOG_DEBUG: + ReportEvent(hAppLog, EVENTLOG_INFORMATION_TYPE, 0, + BIND_INFO_MSG, NULL, 1, 0, str, NULL); + break; + case LOG_WARNING: + ReportEvent(hAppLog, EVENTLOG_WARNING_TYPE, 0, + BIND_WARN_MSG, NULL, 1, 0, str, NULL); + break; + default: + ReportEvent(hAppLog, EVENTLOG_ERROR_TYPE, 0, + BIND_ERR_MSG, NULL, 1, 0, str, NULL); + break; + } + } +} + +/* + * Initialize event logging + */ +void +openlog(const char *name, int flags, ...) { + /* Get a handle to the Application event log */ + hAppLog = RegisterEventSource(NULL, name); +} + +/* + * Close the Handle to the application Event Log + * We don't care whether or not we succeeded so ignore return values + * In fact if we failed then we would have nowhere to put the message + */ +void +closelog(void) { + DeregisterEventSource(hAppLog); +} + +/* + * Keep event logging synced with the current debug level + */ +void +ModifyLogLevel(int level) { + debug_level = level; +} + +/* + * Initialize logging for the port section of libbind. + * Piggyback onto stream given. + */ +void +InitNTLogging(FILE *stream, int debug) { + log_stream = stream; + ModifyLogLevel(debug); +} +/* + * This function is for reporting errors to the application + * event log in case the regular syslog is not available + * mainly during startup. It should not be used under normal + * circumstances. + */ +void +NTReportError(const char *name, const char *str) { + HANDLE hNTAppLog = NULL; + const char *buf[1]; + + buf[0] = str; + + hNTAppLog = RegisterEventSource(NULL, name); + + ReportEvent(hNTAppLog, EVENTLOG_ERROR_TYPE, 0, BIND_ERR_MSG, NULL, 1, 0, + buf, NULL); + + DeregisterEventSource(hNTAppLog); +} diff --git a/lib/isc/win32/syslog.h b/lib/isc/win32/syslog.h new file mode 100644 index 0000000..e785144 --- /dev/null +++ b/lib/isc/win32/syslog.h @@ -0,0 +1,70 @@ +/* + * 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. + */ + +#ifndef _SYSLOG_H +#define _SYSLOG_H + +#include <stdio.h> + +/* Constant definitions for openlog() */ +#define LOG_PID 1 +#define LOG_CONS 2 +/* NT event log does not support facility level */ +#define LOG_KERN 0 +#define LOG_USER 0 +#define LOG_MAIL 0 +#define LOG_DAEMON 0 +#define LOG_AUTH 0 +#define LOG_SYSLOG 0 +#define LOG_LPR 0 +#define LOG_LOCAL0 0 +#define LOG_LOCAL1 0 +#define LOG_LOCAL2 0 +#define LOG_LOCAL3 0 +#define LOG_LOCAL4 0 +#define LOG_LOCAL5 0 +#define LOG_LOCAL6 0 +#define LOG_LOCAL7 0 + +#define LOG_EMERG 0 /* system is unusable */ +#define LOG_ALERT 1 /* action must be taken immediately */ +#define LOG_CRIT 2 /* critical conditions */ +#define LOG_ERR 3 /* error conditions */ +#define LOG_WARNING 4 /* warning conditions */ +#define LOG_NOTICE 5 /* normal but signification condition */ +#define LOG_INFO 6 /* informational */ +#define LOG_DEBUG 7 /* debug-level messages */ + +void +syslog(int level, const char *fmt, ...); + +void +openlog(const char *, int, ...); + +void +closelog(void); + +void +ModifyLogLevel(int level); + +void +InitNTLogging(FILE *, int); + +void +NTReportError(const char *, const char *); +/* + * Include the event codes required for logging. + */ +#include <isc/bindevt.h> + +#endif /* ifndef _SYSLOG_H */ diff --git a/lib/isc/win32/thread.c b/lib/isc/win32/thread.c new file mode 100644 index 0000000..e373e75 --- /dev/null +++ b/lib/isc/win32/thread.c @@ -0,0 +1,82 @@ +/* + * 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. + */ + +#include <process.h> + +#include <isc/mutex.h> +#include <isc/once.h> +#include <isc/strerr.h> +#include <isc/thread.h> +#include <isc/util.h> + +#include "trampoline_p.h" + +void +isc_thread_create(isc_threadfunc_t start, isc_threadarg_t arg, + isc_thread_t *threadp) { + isc_thread_t thread; + unsigned int id; + isc__trampoline_t *trampoline_arg; + + trampoline_arg = isc__trampoline_get(start, arg); + + thread = (isc_thread_t)_beginthreadex(NULL, 0, isc__trampoline_run, + trampoline_arg, 0, &id); + if (thread == NULL) { + char strbuf[ISC_STRERRORSIZE]; + strerror_r(errno, strbuf, sizeof(strbuf)); + isc_error_fatal(__FILE__, __LINE__, "_beginthreadex failed: %s", + strbuf); + } + + *threadp = thread; + + return; +} + +void +isc_thread_join(isc_thread_t thread, isc_threadresult_t *rp) { + DWORD result; + + result = WaitForSingleObject(thread, INFINITE); + if (result != WAIT_OBJECT_0) { + isc_error_fatal(__FILE__, __LINE__, + "WaitForSingleObject() != WAIT_OBJECT_0"); + } + if (rp != NULL && !GetExitCodeThread(thread, rp)) { + isc_error_fatal(__FILE__, __LINE__, + "GetExitCodeThread() failed: %d", + GetLastError()); + } + (void)CloseHandle(thread); +} + +void +isc_thread_setconcurrency(unsigned int level) { + /* + * This is unnecessary on Win32 systems, but is here so that the + * call exists + */ +} + +void +isc_thread_setname(isc_thread_t thread, const char *name) { + UNUSED(thread); + UNUSED(name); +} + +isc_result_t +isc_thread_setaffinity(int cpu) { + /* no-op on Windows for now */ + return (ISC_R_SUCCESS); +} diff --git a/lib/isc/win32/time.c b/lib/isc/win32/time.c new file mode 100644 index 0000000..c29edd3 --- /dev/null +++ b/lib/isc/win32/time.c @@ -0,0 +1,651 @@ +/* + * 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. + */ + +#include <errno.h> +#include <inttypes.h> +#include <limits.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <windows.h> + +#include <isc/assertions.h> +#include <isc/once.h> +#include <isc/string.h> +#include <isc/time.h> +#include <isc/tm.h> +#include <isc/util.h> + +/* + * struct FILETIME uses "100-nanoseconds intervals". + * NS / S = 1000000000 (10^9). + * While it is reasonably obvious that this makes the needed + * conversion factor 10^7, it is coded this way for additional clarity. + */ +#define NS_PER_S 1000000000 +#define NS_INTERVAL 100 +#define INTERVALS_PER_S (NS_PER_S / NS_INTERVAL) + +/*** + *** Absolute Times + ***/ + +static const isc_time_t epoch = { { 0, 0 } }; +LIBISC_EXTERNAL_DATA const isc_time_t *const isc_time_epoch = &epoch; + +/*** + *** Intervals + ***/ + +static const isc_interval_t zero_interval = { 0 }; +LIBISC_EXTERNAL_DATA const isc_interval_t *const isc_interval_zero = + &zero_interval; + +void +isc_interval_set(isc_interval_t *i, unsigned int seconds, + unsigned int nanoseconds) { + REQUIRE(i != NULL); + REQUIRE(nanoseconds < NS_PER_S); + + /* + * This rounds nanoseconds up not down. + */ + i->interval = (LONGLONG)seconds * INTERVALS_PER_S + + (nanoseconds + NS_INTERVAL - 1) / NS_INTERVAL; +} + +bool +isc_interval_iszero(const isc_interval_t *i) { + REQUIRE(i != NULL); + if (i->interval == 0) { + return (true); + } + + return (false); +} + +void +isc_time_set(isc_time_t *t, unsigned int seconds, unsigned int nanoseconds) { + SYSTEMTIME epoch1970 = { 1970, 1, 4, 1, 0, 0, 0, 0 }; + FILETIME temp; + ULARGE_INTEGER i1; + + REQUIRE(t != NULL); + REQUIRE(nanoseconds < NS_PER_S); + + SystemTimeToFileTime(&epoch1970, &temp); + + i1.LowPart = temp.dwLowDateTime; + i1.HighPart = temp.dwHighDateTime; + + /* cppcheck-suppress unreadVariable */ + i1.QuadPart += (unsigned __int64)nanoseconds / 100; + /* cppcheck-suppress unreadVariable */ + i1.QuadPart += (unsigned __int64)seconds * 10000000; + + t->absolute.dwLowDateTime = i1.LowPart; + t->absolute.dwHighDateTime = i1.HighPart; +} + +void +isc_time_settoepoch(isc_time_t *t) { + REQUIRE(t != NULL); + + t->absolute.dwLowDateTime = 0; + t->absolute.dwHighDateTime = 0; +} + +bool +isc_time_isepoch(const isc_time_t *t) { + REQUIRE(t != NULL); + + if (t->absolute.dwLowDateTime == 0 && t->absolute.dwHighDateTime == 0) { + return (true); + } + + return (false); +} + +isc_result_t +isc_time_now(isc_time_t *t) { + REQUIRE(t != NULL); + + GetSystemTimeAsFileTime(&t->absolute); + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_time_now_hires(isc_time_t *t) { + REQUIRE(t != NULL); + + GetSystemTimePreciseAsFileTime(&t->absolute); + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_time_nowplusinterval(isc_time_t *t, const isc_interval_t *i) { + ULARGE_INTEGER i1; + + REQUIRE(t != NULL); + REQUIRE(i != NULL); + + GetSystemTimeAsFileTime(&t->absolute); + + i1.LowPart = t->absolute.dwLowDateTime; + i1.HighPart = t->absolute.dwHighDateTime; + + if (UINT64_MAX - i1.QuadPart < (unsigned __int64)i->interval) { + return (ISC_R_RANGE); + } + + /* cppcheck-suppress unreadVariable */ + i1.QuadPart += i->interval; + + t->absolute.dwLowDateTime = i1.LowPart; + t->absolute.dwHighDateTime = i1.HighPart; + + return (ISC_R_SUCCESS); +} + +int +isc_time_compare(const isc_time_t *t1, const isc_time_t *t2) { + REQUIRE(t1 != NULL && t2 != NULL); + + return ((int)CompareFileTime(&t1->absolute, &t2->absolute)); +} + +isc_result_t +isc_time_add(const isc_time_t *t, const isc_interval_t *i, isc_time_t *result) { + ULARGE_INTEGER i1; + + REQUIRE(t != NULL && i != NULL && result != NULL); + + i1.LowPart = t->absolute.dwLowDateTime; + i1.HighPart = t->absolute.dwHighDateTime; + + if (UINT64_MAX - i1.QuadPart < (unsigned __int64)i->interval) { + return (ISC_R_RANGE); + } + + /* cppcheck-suppress unreadVariable */ + i1.QuadPart += i->interval; + + result->absolute.dwLowDateTime = i1.LowPart; + result->absolute.dwHighDateTime = i1.HighPart; + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_time_subtract(const isc_time_t *t, const isc_interval_t *i, + isc_time_t *result) { + ULARGE_INTEGER i1; + + REQUIRE(t != NULL && i != NULL && result != NULL); + + i1.LowPart = t->absolute.dwLowDateTime; + i1.HighPart = t->absolute.dwHighDateTime; + + if (i1.QuadPart < (unsigned __int64)i->interval) { + return (ISC_R_RANGE); + } + + /* cppcheck-suppress unreadVariable */ + i1.QuadPart -= i->interval; + + result->absolute.dwLowDateTime = i1.LowPart; + result->absolute.dwHighDateTime = i1.HighPart; + + return (ISC_R_SUCCESS); +} + +uint64_t +isc_time_microdiff(const isc_time_t *t1, const isc_time_t *t2) { + ULARGE_INTEGER i1, i2; + LONGLONG i3; + + REQUIRE(t1 != NULL && t2 != NULL); + + /* cppcheck-suppress unreadVariable */ + i1.LowPart = t1->absolute.dwLowDateTime; + /* cppcheck-suppress unreadVariable */ + i1.HighPart = t1->absolute.dwHighDateTime; + /* cppcheck-suppress unreadVariable */ + i2.LowPart = t2->absolute.dwLowDateTime; + /* cppcheck-suppress unreadVariable */ + i2.HighPart = t2->absolute.dwHighDateTime; + + if (i1.QuadPart <= i2.QuadPart) { + return (0); + } + + /* + * Convert to microseconds. + */ + i3 = (i1.QuadPart - i2.QuadPart) / 10; + + return (i3); +} + +uint32_t +isc_time_seconds(const isc_time_t *t) { + SYSTEMTIME epoch1970 = { 1970, 1, 4, 1, 0, 0, 0, 0 }; + FILETIME temp; + ULARGE_INTEGER i1, i2; + LONGLONG i3; + + SystemTimeToFileTime(&epoch1970, &temp); + + /* cppcheck-suppress unreadVariable */ + i1.LowPart = t->absolute.dwLowDateTime; + /* cppcheck-suppress unreadVariable */ + i1.HighPart = t->absolute.dwHighDateTime; + /* cppcheck-suppress unreadVariable */ + i2.LowPart = temp.dwLowDateTime; + /* cppcheck-suppress unreadVariable */ + i2.HighPart = temp.dwHighDateTime; + + i3 = (i1.QuadPart - i2.QuadPart) / 10000000; + + return ((uint32_t)i3); +} + +isc_result_t +isc_time_secondsastimet(const isc_time_t *t, time_t *secondsp) { + time_t seconds; + + REQUIRE(t != NULL); + + seconds = (time_t)isc_time_seconds(t); + + INSIST(sizeof(unsigned int) == sizeof(uint32_t)); + INSIST(sizeof(time_t) >= sizeof(uint32_t)); + + if (isc_time_seconds(t) > (~0U >> 1) && seconds <= (time_t)(~0U >> 1)) { + return (ISC_R_RANGE); + } + + *secondsp = seconds; + + return (ISC_R_SUCCESS); +} + +uint32_t +isc_time_nanoseconds(const isc_time_t *t) { + ULARGE_INTEGER i; + + i.LowPart = t->absolute.dwLowDateTime; + i.HighPart = t->absolute.dwHighDateTime; + return ((uint32_t)(i.QuadPart % 10000000) * 100); +} + +void +isc_time_formattimestamp(const isc_time_t *t, char *buf, unsigned int len) { + FILETIME localft; + SYSTEMTIME st; + char DateBuf[50]; + char TimeBuf[50]; + + REQUIRE(t != NULL); + REQUIRE(buf != NULL); + REQUIRE(len > 0); + + if (FileTimeToLocalFileTime(&t->absolute, &localft) && + FileTimeToSystemTime(&localft, &st)) + { + GetDateFormat(LOCALE_USER_DEFAULT, 0, &st, "dd-MMM-yyyy", + DateBuf, 50); + GetTimeFormat(LOCALE_USER_DEFAULT, + TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT, &st, + NULL, TimeBuf, 50); + + snprintf(buf, len, "%s %s.%03u", DateBuf, TimeBuf, + st.wMilliseconds); + } else { + strlcpy(buf, "99-Bad-9999 99:99:99.999", len); + } +} + +void +isc_time_formathttptimestamp(const isc_time_t *t, char *buf, unsigned int len) { + SYSTEMTIME st; + char DateBuf[50]; + char TimeBuf[50]; + + /* strftime() format: "%a, %d %b %Y %H:%M:%S GMT" */ + + REQUIRE(t != NULL); + REQUIRE(buf != NULL); + REQUIRE(len > 0); + + if (FileTimeToSystemTime(&t->absolute, &st)) { + GetDateFormat(LOCALE_USER_DEFAULT, 0, &st, "ddd',' dd MMM yyyy", + DateBuf, 50); + GetTimeFormat(LOCALE_USER_DEFAULT, + TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT, &st, + "hh':'mm':'ss", TimeBuf, 50); + + snprintf(buf, len, "%s %s GMT", DateBuf, TimeBuf); + } else { + buf[0] = 0; + } +} + +isc_result_t +isc_time_parsehttptimestamp(char *buf, isc_time_t *t) { + struct tm t_tm; + time_t when; + char *p; + + REQUIRE(buf != NULL); + REQUIRE(t != NULL); + + p = isc_tm_strptime(buf, "%a, %d %b %Y %H:%M:%S", &t_tm); + if (p == NULL) { + return (ISC_R_UNEXPECTED); + } + when = isc_tm_timegm(&t_tm); + if (when == -1) { + return (ISC_R_UNEXPECTED); + } + isc_time_set(t, (unsigned int)when, 0); + return (ISC_R_SUCCESS); +} + +void +isc_time_formatISO8601L(const isc_time_t *t, char *buf, unsigned int len) { + SYSTEMTIME st; + char DateBuf[50]; + char TimeBuf[50]; + + /* strtime() format: "%Y-%m-%dT%H:%M:%S" */ + + REQUIRE(t != NULL); + REQUIRE(buf != NULL); + REQUIRE(len > 0); + + if (FileTimeToSystemTime(&t->absolute, &st)) { + GetDateFormat(LOCALE_USER_DEFAULT, 0, &st, "yyyy-MM-dd", + DateBuf, 50); + GetTimeFormat(LOCALE_USER_DEFAULT, + TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT, &st, + "hh':'mm':'ss", TimeBuf, 50); + snprintf(buf, len, "%sT%s", DateBuf, TimeBuf); + } else { + buf[0] = 0; + } +} + +void +isc_time_formatISO8601Lms(const isc_time_t *t, char *buf, unsigned int len) { + SYSTEMTIME st; + char DateBuf[50]; + char TimeBuf[50]; + + /* strtime() format: "%Y-%m-%dT%H:%M:%S.SSS" */ + + REQUIRE(t != NULL); + REQUIRE(buf != NULL); + REQUIRE(len > 0); + + if (FileTimeToSystemTime(&t->absolute, &st)) { + GetDateFormat(LOCALE_USER_DEFAULT, 0, &st, "yyyy-MM-dd", + DateBuf, 50); + GetTimeFormat(LOCALE_USER_DEFAULT, + TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT, &st, + "hh':'mm':'ss", TimeBuf, 50); + snprintf(buf, len, "%sT%s.%03u", DateBuf, TimeBuf, + st.wMilliseconds); + } else { + buf[0] = 0; + } +} + +void +isc_time_formatISO8601Lus(const isc_time_t *t, char *buf, unsigned int len) { + SYSTEMTIME st; + char DateBuf[50]; + char TimeBuf[50]; + + /* strtime() format: "%Y-%m-%dT%H:%M:%S.SSSSSS" */ + + REQUIRE(t != NULL); + REQUIRE(buf != NULL); + REQUIRE(len > 0); + + if (FileTimeToSystemTime(&t->absolute, &st)) { + ULARGE_INTEGER i; + + GetDateFormat(LOCALE_USER_DEFAULT, 0, &st, "yyyy-MM-dd", + DateBuf, 50); + GetTimeFormat(LOCALE_USER_DEFAULT, + TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT, &st, + "hh':'mm':'ss", TimeBuf, 50); + i.LowPart = t->absolute.dwLowDateTime; + i.HighPart = t->absolute.dwHighDateTime; + snprintf(buf, len, "%sT%s.%06u", DateBuf, TimeBuf, + (uint32_t)(i.QuadPart % 10000000) / 10); + } else { + buf[0] = 0; + } +} + +void +isc_time_formatISO8601(const isc_time_t *t, char *buf, unsigned int len) { + SYSTEMTIME st; + char DateBuf[50]; + char TimeBuf[50]; + + /* strtime() format: "%Y-%m-%dT%H:%M:%SZ" */ + + REQUIRE(t != NULL); + REQUIRE(buf != NULL); + REQUIRE(len > 0); + + if (FileTimeToSystemTime(&t->absolute, &st)) { + GetDateFormat(LOCALE_NEUTRAL, 0, &st, "yyyy-MM-dd", DateBuf, + 50); + GetTimeFormat(LOCALE_NEUTRAL, + TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT, &st, + "hh':'mm':'ss", TimeBuf, 50); + snprintf(buf, len, "%sT%sZ", DateBuf, TimeBuf); + } else { + buf[0] = 0; + } +} + +void +isc_time_formatISO8601ms(const isc_time_t *t, char *buf, unsigned int len) { + SYSTEMTIME st; + char DateBuf[50]; + char TimeBuf[50]; + + /* strtime() format: "%Y-%m-%dT%H:%M:%S.SSSZ" */ + + REQUIRE(t != NULL); + REQUIRE(buf != NULL); + REQUIRE(len > 0); + + if (FileTimeToSystemTime(&t->absolute, &st)) { + GetDateFormat(LOCALE_NEUTRAL, 0, &st, "yyyy-MM-dd", DateBuf, + 50); + GetTimeFormat(LOCALE_NEUTRAL, + TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT, &st, + "hh':'mm':'ss", TimeBuf, 50); + snprintf(buf, len, "%sT%s.%03uZ", DateBuf, TimeBuf, + st.wMilliseconds); + } else { + buf[0] = 0; + } +} + +void +isc_time_formatISO8601us(const isc_time_t *t, char *buf, unsigned int len) { + SYSTEMTIME st; + char DateBuf[50]; + char TimeBuf[50]; + + /* strtime() format: "%Y-%m-%dT%H:%M:%S.SSSSSSZ" */ + + REQUIRE(t != NULL); + REQUIRE(buf != NULL); + REQUIRE(len > 0); + + if (FileTimeToSystemTime(&t->absolute, &st)) { + ULARGE_INTEGER i; + + GetDateFormat(LOCALE_NEUTRAL, 0, &st, "yyyy-MM-dd", DateBuf, + 50); + GetTimeFormat(LOCALE_NEUTRAL, + TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT, &st, + "hh':'mm':'ss", TimeBuf, 50); + i.LowPart = t->absolute.dwLowDateTime; + i.HighPart = t->absolute.dwHighDateTime; + snprintf(buf, len, "%sT%s.%06uZ", DateBuf, TimeBuf, + (uint32_t)(i.QuadPart % 10000000) / 10); + } else { + buf[0] = 0; + } +} + +void +isc_time_formatshorttimestamp(const isc_time_t *t, char *buf, + unsigned int len) { + SYSTEMTIME st; + char DateBuf[50]; + char TimeBuf[50]; + + /* strtime() format: "%Y%m%d%H%M%SSSS" */ + + REQUIRE(t != NULL); + REQUIRE(buf != NULL); + REQUIRE(len > 0); + + if (FileTimeToSystemTime(&t->absolute, &st)) { + GetDateFormat(LOCALE_NEUTRAL, 0, &st, "yyyyMMdd", DateBuf, 50); + GetTimeFormat(LOCALE_NEUTRAL, + TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT, &st, + "hhmmss", TimeBuf, 50); + snprintf(buf, len, "%s%s%03u", DateBuf, TimeBuf, + st.wMilliseconds); + } else { + buf[0] = 0; + } +} + +/* + * POSIX Shims + */ + +struct tm * +gmtime_r(const time_t *clock, struct tm *result) { + errno_t ret = gmtime_s(result, clock); + if (ret != 0) { + errno = ret; + return (NULL); + } + return (result); +} + +struct tm * +localtime_r(const time_t *clock, struct tm *result) { + errno_t ret = localtime_s(result, clock); + if (ret != 0) { + errno = ret; + return (NULL); + } + return (result); +} + +#define BILLION 1000000000 + +static isc_once_t nsec_ticks_once = ISC_ONCE_INIT; +static double nsec_ticks = 0; + +static void +nsec_ticks_init(void) { + LARGE_INTEGER ticks; + RUNTIME_CHECK(QueryPerformanceFrequency(&ticks) != 0); + nsec_ticks = (double)ticks.QuadPart / 1000000000.0; + RUNTIME_CHECK(nsec_ticks != 0.0); +} + +int +nanosleep(const struct timespec *req, struct timespec *rem) { + int_fast64_t sleep_msec; + uint_fast64_t ticks, until; + LARGE_INTEGER before, after; + + RUNTIME_CHECK(isc_once_do(&nsec_ticks_once, nsec_ticks_init) == + ISC_R_SUCCESS); + + if (req->tv_nsec < 0 || BILLION <= req->tv_nsec) { + errno = EINVAL; + return (-1); + } + + /* Sleep() is not interruptible; there is no remaining delay ever */ + if (rem != NULL) { + rem->tv_sec = 0; + rem->tv_nsec = 0; + } + + if (req->tv_sec >= 0) { + /* + * For requested delays of one second or more, 15ms resolution + * granularity is sufficient. + */ + Sleep(req->tv_sec * 1000 + req->tv_nsec / 1000000); + + return (0); + } + + /* Sleep has <-8,8> ms precision, so substract 10 milliseconds */ + sleep_msec = (int64_t)req->tv_nsec / 1000000 - 10; + ticks = req->tv_nsec * nsec_ticks; + + RUNTIME_CHECK(QueryPerformanceCounter(&before) != 0); + + until = before.QuadPart + ticks; + + if (sleep_msec > 0) { + Sleep(sleep_msec); + } + + while (true) { + LARGE_INTEGER after; + RUNTIME_CHECK(QueryPerformanceCounter(&after) != 0); + if (after.QuadPart >= until) { + break; + } + } + +done: + return (0); +} + +int +usleep(useconds_t useconds) { + struct timespec req; + + req.tv_sec = useconds / 1000000; + req.tv_nsec = (useconds * 1000) % 1000000000; + + nanosleep(&req, NULL); + + return (0); +} diff --git a/lib/isc/win32/unistd.h b/lib/isc/win32/unistd.h new file mode 100644 index 0000000..e1aff40 --- /dev/null +++ b/lib/isc/win32/unistd.h @@ -0,0 +1,55 @@ +/* + * 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. + */ + +/* None of these are defined in NT, so define them for our use */ +#define O_NONBLOCK 1 +#define PORT_NONBLOCK O_NONBLOCK + +/* + * fcntl() commands + */ +#define F_SETFL 0 +#define F_GETFL 1 +#define F_SETFD 2 +#define F_GETFD 3 +/* + * Enough problems not having full fcntl() without worrying about this! + */ +#undef F_DUPFD + +int +fcntl(int, int, ...); + +/* + * access() related definitions for winXP + */ +#include <io.h> +#ifndef F_OK +#define F_OK 0 +#endif /* ifndef F_OK */ + +#ifndef X_OK +#define X_OK 1 +#endif /* ifndef X_OK */ + +#ifndef W_OK +#define W_OK 2 +#endif /* ifndef W_OK */ + +#ifndef R_OK +#define R_OK 4 +#endif /* ifndef R_OK */ + +#define access _access + +#include <process.h> diff --git a/lib/isc/win32/version.c b/lib/isc/win32/version.c new file mode 100644 index 0000000..b49347f --- /dev/null +++ b/lib/isc/win32/version.c @@ -0,0 +1,18 @@ +/* + * 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. + */ + +#include <versions.h> + +#include <isc/version.h> + +LIBISC_EXTERNAL_DATA const char isc_version[] = VERSION; diff --git a/lib/isc/win32/win32os.c b/lib/isc/win32/win32os.c new file mode 100644 index 0000000..4f86d38 --- /dev/null +++ b/lib/isc/win32/win32os.c @@ -0,0 +1,113 @@ +/* + * 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. + */ + +#include <windows.h> + +#ifndef TESTVERSION +#include <isc/win32os.h> +#else /* ifndef TESTVERSION */ +#include <stdio.h> + +#include <isc/util.h> +#endif /* ifndef TESTVERSION */ +#include <isc/print.h> + +int +isc_win32os_versioncheck(unsigned int major, unsigned int minor, + unsigned int spmajor, unsigned int spminor) { + OSVERSIONINFOEX osVer; + DWORD typeMask; + ULONGLONG conditionMask; + + memset(&osVer, 0, sizeof(OSVERSIONINFOEX)); + osVer.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + typeMask = 0; + conditionMask = 0; + + /* Optimistic: likely greater */ + osVer.dwMajorVersion = major; + typeMask |= VER_MAJORVERSION; + conditionMask = VerSetConditionMask(conditionMask, VER_MAJORVERSION, + VER_GREATER); + osVer.dwMinorVersion = minor; + typeMask |= VER_MINORVERSION; + conditionMask = VerSetConditionMask(conditionMask, VER_MINORVERSION, + VER_GREATER); + osVer.wServicePackMajor = spmajor; + typeMask |= VER_SERVICEPACKMAJOR; + conditionMask = VerSetConditionMask(conditionMask, VER_SERVICEPACKMAJOR, + VER_GREATER); + osVer.wServicePackMinor = spminor; + typeMask |= VER_SERVICEPACKMINOR; + conditionMask = VerSetConditionMask(conditionMask, VER_SERVICEPACKMINOR, + VER_GREATER); + if (VerifyVersionInfo(&osVer, typeMask, conditionMask)) { + return (1); + } + + /* Failed: retry with equal */ + conditionMask = 0; + conditionMask = VerSetConditionMask(conditionMask, VER_MAJORVERSION, + VER_EQUAL); + conditionMask = VerSetConditionMask(conditionMask, VER_MINORVERSION, + VER_EQUAL); + conditionMask = VerSetConditionMask(conditionMask, VER_SERVICEPACKMAJOR, + VER_EQUAL); + conditionMask = VerSetConditionMask(conditionMask, VER_SERVICEPACKMINOR, + VER_EQUAL); + if (VerifyVersionInfo(&osVer, typeMask, conditionMask)) { + return (0); + } else { + return (-1); + } +} + +#ifdef TESTVERSION +int +main(int argc, char **argv) { + unsigned int major = 0; + unsigned int minor = 0; + unsigned int spmajor = 0; + unsigned int spminor = 0; + int ret; + + if (argc > 1) { + --argc; + ++argv; + major = (unsigned int)atoi(argv[0]); + } + if (argc > 1) { + --argc; + ++argv; + minor = (unsigned int)atoi(argv[0]); + } + if (argc > 1) { + --argc; + ++argv; + spmajor = (unsigned int)atoi(argv[0]); + } + if (argc > 1) { + --argc; + POST(argc); + ++argv; + spminor = (unsigned int)atoi(argv[0]); + } + + ret = isc_win32os_versioncheck(major, minor, spmajor, spminor); + + printf("%s major %u minor %u SP major %u SP minor %u\n", + ret > 0 ? "greater" : (ret == 0 ? "equal" : "less"), major, + minor, spmajor, spminor); + return (ret); +} +#endif /* ifdef TESTVERSION */ diff --git a/lib/isc/xoshiro128starstar.c b/lib/isc/xoshiro128starstar.c new file mode 100644 index 0000000..7698ee8 --- /dev/null +++ b/lib/isc/xoshiro128starstar.c @@ -0,0 +1,63 @@ +/* + * 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. + */ + +/* + * Written in 2018 by David Blackman and Sebastiano Vigna (vigna@acm.org) + * + * To the extent possible under law, the author has dedicated all + * copyright and related and neighboring rights to this software to the + * public domain worldwide. This software is distributed without any + * warranty. + * + * See <http://creativecommons.org/publicdomain/zero/1.0/>. + */ + +#include <inttypes.h> + +#include <isc/thread.h> + +/* + * This is xoshiro128** 1.0, our 32-bit all-purpose, rock-solid generator. + * It has excellent (sub-ns) speed, a state size (128 bits) that is large + * enough for mild parallelism, and it passes all tests we are aware of. + * + * For generating just single-precision (i.e., 32-bit) floating-point + * numbers, xoshiro128+ is even faster. + * + * The state must be seeded so that it is not everywhere zero. + */ +ISC_THREAD_LOCAL uint32_t seed[4] = { 0 }; + +static uint32_t +rotl(const uint32_t x, int k) { + return ((x << k) | (x >> (32 - k))); +} + +static uint32_t +next(void) { + uint32_t result_starstar, t; + + result_starstar = rotl(seed[0] * 5, 7) * 9; + t = seed[1] << 9; + + seed[2] ^= seed[0]; + seed[3] ^= seed[1]; + seed[1] ^= seed[2]; + seed[0] ^= seed[3]; + + seed[2] ^= t; + + seed[3] = rotl(seed[3], 11); + + return (result_starstar); +} |