diff options
Diffstat (limited to 'lib/isc')
196 files changed, 61990 insertions, 0 deletions
diff --git a/lib/isc/Makefile.am b/lib/isc/Makefile.am new file mode 100644 index 0000000..b962f68 --- /dev/null +++ b/lib/isc/Makefile.am @@ -0,0 +1,253 @@ +include $(top_srcdir)/Makefile.top + +lib_LTLIBRARIES = libisc.la + +libisc_ladir = $(includedir)/isc +libisc_la_HEADERS = \ + include/isc/aes.h \ + include/isc/align.h \ + include/isc/app.h \ + include/isc/assertions.h \ + include/isc/astack.h \ + include/isc/atomic.h \ + include/isc/attributes.h \ + include/isc/backtrace.h \ + include/isc/barrier.h \ + include/isc/base32.h \ + include/isc/base64.h \ + include/isc/buffer.h \ + include/isc/cmocka.h \ + include/isc/commandline.h \ + include/isc/condition.h \ + include/isc/counter.h \ + include/isc/crc64.h \ + include/isc/deprecated.h \ + include/isc/dir.h \ + include/isc/endian.h \ + include/isc/errno.h \ + include/isc/error.h \ + include/isc/event.h \ + include/isc/eventclass.h \ + include/isc/file.h \ + include/isc/formatcheck.h \ + include/isc/fuzz.h \ + include/isc/glob.h \ + include/isc/hash.h \ + include/isc/heap.h \ + include/isc/hex.h \ + include/isc/hmac.h \ + include/isc/ht.h \ + include/isc/httpd.h \ + include/isc/interfaceiter.h \ + include/isc/iterated_hash.h \ + include/isc/lang.h \ + include/isc/lex.h \ + include/isc/list.h \ + include/isc/log.h \ + include/isc/magic.h \ + include/isc/managers.h \ + include/isc/md.h \ + include/isc/mem.h \ + include/isc/meminfo.h \ + include/isc/mutex.h \ + include/isc/mutexblock.h \ + include/isc/net.h \ + include/isc/netaddr.h \ + include/isc/netdb.h \ + include/isc/netmgr.h \ + include/isc/netscope.h \ + include/isc/nonce.h \ + include/isc/offset.h \ + include/isc/once.h \ + include/isc/os.h \ + include/isc/parseint.h \ + include/isc/pool.h \ + include/isc/portset.h \ + include/isc/print.h \ + include/isc/quota.h \ + include/isc/radix.h \ + include/isc/random.h \ + include/isc/ratelimiter.h \ + include/isc/refcount.h \ + include/isc/regex.h \ + include/isc/region.h \ + include/isc/resource.h \ + include/isc/result.h \ + include/isc/rwlock.h \ + include/isc/safe.h \ + include/isc/serial.h \ + include/isc/siphash.h \ + include/isc/sockaddr.h \ + include/isc/stat.h \ + include/isc/stats.h \ + include/isc/stdatomic.h \ + include/isc/stdio.h \ + include/isc/stdtime.h \ + include/isc/strerr.h \ + include/isc/string.h \ + include/isc/symtab.h \ + include/isc/syslog.h \ + include/isc/task.h \ + include/isc/taskpool.h \ + include/isc/thread.h \ + include/isc/time.h \ + include/isc/timer.h \ + include/isc/tls.h \ + include/isc/tm.h \ + include/isc/types.h \ + include/isc/url.h \ + include/isc/utf8.h \ + include/isc/util.h + +libisc_la_SOURCES = \ + $(libisc_la_HEADERS) \ + netmgr/netmgr-int.h \ + netmgr/netmgr.c \ + netmgr/tcp.c \ + netmgr/tcpdns.c \ + netmgr/timer.c \ + netmgr/tlsdns.c \ + netmgr/udp.c \ + netmgr/uv-compat.c \ + netmgr/uv-compat.h \ + netmgr/uverr2result.c \ + aes.c \ + app.c \ + assertions.c \ + astack.c \ + backtrace.c \ + base32.c \ + base64.c \ + buffer.c \ + commandline.c \ + condition.c \ + counter.c \ + crc64.c \ + dir.c \ + entropy.c \ + entropy_private.h \ + errno.c \ + errno2result.c \ + errno2result.h \ + error.c \ + event.c \ + file.c \ + glob.c \ + hash.c \ + heap.c \ + hex.c \ + hmac.c \ + ht.c \ + httpd.c \ + interfaceiter.c \ + iterated_hash.c \ + jemalloc_shim.h \ + lex.c \ + lib.c \ + log.c \ + managers.c \ + md.c \ + mem.c \ + mem_p.h \ + meminfo.c \ + mutex.c \ + mutexblock.c \ + net.c \ + netaddr.c \ + netmgr_p.h \ + netscope.c \ + nonce.c \ + openssl_shim.c \ + openssl_shim.h \ + os.c \ + os_p.h \ + parseint.c \ + pool.c \ + picohttpparser.c \ + picohttpparser.h \ + portset.c \ + quota.c \ + radix.c \ + random.c \ + ratelimiter.c \ + regex.c \ + region.c \ + resource.c \ + result.c \ + rwlock.c \ + safe.c \ + serial.c \ + siphash.c \ + sockaddr.c \ + stats.c \ + stdio.c \ + stdtime.c \ + string.c \ + symtab.c \ + syslog.c \ + task.c \ + task_p.h \ + taskpool.c \ + thread.c \ + time.c \ + timer.c \ + timer_p.h \ + tls.c \ + tls_p.h \ + tm.c \ + trampoline.c \ + trampoline_p.h \ + url.c \ + utf8.c + +libisc_la_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + $(LIBISC_CFLAGS) \ + $(LIBUV_CFLAGS) \ + $(OPENSSL_CFLAGS) \ + $(ZLIB_CFLAGS) + +libisc_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + -release "$(PACKAGE_VERSION)" + +libisc_la_LIBADD = \ + $(LIBUV_LIBS) \ + $(OPENSSL_LIBS) \ + $(ZLIB_LIBS) + +if HAVE_JEMALLOC +libisc_la_CPPFLAGS += \ + $(JEMALLOC_CFLAGS) + +libisc_la_LIBADD += \ + $(JEMALLOC_LIBS) +endif HAVE_JEMALLOC + +if HAVE_JSON_C +libisc_la_CPPFLAGS += \ + $(JSON_C_CFLAGS) + +libisc_la_LIBADD += \ + $(JSON_C_LIBS) +endif HAVE_JSON_C + +if HAVE_LIBNGHTTP2 +libisc_la_SOURCES += \ + netmgr/http.c \ + netmgr/tlsstream.c + +libisc_la_CPPFLAGS += \ + $(LIBNGHTTP2_CFLAGS) + +libisc_la_LIBADD += \ + $(LIBNGHTTP2_LIBS) +endif + +if HAVE_LIBXML2 +libisc_la_CPPFLAGS += \ + $(LIBXML2_CFLAGS) + +libisc_la_LIBADD += \ + $(LIBXML2_LIBS) +endif HAVE_LIBXML2 diff --git a/lib/isc/Makefile.in b/lib/isc/Makefile.in new file mode 100644 index 0000000..50ec06e --- /dev/null +++ b/lib/isc/Makefile.in @@ -0,0 +1,2073 @@ +# Makefile.in generated by automake 1.16.5 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2021 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +# Hey Emacs, this is -*- makefile-automake -*- file! +# vim: filetype=automake + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +target_triplet = @target@ +@HOST_MACOS_TRUE@am__append_1 = \ +@HOST_MACOS_TRUE@ -Wl,-flat_namespace + +@HAVE_JEMALLOC_TRUE@am__append_2 = \ +@HAVE_JEMALLOC_TRUE@ $(JEMALLOC_CFLAGS) + +@HAVE_JEMALLOC_TRUE@am__append_3 = \ +@HAVE_JEMALLOC_TRUE@ $(JEMALLOC_LIBS) + +@HAVE_JSON_C_TRUE@am__append_4 = \ +@HAVE_JSON_C_TRUE@ $(JSON_C_CFLAGS) + +@HAVE_JSON_C_TRUE@am__append_5 = \ +@HAVE_JSON_C_TRUE@ $(JSON_C_LIBS) + +@HAVE_LIBNGHTTP2_TRUE@am__append_6 = \ +@HAVE_LIBNGHTTP2_TRUE@ netmgr/http.c \ +@HAVE_LIBNGHTTP2_TRUE@ netmgr/tlsstream.c + +@HAVE_LIBNGHTTP2_TRUE@am__append_7 = \ +@HAVE_LIBNGHTTP2_TRUE@ $(LIBNGHTTP2_CFLAGS) + +@HAVE_LIBNGHTTP2_TRUE@am__append_8 = \ +@HAVE_LIBNGHTTP2_TRUE@ $(LIBNGHTTP2_LIBS) + +@HAVE_LIBXML2_TRUE@am__append_9 = \ +@HAVE_LIBXML2_TRUE@ $(LIBXML2_CFLAGS) + +@HAVE_LIBXML2_TRUE@am__append_10 = \ +@HAVE_LIBXML2_TRUE@ $(LIBXML2_LIBS) + +subdir = lib/isc +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_check_compile_flag.m4 \ + $(top_srcdir)/m4/ax_check_link_flag.m4 \ + $(top_srcdir)/m4/ax_check_openssl.m4 \ + $(top_srcdir)/m4/ax_gcc_func_attribute.m4 \ + $(top_srcdir)/m4/ax_jemalloc.m4 \ + $(top_srcdir)/m4/ax_lib_lmdb.m4 \ + $(top_srcdir)/m4/ax_perl_module.m4 \ + $(top_srcdir)/m4/ax_posix_shell.m4 \ + $(top_srcdir)/m4/ax_prog_cc_for_build.m4 \ + $(top_srcdir)/m4/ax_pthread.m4 \ + $(top_srcdir)/m4/ax_python_module.m4 \ + $(top_srcdir)/m4/ax_restore_flags.m4 \ + $(top_srcdir)/m4/ax_save_flags.m4 $(top_srcdir)/m4/ax_tls.m4 \ + $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \ + $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \ + $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(libisc_la_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libisc_ladir)" +LTLIBRARIES = $(lib_LTLIBRARIES) +am__DEPENDENCIES_1 = +@HAVE_JEMALLOC_TRUE@am__DEPENDENCIES_2 = $(am__DEPENDENCIES_1) +@HAVE_JSON_C_TRUE@am__DEPENDENCIES_3 = $(am__DEPENDENCIES_1) +@HAVE_LIBNGHTTP2_TRUE@am__DEPENDENCIES_4 = $(am__DEPENDENCIES_1) +@HAVE_LIBXML2_TRUE@am__DEPENDENCIES_5 = $(am__DEPENDENCIES_1) +libisc_la_DEPENDENCIES = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_2) \ + $(am__DEPENDENCIES_3) $(am__DEPENDENCIES_4) \ + $(am__DEPENDENCIES_5) +am__libisc_la_SOURCES_DIST = include/isc/aes.h include/isc/align.h \ + include/isc/app.h include/isc/assertions.h \ + include/isc/astack.h include/isc/atomic.h \ + include/isc/attributes.h include/isc/backtrace.h \ + include/isc/barrier.h include/isc/base32.h \ + include/isc/base64.h include/isc/buffer.h include/isc/cmocka.h \ + include/isc/commandline.h include/isc/condition.h \ + include/isc/counter.h include/isc/crc64.h \ + include/isc/deprecated.h include/isc/dir.h \ + include/isc/endian.h include/isc/errno.h include/isc/error.h \ + include/isc/event.h include/isc/eventclass.h \ + include/isc/file.h include/isc/formatcheck.h \ + include/isc/fuzz.h include/isc/glob.h include/isc/hash.h \ + include/isc/heap.h include/isc/hex.h include/isc/hmac.h \ + include/isc/ht.h include/isc/httpd.h \ + include/isc/interfaceiter.h include/isc/iterated_hash.h \ + include/isc/lang.h include/isc/lex.h include/isc/list.h \ + include/isc/log.h include/isc/magic.h include/isc/managers.h \ + include/isc/md.h include/isc/mem.h include/isc/meminfo.h \ + include/isc/mutex.h include/isc/mutexblock.h include/isc/net.h \ + include/isc/netaddr.h include/isc/netdb.h include/isc/netmgr.h \ + include/isc/netscope.h include/isc/nonce.h \ + include/isc/offset.h include/isc/once.h include/isc/os.h \ + include/isc/parseint.h include/isc/pool.h \ + include/isc/portset.h include/isc/print.h include/isc/quota.h \ + include/isc/radix.h include/isc/random.h \ + include/isc/ratelimiter.h include/isc/refcount.h \ + include/isc/regex.h include/isc/region.h \ + include/isc/resource.h include/isc/result.h \ + include/isc/rwlock.h include/isc/safe.h include/isc/serial.h \ + include/isc/siphash.h include/isc/sockaddr.h \ + include/isc/stat.h include/isc/stats.h include/isc/stdatomic.h \ + include/isc/stdio.h include/isc/stdtime.h include/isc/strerr.h \ + include/isc/string.h include/isc/symtab.h include/isc/syslog.h \ + include/isc/task.h include/isc/taskpool.h include/isc/thread.h \ + include/isc/time.h include/isc/timer.h include/isc/tls.h \ + include/isc/tm.h include/isc/types.h include/isc/url.h \ + include/isc/utf8.h include/isc/util.h netmgr/netmgr-int.h \ + netmgr/netmgr.c netmgr/tcp.c netmgr/tcpdns.c netmgr/timer.c \ + netmgr/tlsdns.c netmgr/udp.c netmgr/uv-compat.c \ + netmgr/uv-compat.h netmgr/uverr2result.c aes.c app.c \ + assertions.c astack.c backtrace.c base32.c base64.c buffer.c \ + commandline.c condition.c counter.c crc64.c dir.c entropy.c \ + entropy_private.h errno.c errno2result.c errno2result.h \ + error.c event.c file.c glob.c hash.c heap.c hex.c hmac.c ht.c \ + httpd.c interfaceiter.c iterated_hash.c jemalloc_shim.h lex.c \ + lib.c log.c managers.c md.c mem.c mem_p.h meminfo.c mutex.c \ + mutexblock.c net.c netaddr.c netmgr_p.h netscope.c nonce.c \ + openssl_shim.c openssl_shim.h os.c os_p.h parseint.c pool.c \ + picohttpparser.c picohttpparser.h portset.c quota.c radix.c \ + random.c ratelimiter.c regex.c region.c resource.c result.c \ + rwlock.c safe.c serial.c siphash.c sockaddr.c stats.c stdio.c \ + stdtime.c string.c symtab.c syslog.c task.c task_p.h \ + taskpool.c thread.c time.c timer.c timer_p.h tls.c tls_p.h \ + tm.c trampoline.c trampoline_p.h url.c utf8.c netmgr/http.c \ + netmgr/tlsstream.c +am__objects_1 = +am__dirstamp = $(am__leading_dot)dirstamp +@HAVE_LIBNGHTTP2_TRUE@am__objects_2 = netmgr/libisc_la-http.lo \ +@HAVE_LIBNGHTTP2_TRUE@ netmgr/libisc_la-tlsstream.lo +am_libisc_la_OBJECTS = $(am__objects_1) netmgr/libisc_la-netmgr.lo \ + netmgr/libisc_la-tcp.lo netmgr/libisc_la-tcpdns.lo \ + netmgr/libisc_la-timer.lo netmgr/libisc_la-tlsdns.lo \ + netmgr/libisc_la-udp.lo netmgr/libisc_la-uv-compat.lo \ + netmgr/libisc_la-uverr2result.lo libisc_la-aes.lo \ + libisc_la-app.lo libisc_la-assertions.lo libisc_la-astack.lo \ + libisc_la-backtrace.lo libisc_la-base32.lo libisc_la-base64.lo \ + libisc_la-buffer.lo libisc_la-commandline.lo \ + libisc_la-condition.lo libisc_la-counter.lo libisc_la-crc64.lo \ + libisc_la-dir.lo libisc_la-entropy.lo libisc_la-errno.lo \ + libisc_la-errno2result.lo libisc_la-error.lo \ + libisc_la-event.lo libisc_la-file.lo libisc_la-glob.lo \ + libisc_la-hash.lo libisc_la-heap.lo libisc_la-hex.lo \ + libisc_la-hmac.lo libisc_la-ht.lo libisc_la-httpd.lo \ + libisc_la-interfaceiter.lo libisc_la-iterated_hash.lo \ + libisc_la-lex.lo libisc_la-lib.lo libisc_la-log.lo \ + libisc_la-managers.lo libisc_la-md.lo libisc_la-mem.lo \ + libisc_la-meminfo.lo libisc_la-mutex.lo \ + libisc_la-mutexblock.lo libisc_la-net.lo libisc_la-netaddr.lo \ + libisc_la-netscope.lo libisc_la-nonce.lo \ + libisc_la-openssl_shim.lo libisc_la-os.lo \ + libisc_la-parseint.lo libisc_la-pool.lo \ + libisc_la-picohttpparser.lo libisc_la-portset.lo \ + libisc_la-quota.lo libisc_la-radix.lo libisc_la-random.lo \ + libisc_la-ratelimiter.lo libisc_la-regex.lo \ + libisc_la-region.lo libisc_la-resource.lo libisc_la-result.lo \ + libisc_la-rwlock.lo libisc_la-safe.lo libisc_la-serial.lo \ + libisc_la-siphash.lo libisc_la-sockaddr.lo libisc_la-stats.lo \ + libisc_la-stdio.lo libisc_la-stdtime.lo libisc_la-string.lo \ + libisc_la-symtab.lo libisc_la-syslog.lo libisc_la-task.lo \ + libisc_la-taskpool.lo libisc_la-thread.lo libisc_la-time.lo \ + libisc_la-timer.lo libisc_la-tls.lo libisc_la-tm.lo \ + libisc_la-trampoline.lo libisc_la-url.lo libisc_la-utf8.lo \ + $(am__objects_2) +libisc_la_OBJECTS = $(am_libisc_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +libisc_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(libisc_la_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/libisc_la-aes.Plo \ + ./$(DEPDIR)/libisc_la-app.Plo \ + ./$(DEPDIR)/libisc_la-assertions.Plo \ + ./$(DEPDIR)/libisc_la-astack.Plo \ + ./$(DEPDIR)/libisc_la-backtrace.Plo \ + ./$(DEPDIR)/libisc_la-base32.Plo \ + ./$(DEPDIR)/libisc_la-base64.Plo \ + ./$(DEPDIR)/libisc_la-buffer.Plo \ + ./$(DEPDIR)/libisc_la-commandline.Plo \ + ./$(DEPDIR)/libisc_la-condition.Plo \ + ./$(DEPDIR)/libisc_la-counter.Plo \ + ./$(DEPDIR)/libisc_la-crc64.Plo ./$(DEPDIR)/libisc_la-dir.Plo \ + ./$(DEPDIR)/libisc_la-entropy.Plo \ + ./$(DEPDIR)/libisc_la-errno.Plo \ + ./$(DEPDIR)/libisc_la-errno2result.Plo \ + ./$(DEPDIR)/libisc_la-error.Plo \ + ./$(DEPDIR)/libisc_la-event.Plo ./$(DEPDIR)/libisc_la-file.Plo \ + ./$(DEPDIR)/libisc_la-glob.Plo ./$(DEPDIR)/libisc_la-hash.Plo \ + ./$(DEPDIR)/libisc_la-heap.Plo ./$(DEPDIR)/libisc_la-hex.Plo \ + ./$(DEPDIR)/libisc_la-hmac.Plo ./$(DEPDIR)/libisc_la-ht.Plo \ + ./$(DEPDIR)/libisc_la-httpd.Plo \ + ./$(DEPDIR)/libisc_la-interfaceiter.Plo \ + ./$(DEPDIR)/libisc_la-iterated_hash.Plo \ + ./$(DEPDIR)/libisc_la-lex.Plo ./$(DEPDIR)/libisc_la-lib.Plo \ + ./$(DEPDIR)/libisc_la-log.Plo \ + ./$(DEPDIR)/libisc_la-managers.Plo \ + ./$(DEPDIR)/libisc_la-md.Plo ./$(DEPDIR)/libisc_la-mem.Plo \ + ./$(DEPDIR)/libisc_la-meminfo.Plo \ + ./$(DEPDIR)/libisc_la-mutex.Plo \ + ./$(DEPDIR)/libisc_la-mutexblock.Plo \ + ./$(DEPDIR)/libisc_la-net.Plo \ + ./$(DEPDIR)/libisc_la-netaddr.Plo \ + ./$(DEPDIR)/libisc_la-netscope.Plo \ + ./$(DEPDIR)/libisc_la-nonce.Plo \ + ./$(DEPDIR)/libisc_la-openssl_shim.Plo \ + ./$(DEPDIR)/libisc_la-os.Plo \ + ./$(DEPDIR)/libisc_la-parseint.Plo \ + ./$(DEPDIR)/libisc_la-picohttpparser.Plo \ + ./$(DEPDIR)/libisc_la-pool.Plo \ + ./$(DEPDIR)/libisc_la-portset.Plo \ + ./$(DEPDIR)/libisc_la-quota.Plo \ + ./$(DEPDIR)/libisc_la-radix.Plo \ + ./$(DEPDIR)/libisc_la-random.Plo \ + ./$(DEPDIR)/libisc_la-ratelimiter.Plo \ + ./$(DEPDIR)/libisc_la-regex.Plo \ + ./$(DEPDIR)/libisc_la-region.Plo \ + ./$(DEPDIR)/libisc_la-resource.Plo \ + ./$(DEPDIR)/libisc_la-result.Plo \ + ./$(DEPDIR)/libisc_la-rwlock.Plo \ + ./$(DEPDIR)/libisc_la-safe.Plo \ + ./$(DEPDIR)/libisc_la-serial.Plo \ + ./$(DEPDIR)/libisc_la-siphash.Plo \ + ./$(DEPDIR)/libisc_la-sockaddr.Plo \ + ./$(DEPDIR)/libisc_la-stats.Plo \ + ./$(DEPDIR)/libisc_la-stdio.Plo \ + ./$(DEPDIR)/libisc_la-stdtime.Plo \ + ./$(DEPDIR)/libisc_la-string.Plo \ + ./$(DEPDIR)/libisc_la-symtab.Plo \ + ./$(DEPDIR)/libisc_la-syslog.Plo \ + ./$(DEPDIR)/libisc_la-task.Plo \ + ./$(DEPDIR)/libisc_la-taskpool.Plo \ + ./$(DEPDIR)/libisc_la-thread.Plo \ + ./$(DEPDIR)/libisc_la-time.Plo ./$(DEPDIR)/libisc_la-timer.Plo \ + ./$(DEPDIR)/libisc_la-tls.Plo ./$(DEPDIR)/libisc_la-tm.Plo \ + ./$(DEPDIR)/libisc_la-trampoline.Plo \ + ./$(DEPDIR)/libisc_la-url.Plo ./$(DEPDIR)/libisc_la-utf8.Plo \ + netmgr/$(DEPDIR)/libisc_la-http.Plo \ + netmgr/$(DEPDIR)/libisc_la-netmgr.Plo \ + netmgr/$(DEPDIR)/libisc_la-tcp.Plo \ + netmgr/$(DEPDIR)/libisc_la-tcpdns.Plo \ + netmgr/$(DEPDIR)/libisc_la-timer.Plo \ + netmgr/$(DEPDIR)/libisc_la-tlsdns.Plo \ + netmgr/$(DEPDIR)/libisc_la-tlsstream.Plo \ + netmgr/$(DEPDIR)/libisc_la-udp.Plo \ + netmgr/$(DEPDIR)/libisc_la-uv-compat.Plo \ + netmgr/$(DEPDIR)/libisc_la-uverr2result.Plo +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(libisc_la_SOURCES) +DIST_SOURCES = $(am__libisc_la_SOURCES_DIST) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(libisc_la_HEADERS) +am__extra_recursive_targets = test-recursive unit-recursive \ + doc-recursive +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/Makefile.top \ + $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BUILD_EXEEXT = @BUILD_EXEEXT@ +BUILD_OBJEXT = @BUILD_OBJEXT@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CC_FOR_BUILD = @CC_FOR_BUILD@ +CFLAGS = @CFLAGS@ +CFLAGS_FOR_BUILD = @CFLAGS_FOR_BUILD@ +CMOCKA_CFLAGS = @CMOCKA_CFLAGS@ +CMOCKA_LIBS = @CMOCKA_LIBS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CPPFLAGS_FOR_BUILD = @CPPFLAGS_FOR_BUILD@ +CPP_FOR_BUILD = @CPP_FOR_BUILD@ +CSCOPE = @CSCOPE@ +CTAGS = @CTAGS@ +CURL = @CURL@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DEVELOPER_MODE = @DEVELOPER_MODE@ +DLLTOOL = @DLLTOOL@ +DNSTAP_CFLAGS = @DNSTAP_CFLAGS@ +DNSTAP_LIBS = @DNSTAP_LIBS@ +DOXYGEN = @DOXYGEN@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +ETAGS = @ETAGS@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FILECMD = @FILECMD@ +FSTRM_CAPTURE = @FSTRM_CAPTURE@ +FUZZ_LDFLAGS = @FUZZ_LDFLAGS@ +FUZZ_LOG_COMPILER = @FUZZ_LOG_COMPILER@ +GREP = @GREP@ +GSSAPI_CFLAGS = @GSSAPI_CFLAGS@ +GSSAPI_LIBS = @GSSAPI_LIBS@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +JEMALLOC_CFLAGS = @JEMALLOC_CFLAGS@ +JEMALLOC_LIBS = @JEMALLOC_LIBS@ +JSON_C_CFLAGS = @JSON_C_CFLAGS@ +JSON_C_LIBS = @JSON_C_LIBS@ +KRB5_CFLAGS = @KRB5_CFLAGS@ +KRB5_CONFIG = @KRB5_CONFIG@ +KRB5_LIBS = @KRB5_LIBS@ +LATEXMK = @LATEXMK@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LDFLAGS_FOR_BUILD = @LDFLAGS_FOR_BUILD@ +LIBCAP_LIBS = @LIBCAP_LIBS@ +LIBIDN2_CFLAGS = @LIBIDN2_CFLAGS@ +LIBIDN2_LIBS = @LIBIDN2_LIBS@ +LIBNGHTTP2_CFLAGS = @LIBNGHTTP2_CFLAGS@ +LIBNGHTTP2_LIBS = @LIBNGHTTP2_LIBS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIBUV_CFLAGS = @LIBUV_CFLAGS@ +LIBUV_LIBS = @LIBUV_LIBS@ +LIBXML2_CFLAGS = @LIBXML2_CFLAGS@ +LIBXML2_LIBS = @LIBXML2_LIBS@ +LIPO = @LIPO@ +LMDB_CFLAGS = @LMDB_CFLAGS@ +LMDB_LIBS = @LMDB_LIBS@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MAXMINDDB_CFLAGS = @MAXMINDDB_CFLAGS@ +MAXMINDDB_LIBS = @MAXMINDDB_LIBS@ +MAXMINDDB_PREFIX = @MAXMINDDB_PREFIX@ +MKDIR_P = @MKDIR_P@ +NC = @NC@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OPENSSL_CFLAGS = @OPENSSL_CFLAGS@ +OPENSSL_LDFLAGS = @OPENSSL_LDFLAGS@ +OPENSSL_LIBS = @OPENSSL_LIBS@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PERL = @PERL@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +PROTOC_C = @PROTOC_C@ +PTHREAD_CC = @PTHREAD_CC@ +PTHREAD_CFLAGS = @PTHREAD_CFLAGS@ +PTHREAD_CXX = @PTHREAD_CXX@ +PTHREAD_LIBS = @PTHREAD_LIBS@ +PYTEST = @PYTEST@ +PYTHON = @PYTHON@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +READLINE_CFLAGS = @READLINE_CFLAGS@ +READLINE_LIBS = @READLINE_LIBS@ +RELEASE_DATE = @RELEASE_DATE@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SPHINX_BUILD = @SPHINX_BUILD@ +STD_CFLAGS = @STD_CFLAGS@ +STD_CPPFLAGS = @STD_CPPFLAGS@ +STD_LDFLAGS = @STD_LDFLAGS@ +STRIP = @STRIP@ +TEST_CFLAGS = @TEST_CFLAGS@ +VERSION = @VERSION@ +XELATEX = @XELATEX@ +XSLTPROC = @XSLTPROC@ +ZLIB_CFLAGS = @ZLIB_CFLAGS@ +ZLIB_LIBS = @ZLIB_LIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CC_FOR_BUILD = @ac_ct_CC_FOR_BUILD@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +ax_pthread_config = @ax_pthread_config@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target = @target@ +target_alias = @target_alias@ +target_cpu = @target_cpu@ +target_os = @target_os@ +target_vendor = @target_vendor@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +ACLOCAL_AMFLAGS = -I $(top_srcdir)/m4 +AM_CFLAGS = \ + $(STD_CFLAGS) + +AM_CPPFLAGS = \ + $(STD_CPPFLAGS) \ + -include $(top_builddir)/config.h \ + -I$(srcdir)/include + +AM_LDFLAGS = $(STD_LDFLAGS) $(am__append_1) +LDADD = +LIBISC_CFLAGS = \ + -I$(top_srcdir)/include \ + -I$(top_srcdir)/lib/isc/include \ + -I$(top_builddir)/lib/isc/include + +LIBISC_LIBS = $(top_builddir)/lib/isc/libisc.la +LIBDNS_CFLAGS = \ + -I$(top_srcdir)/lib/dns/include \ + -I$(top_builddir)/lib/dns/include + +LIBDNS_LIBS = \ + $(top_builddir)/lib/dns/libdns.la + +LIBNS_CFLAGS = \ + -I$(top_srcdir)/lib/ns/include + +LIBNS_LIBS = \ + $(top_builddir)/lib/ns/libns.la + +LIBIRS_CFLAGS = \ + -I$(top_srcdir)/lib/irs/include + +LIBIRS_LIBS = \ + $(top_builddir)/lib/irs/libirs.la + +LIBISCCFG_CFLAGS = \ + -I$(top_srcdir)/lib/isccfg/include + +LIBISCCFG_LIBS = \ + $(top_builddir)/lib/isccfg/libisccfg.la + +LIBISCCC_CFLAGS = \ + -I$(top_srcdir)/lib/isccc/include/ + +LIBISCCC_LIBS = \ + $(top_builddir)/lib/isccc/libisccc.la + +LIBBIND9_CFLAGS = \ + -I$(top_srcdir)/lib/bind9/include + +LIBBIND9_LIBS = \ + $(top_builddir)/lib/bind9/libbind9.la + +lib_LTLIBRARIES = libisc.la +libisc_ladir = $(includedir)/isc +libisc_la_HEADERS = \ + include/isc/aes.h \ + include/isc/align.h \ + include/isc/app.h \ + include/isc/assertions.h \ + include/isc/astack.h \ + include/isc/atomic.h \ + include/isc/attributes.h \ + include/isc/backtrace.h \ + include/isc/barrier.h \ + include/isc/base32.h \ + include/isc/base64.h \ + include/isc/buffer.h \ + include/isc/cmocka.h \ + include/isc/commandline.h \ + include/isc/condition.h \ + include/isc/counter.h \ + include/isc/crc64.h \ + include/isc/deprecated.h \ + include/isc/dir.h \ + include/isc/endian.h \ + include/isc/errno.h \ + include/isc/error.h \ + include/isc/event.h \ + include/isc/eventclass.h \ + include/isc/file.h \ + include/isc/formatcheck.h \ + include/isc/fuzz.h \ + include/isc/glob.h \ + include/isc/hash.h \ + include/isc/heap.h \ + include/isc/hex.h \ + include/isc/hmac.h \ + include/isc/ht.h \ + include/isc/httpd.h \ + include/isc/interfaceiter.h \ + include/isc/iterated_hash.h \ + include/isc/lang.h \ + include/isc/lex.h \ + include/isc/list.h \ + include/isc/log.h \ + include/isc/magic.h \ + include/isc/managers.h \ + include/isc/md.h \ + include/isc/mem.h \ + include/isc/meminfo.h \ + include/isc/mutex.h \ + include/isc/mutexblock.h \ + include/isc/net.h \ + include/isc/netaddr.h \ + include/isc/netdb.h \ + include/isc/netmgr.h \ + include/isc/netscope.h \ + include/isc/nonce.h \ + include/isc/offset.h \ + include/isc/once.h \ + include/isc/os.h \ + include/isc/parseint.h \ + include/isc/pool.h \ + include/isc/portset.h \ + include/isc/print.h \ + include/isc/quota.h \ + include/isc/radix.h \ + include/isc/random.h \ + include/isc/ratelimiter.h \ + include/isc/refcount.h \ + include/isc/regex.h \ + include/isc/region.h \ + include/isc/resource.h \ + include/isc/result.h \ + include/isc/rwlock.h \ + include/isc/safe.h \ + include/isc/serial.h \ + include/isc/siphash.h \ + include/isc/sockaddr.h \ + include/isc/stat.h \ + include/isc/stats.h \ + include/isc/stdatomic.h \ + include/isc/stdio.h \ + include/isc/stdtime.h \ + include/isc/strerr.h \ + include/isc/string.h \ + include/isc/symtab.h \ + include/isc/syslog.h \ + include/isc/task.h \ + include/isc/taskpool.h \ + include/isc/thread.h \ + include/isc/time.h \ + include/isc/timer.h \ + include/isc/tls.h \ + include/isc/tm.h \ + include/isc/types.h \ + include/isc/url.h \ + include/isc/utf8.h \ + include/isc/util.h + +libisc_la_SOURCES = $(libisc_la_HEADERS) netmgr/netmgr-int.h \ + netmgr/netmgr.c netmgr/tcp.c netmgr/tcpdns.c netmgr/timer.c \ + netmgr/tlsdns.c netmgr/udp.c netmgr/uv-compat.c \ + netmgr/uv-compat.h netmgr/uverr2result.c aes.c app.c \ + assertions.c astack.c backtrace.c base32.c base64.c buffer.c \ + commandline.c condition.c counter.c crc64.c dir.c entropy.c \ + entropy_private.h errno.c errno2result.c errno2result.h \ + error.c event.c file.c glob.c hash.c heap.c hex.c hmac.c ht.c \ + httpd.c interfaceiter.c iterated_hash.c jemalloc_shim.h lex.c \ + lib.c log.c managers.c md.c mem.c mem_p.h meminfo.c mutex.c \ + mutexblock.c net.c netaddr.c netmgr_p.h netscope.c nonce.c \ + openssl_shim.c openssl_shim.h os.c os_p.h parseint.c pool.c \ + picohttpparser.c picohttpparser.h portset.c quota.c radix.c \ + random.c ratelimiter.c regex.c region.c resource.c result.c \ + rwlock.c safe.c serial.c siphash.c sockaddr.c stats.c stdio.c \ + stdtime.c string.c symtab.c syslog.c task.c task_p.h \ + taskpool.c thread.c time.c timer.c timer_p.h tls.c tls_p.h \ + tm.c trampoline.c trampoline_p.h url.c utf8.c $(am__append_6) +libisc_la_CPPFLAGS = $(AM_CPPFLAGS) $(LIBISC_CFLAGS) $(LIBUV_CFLAGS) \ + $(OPENSSL_CFLAGS) $(ZLIB_CFLAGS) $(am__append_2) \ + $(am__append_4) $(am__append_7) $(am__append_9) +libisc_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + -release "$(PACKAGE_VERSION)" + +libisc_la_LIBADD = $(LIBUV_LIBS) $(OPENSSL_LIBS) $(ZLIB_LIBS) \ + $(am__append_3) $(am__append_5) $(am__append_8) \ + $(am__append_10) +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(top_srcdir)/Makefile.top $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign lib/isc/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign lib/isc/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; +$(top_srcdir)/Makefile.top $(am__empty): + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-libLTLIBRARIES: $(lib_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \ + } + +uninstall-libLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \ + done + +clean-libLTLIBRARIES: + -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES) + @list='$(lib_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } +netmgr/$(am__dirstamp): + @$(MKDIR_P) netmgr + @: > netmgr/$(am__dirstamp) +netmgr/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) netmgr/$(DEPDIR) + @: > netmgr/$(DEPDIR)/$(am__dirstamp) +netmgr/libisc_la-netmgr.lo: netmgr/$(am__dirstamp) \ + netmgr/$(DEPDIR)/$(am__dirstamp) +netmgr/libisc_la-tcp.lo: netmgr/$(am__dirstamp) \ + netmgr/$(DEPDIR)/$(am__dirstamp) +netmgr/libisc_la-tcpdns.lo: netmgr/$(am__dirstamp) \ + netmgr/$(DEPDIR)/$(am__dirstamp) +netmgr/libisc_la-timer.lo: netmgr/$(am__dirstamp) \ + netmgr/$(DEPDIR)/$(am__dirstamp) +netmgr/libisc_la-tlsdns.lo: netmgr/$(am__dirstamp) \ + netmgr/$(DEPDIR)/$(am__dirstamp) +netmgr/libisc_la-udp.lo: netmgr/$(am__dirstamp) \ + netmgr/$(DEPDIR)/$(am__dirstamp) +netmgr/libisc_la-uv-compat.lo: netmgr/$(am__dirstamp) \ + netmgr/$(DEPDIR)/$(am__dirstamp) +netmgr/libisc_la-uverr2result.lo: netmgr/$(am__dirstamp) \ + netmgr/$(DEPDIR)/$(am__dirstamp) +netmgr/libisc_la-http.lo: netmgr/$(am__dirstamp) \ + netmgr/$(DEPDIR)/$(am__dirstamp) +netmgr/libisc_la-tlsstream.lo: netmgr/$(am__dirstamp) \ + netmgr/$(DEPDIR)/$(am__dirstamp) + +libisc.la: $(libisc_la_OBJECTS) $(libisc_la_DEPENDENCIES) $(EXTRA_libisc_la_DEPENDENCIES) + $(AM_V_CCLD)$(libisc_la_LINK) -rpath $(libdir) $(libisc_la_OBJECTS) $(libisc_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + -rm -f netmgr/*.$(OBJEXT) + -rm -f netmgr/*.lo + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-aes.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-app.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-assertions.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-astack.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-backtrace.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-base32.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-base64.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-buffer.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-commandline.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-condition.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-counter.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-crc64.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-dir.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-entropy.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-errno.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-errno2result.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-error.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-event.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-file.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-glob.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-hash.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-heap.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-hex.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-hmac.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-ht.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-httpd.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-interfaceiter.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-iterated_hash.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-lex.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-lib.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-log.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-managers.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-md.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-mem.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-meminfo.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-mutex.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-mutexblock.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-net.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-netaddr.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-netscope.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-nonce.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-openssl_shim.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-os.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-parseint.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-picohttpparser.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-pool.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-portset.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-quota.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-radix.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-random.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-ratelimiter.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-regex.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-region.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-resource.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-result.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-rwlock.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-safe.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-serial.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-siphash.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-sockaddr.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-stats.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-stdio.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-stdtime.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-string.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-symtab.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-syslog.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-task.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-taskpool.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-thread.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-time.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-timer.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-tls.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-tm.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-trampoline.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-url.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-utf8.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@netmgr/$(DEPDIR)/libisc_la-http.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@netmgr/$(DEPDIR)/libisc_la-netmgr.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@netmgr/$(DEPDIR)/libisc_la-tcp.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@netmgr/$(DEPDIR)/libisc_la-tcpdns.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@netmgr/$(DEPDIR)/libisc_la-timer.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@netmgr/$(DEPDIR)/libisc_la-tlsdns.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@netmgr/$(DEPDIR)/libisc_la-tlsstream.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@netmgr/$(DEPDIR)/libisc_la-udp.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@netmgr/$(DEPDIR)/libisc_la-uv-compat.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@netmgr/$(DEPDIR)/libisc_la-uverr2result.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ +@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +netmgr/libisc_la-netmgr.lo: netmgr/netmgr.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT netmgr/libisc_la-netmgr.lo -MD -MP -MF netmgr/$(DEPDIR)/libisc_la-netmgr.Tpo -c -o netmgr/libisc_la-netmgr.lo `test -f 'netmgr/netmgr.c' || echo '$(srcdir)/'`netmgr/netmgr.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) netmgr/$(DEPDIR)/libisc_la-netmgr.Tpo netmgr/$(DEPDIR)/libisc_la-netmgr.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='netmgr/netmgr.c' object='netmgr/libisc_la-netmgr.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o netmgr/libisc_la-netmgr.lo `test -f 'netmgr/netmgr.c' || echo '$(srcdir)/'`netmgr/netmgr.c + +netmgr/libisc_la-tcp.lo: netmgr/tcp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT netmgr/libisc_la-tcp.lo -MD -MP -MF netmgr/$(DEPDIR)/libisc_la-tcp.Tpo -c -o netmgr/libisc_la-tcp.lo `test -f 'netmgr/tcp.c' || echo '$(srcdir)/'`netmgr/tcp.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) netmgr/$(DEPDIR)/libisc_la-tcp.Tpo netmgr/$(DEPDIR)/libisc_la-tcp.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='netmgr/tcp.c' object='netmgr/libisc_la-tcp.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o netmgr/libisc_la-tcp.lo `test -f 'netmgr/tcp.c' || echo '$(srcdir)/'`netmgr/tcp.c + +netmgr/libisc_la-tcpdns.lo: netmgr/tcpdns.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT netmgr/libisc_la-tcpdns.lo -MD -MP -MF netmgr/$(DEPDIR)/libisc_la-tcpdns.Tpo -c -o netmgr/libisc_la-tcpdns.lo `test -f 'netmgr/tcpdns.c' || echo '$(srcdir)/'`netmgr/tcpdns.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) netmgr/$(DEPDIR)/libisc_la-tcpdns.Tpo netmgr/$(DEPDIR)/libisc_la-tcpdns.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='netmgr/tcpdns.c' object='netmgr/libisc_la-tcpdns.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o netmgr/libisc_la-tcpdns.lo `test -f 'netmgr/tcpdns.c' || echo '$(srcdir)/'`netmgr/tcpdns.c + +netmgr/libisc_la-timer.lo: netmgr/timer.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT netmgr/libisc_la-timer.lo -MD -MP -MF netmgr/$(DEPDIR)/libisc_la-timer.Tpo -c -o netmgr/libisc_la-timer.lo `test -f 'netmgr/timer.c' || echo '$(srcdir)/'`netmgr/timer.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) netmgr/$(DEPDIR)/libisc_la-timer.Tpo netmgr/$(DEPDIR)/libisc_la-timer.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='netmgr/timer.c' object='netmgr/libisc_la-timer.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o netmgr/libisc_la-timer.lo `test -f 'netmgr/timer.c' || echo '$(srcdir)/'`netmgr/timer.c + +netmgr/libisc_la-tlsdns.lo: netmgr/tlsdns.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT netmgr/libisc_la-tlsdns.lo -MD -MP -MF netmgr/$(DEPDIR)/libisc_la-tlsdns.Tpo -c -o netmgr/libisc_la-tlsdns.lo `test -f 'netmgr/tlsdns.c' || echo '$(srcdir)/'`netmgr/tlsdns.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) netmgr/$(DEPDIR)/libisc_la-tlsdns.Tpo netmgr/$(DEPDIR)/libisc_la-tlsdns.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='netmgr/tlsdns.c' object='netmgr/libisc_la-tlsdns.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o netmgr/libisc_la-tlsdns.lo `test -f 'netmgr/tlsdns.c' || echo '$(srcdir)/'`netmgr/tlsdns.c + +netmgr/libisc_la-udp.lo: netmgr/udp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT netmgr/libisc_la-udp.lo -MD -MP -MF netmgr/$(DEPDIR)/libisc_la-udp.Tpo -c -o netmgr/libisc_la-udp.lo `test -f 'netmgr/udp.c' || echo '$(srcdir)/'`netmgr/udp.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) netmgr/$(DEPDIR)/libisc_la-udp.Tpo netmgr/$(DEPDIR)/libisc_la-udp.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='netmgr/udp.c' object='netmgr/libisc_la-udp.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o netmgr/libisc_la-udp.lo `test -f 'netmgr/udp.c' || echo '$(srcdir)/'`netmgr/udp.c + +netmgr/libisc_la-uv-compat.lo: netmgr/uv-compat.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT netmgr/libisc_la-uv-compat.lo -MD -MP -MF netmgr/$(DEPDIR)/libisc_la-uv-compat.Tpo -c -o netmgr/libisc_la-uv-compat.lo `test -f 'netmgr/uv-compat.c' || echo '$(srcdir)/'`netmgr/uv-compat.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) netmgr/$(DEPDIR)/libisc_la-uv-compat.Tpo netmgr/$(DEPDIR)/libisc_la-uv-compat.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='netmgr/uv-compat.c' object='netmgr/libisc_la-uv-compat.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o netmgr/libisc_la-uv-compat.lo `test -f 'netmgr/uv-compat.c' || echo '$(srcdir)/'`netmgr/uv-compat.c + +netmgr/libisc_la-uverr2result.lo: netmgr/uverr2result.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT netmgr/libisc_la-uverr2result.lo -MD -MP -MF netmgr/$(DEPDIR)/libisc_la-uverr2result.Tpo -c -o netmgr/libisc_la-uverr2result.lo `test -f 'netmgr/uverr2result.c' || echo '$(srcdir)/'`netmgr/uverr2result.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) netmgr/$(DEPDIR)/libisc_la-uverr2result.Tpo netmgr/$(DEPDIR)/libisc_la-uverr2result.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='netmgr/uverr2result.c' object='netmgr/libisc_la-uverr2result.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o netmgr/libisc_la-uverr2result.lo `test -f 'netmgr/uverr2result.c' || echo '$(srcdir)/'`netmgr/uverr2result.c + +libisc_la-aes.lo: aes.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-aes.lo -MD -MP -MF $(DEPDIR)/libisc_la-aes.Tpo -c -o libisc_la-aes.lo `test -f 'aes.c' || echo '$(srcdir)/'`aes.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-aes.Tpo $(DEPDIR)/libisc_la-aes.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='aes.c' object='libisc_la-aes.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-aes.lo `test -f 'aes.c' || echo '$(srcdir)/'`aes.c + +libisc_la-app.lo: app.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-app.lo -MD -MP -MF $(DEPDIR)/libisc_la-app.Tpo -c -o libisc_la-app.lo `test -f 'app.c' || echo '$(srcdir)/'`app.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-app.Tpo $(DEPDIR)/libisc_la-app.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='app.c' object='libisc_la-app.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-app.lo `test -f 'app.c' || echo '$(srcdir)/'`app.c + +libisc_la-assertions.lo: assertions.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-assertions.lo -MD -MP -MF $(DEPDIR)/libisc_la-assertions.Tpo -c -o libisc_la-assertions.lo `test -f 'assertions.c' || echo '$(srcdir)/'`assertions.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-assertions.Tpo $(DEPDIR)/libisc_la-assertions.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='assertions.c' object='libisc_la-assertions.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-assertions.lo `test -f 'assertions.c' || echo '$(srcdir)/'`assertions.c + +libisc_la-astack.lo: astack.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-astack.lo -MD -MP -MF $(DEPDIR)/libisc_la-astack.Tpo -c -o libisc_la-astack.lo `test -f 'astack.c' || echo '$(srcdir)/'`astack.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-astack.Tpo $(DEPDIR)/libisc_la-astack.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='astack.c' object='libisc_la-astack.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-astack.lo `test -f 'astack.c' || echo '$(srcdir)/'`astack.c + +libisc_la-backtrace.lo: backtrace.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-backtrace.lo -MD -MP -MF $(DEPDIR)/libisc_la-backtrace.Tpo -c -o libisc_la-backtrace.lo `test -f 'backtrace.c' || echo '$(srcdir)/'`backtrace.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-backtrace.Tpo $(DEPDIR)/libisc_la-backtrace.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='backtrace.c' object='libisc_la-backtrace.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-backtrace.lo `test -f 'backtrace.c' || echo '$(srcdir)/'`backtrace.c + +libisc_la-base32.lo: base32.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-base32.lo -MD -MP -MF $(DEPDIR)/libisc_la-base32.Tpo -c -o libisc_la-base32.lo `test -f 'base32.c' || echo '$(srcdir)/'`base32.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-base32.Tpo $(DEPDIR)/libisc_la-base32.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='base32.c' object='libisc_la-base32.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-base32.lo `test -f 'base32.c' || echo '$(srcdir)/'`base32.c + +libisc_la-base64.lo: base64.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-base64.lo -MD -MP -MF $(DEPDIR)/libisc_la-base64.Tpo -c -o libisc_la-base64.lo `test -f 'base64.c' || echo '$(srcdir)/'`base64.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-base64.Tpo $(DEPDIR)/libisc_la-base64.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='base64.c' object='libisc_la-base64.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-base64.lo `test -f 'base64.c' || echo '$(srcdir)/'`base64.c + +libisc_la-buffer.lo: buffer.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-buffer.lo -MD -MP -MF $(DEPDIR)/libisc_la-buffer.Tpo -c -o libisc_la-buffer.lo `test -f 'buffer.c' || echo '$(srcdir)/'`buffer.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-buffer.Tpo $(DEPDIR)/libisc_la-buffer.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='buffer.c' object='libisc_la-buffer.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-buffer.lo `test -f 'buffer.c' || echo '$(srcdir)/'`buffer.c + +libisc_la-commandline.lo: commandline.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-commandline.lo -MD -MP -MF $(DEPDIR)/libisc_la-commandline.Tpo -c -o libisc_la-commandline.lo `test -f 'commandline.c' || echo '$(srcdir)/'`commandline.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-commandline.Tpo $(DEPDIR)/libisc_la-commandline.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='commandline.c' object='libisc_la-commandline.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-commandline.lo `test -f 'commandline.c' || echo '$(srcdir)/'`commandline.c + +libisc_la-condition.lo: condition.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-condition.lo -MD -MP -MF $(DEPDIR)/libisc_la-condition.Tpo -c -o libisc_la-condition.lo `test -f 'condition.c' || echo '$(srcdir)/'`condition.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-condition.Tpo $(DEPDIR)/libisc_la-condition.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='condition.c' object='libisc_la-condition.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-condition.lo `test -f 'condition.c' || echo '$(srcdir)/'`condition.c + +libisc_la-counter.lo: counter.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-counter.lo -MD -MP -MF $(DEPDIR)/libisc_la-counter.Tpo -c -o libisc_la-counter.lo `test -f 'counter.c' || echo '$(srcdir)/'`counter.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-counter.Tpo $(DEPDIR)/libisc_la-counter.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='counter.c' object='libisc_la-counter.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-counter.lo `test -f 'counter.c' || echo '$(srcdir)/'`counter.c + +libisc_la-crc64.lo: crc64.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-crc64.lo -MD -MP -MF $(DEPDIR)/libisc_la-crc64.Tpo -c -o libisc_la-crc64.lo `test -f 'crc64.c' || echo '$(srcdir)/'`crc64.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-crc64.Tpo $(DEPDIR)/libisc_la-crc64.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='crc64.c' object='libisc_la-crc64.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-crc64.lo `test -f 'crc64.c' || echo '$(srcdir)/'`crc64.c + +libisc_la-dir.lo: dir.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-dir.lo -MD -MP -MF $(DEPDIR)/libisc_la-dir.Tpo -c -o libisc_la-dir.lo `test -f 'dir.c' || echo '$(srcdir)/'`dir.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-dir.Tpo $(DEPDIR)/libisc_la-dir.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dir.c' object='libisc_la-dir.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-dir.lo `test -f 'dir.c' || echo '$(srcdir)/'`dir.c + +libisc_la-entropy.lo: entropy.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-entropy.lo -MD -MP -MF $(DEPDIR)/libisc_la-entropy.Tpo -c -o libisc_la-entropy.lo `test -f 'entropy.c' || echo '$(srcdir)/'`entropy.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-entropy.Tpo $(DEPDIR)/libisc_la-entropy.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='entropy.c' object='libisc_la-entropy.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-entropy.lo `test -f 'entropy.c' || echo '$(srcdir)/'`entropy.c + +libisc_la-errno.lo: errno.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-errno.lo -MD -MP -MF $(DEPDIR)/libisc_la-errno.Tpo -c -o libisc_la-errno.lo `test -f 'errno.c' || echo '$(srcdir)/'`errno.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-errno.Tpo $(DEPDIR)/libisc_la-errno.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='errno.c' object='libisc_la-errno.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-errno.lo `test -f 'errno.c' || echo '$(srcdir)/'`errno.c + +libisc_la-errno2result.lo: errno2result.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-errno2result.lo -MD -MP -MF $(DEPDIR)/libisc_la-errno2result.Tpo -c -o libisc_la-errno2result.lo `test -f 'errno2result.c' || echo '$(srcdir)/'`errno2result.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-errno2result.Tpo $(DEPDIR)/libisc_la-errno2result.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='errno2result.c' object='libisc_la-errno2result.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-errno2result.lo `test -f 'errno2result.c' || echo '$(srcdir)/'`errno2result.c + +libisc_la-error.lo: error.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-error.lo -MD -MP -MF $(DEPDIR)/libisc_la-error.Tpo -c -o libisc_la-error.lo `test -f 'error.c' || echo '$(srcdir)/'`error.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-error.Tpo $(DEPDIR)/libisc_la-error.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='error.c' object='libisc_la-error.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-error.lo `test -f 'error.c' || echo '$(srcdir)/'`error.c + +libisc_la-event.lo: event.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-event.lo -MD -MP -MF $(DEPDIR)/libisc_la-event.Tpo -c -o libisc_la-event.lo `test -f 'event.c' || echo '$(srcdir)/'`event.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-event.Tpo $(DEPDIR)/libisc_la-event.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='event.c' object='libisc_la-event.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-event.lo `test -f 'event.c' || echo '$(srcdir)/'`event.c + +libisc_la-file.lo: file.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-file.lo -MD -MP -MF $(DEPDIR)/libisc_la-file.Tpo -c -o libisc_la-file.lo `test -f 'file.c' || echo '$(srcdir)/'`file.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-file.Tpo $(DEPDIR)/libisc_la-file.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='file.c' object='libisc_la-file.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-file.lo `test -f 'file.c' || echo '$(srcdir)/'`file.c + +libisc_la-glob.lo: glob.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-glob.lo -MD -MP -MF $(DEPDIR)/libisc_la-glob.Tpo -c -o libisc_la-glob.lo `test -f 'glob.c' || echo '$(srcdir)/'`glob.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-glob.Tpo $(DEPDIR)/libisc_la-glob.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='glob.c' object='libisc_la-glob.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-glob.lo `test -f 'glob.c' || echo '$(srcdir)/'`glob.c + +libisc_la-hash.lo: hash.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-hash.lo -MD -MP -MF $(DEPDIR)/libisc_la-hash.Tpo -c -o libisc_la-hash.lo `test -f 'hash.c' || echo '$(srcdir)/'`hash.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-hash.Tpo $(DEPDIR)/libisc_la-hash.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='hash.c' object='libisc_la-hash.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-hash.lo `test -f 'hash.c' || echo '$(srcdir)/'`hash.c + +libisc_la-heap.lo: heap.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-heap.lo -MD -MP -MF $(DEPDIR)/libisc_la-heap.Tpo -c -o libisc_la-heap.lo `test -f 'heap.c' || echo '$(srcdir)/'`heap.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-heap.Tpo $(DEPDIR)/libisc_la-heap.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='heap.c' object='libisc_la-heap.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-heap.lo `test -f 'heap.c' || echo '$(srcdir)/'`heap.c + +libisc_la-hex.lo: hex.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-hex.lo -MD -MP -MF $(DEPDIR)/libisc_la-hex.Tpo -c -o libisc_la-hex.lo `test -f 'hex.c' || echo '$(srcdir)/'`hex.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-hex.Tpo $(DEPDIR)/libisc_la-hex.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='hex.c' object='libisc_la-hex.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-hex.lo `test -f 'hex.c' || echo '$(srcdir)/'`hex.c + +libisc_la-hmac.lo: hmac.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-hmac.lo -MD -MP -MF $(DEPDIR)/libisc_la-hmac.Tpo -c -o libisc_la-hmac.lo `test -f 'hmac.c' || echo '$(srcdir)/'`hmac.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-hmac.Tpo $(DEPDIR)/libisc_la-hmac.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='hmac.c' object='libisc_la-hmac.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-hmac.lo `test -f 'hmac.c' || echo '$(srcdir)/'`hmac.c + +libisc_la-ht.lo: ht.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-ht.lo -MD -MP -MF $(DEPDIR)/libisc_la-ht.Tpo -c -o libisc_la-ht.lo `test -f 'ht.c' || echo '$(srcdir)/'`ht.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-ht.Tpo $(DEPDIR)/libisc_la-ht.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ht.c' object='libisc_la-ht.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-ht.lo `test -f 'ht.c' || echo '$(srcdir)/'`ht.c + +libisc_la-httpd.lo: httpd.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-httpd.lo -MD -MP -MF $(DEPDIR)/libisc_la-httpd.Tpo -c -o libisc_la-httpd.lo `test -f 'httpd.c' || echo '$(srcdir)/'`httpd.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-httpd.Tpo $(DEPDIR)/libisc_la-httpd.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='httpd.c' object='libisc_la-httpd.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-httpd.lo `test -f 'httpd.c' || echo '$(srcdir)/'`httpd.c + +libisc_la-interfaceiter.lo: interfaceiter.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-interfaceiter.lo -MD -MP -MF $(DEPDIR)/libisc_la-interfaceiter.Tpo -c -o libisc_la-interfaceiter.lo `test -f 'interfaceiter.c' || echo '$(srcdir)/'`interfaceiter.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-interfaceiter.Tpo $(DEPDIR)/libisc_la-interfaceiter.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='interfaceiter.c' object='libisc_la-interfaceiter.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-interfaceiter.lo `test -f 'interfaceiter.c' || echo '$(srcdir)/'`interfaceiter.c + +libisc_la-iterated_hash.lo: iterated_hash.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-iterated_hash.lo -MD -MP -MF $(DEPDIR)/libisc_la-iterated_hash.Tpo -c -o libisc_la-iterated_hash.lo `test -f 'iterated_hash.c' || echo '$(srcdir)/'`iterated_hash.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-iterated_hash.Tpo $(DEPDIR)/libisc_la-iterated_hash.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='iterated_hash.c' object='libisc_la-iterated_hash.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-iterated_hash.lo `test -f 'iterated_hash.c' || echo '$(srcdir)/'`iterated_hash.c + +libisc_la-lex.lo: lex.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-lex.lo -MD -MP -MF $(DEPDIR)/libisc_la-lex.Tpo -c -o libisc_la-lex.lo `test -f 'lex.c' || echo '$(srcdir)/'`lex.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-lex.Tpo $(DEPDIR)/libisc_la-lex.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lex.c' object='libisc_la-lex.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-lex.lo `test -f 'lex.c' || echo '$(srcdir)/'`lex.c + +libisc_la-lib.lo: lib.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-lib.lo -MD -MP -MF $(DEPDIR)/libisc_la-lib.Tpo -c -o libisc_la-lib.lo `test -f 'lib.c' || echo '$(srcdir)/'`lib.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-lib.Tpo $(DEPDIR)/libisc_la-lib.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lib.c' object='libisc_la-lib.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-lib.lo `test -f 'lib.c' || echo '$(srcdir)/'`lib.c + +libisc_la-log.lo: log.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-log.lo -MD -MP -MF $(DEPDIR)/libisc_la-log.Tpo -c -o libisc_la-log.lo `test -f 'log.c' || echo '$(srcdir)/'`log.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-log.Tpo $(DEPDIR)/libisc_la-log.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='log.c' object='libisc_la-log.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-log.lo `test -f 'log.c' || echo '$(srcdir)/'`log.c + +libisc_la-managers.lo: managers.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-managers.lo -MD -MP -MF $(DEPDIR)/libisc_la-managers.Tpo -c -o libisc_la-managers.lo `test -f 'managers.c' || echo '$(srcdir)/'`managers.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-managers.Tpo $(DEPDIR)/libisc_la-managers.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='managers.c' object='libisc_la-managers.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-managers.lo `test -f 'managers.c' || echo '$(srcdir)/'`managers.c + +libisc_la-md.lo: md.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-md.lo -MD -MP -MF $(DEPDIR)/libisc_la-md.Tpo -c -o libisc_la-md.lo `test -f 'md.c' || echo '$(srcdir)/'`md.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-md.Tpo $(DEPDIR)/libisc_la-md.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='md.c' object='libisc_la-md.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-md.lo `test -f 'md.c' || echo '$(srcdir)/'`md.c + +libisc_la-mem.lo: mem.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-mem.lo -MD -MP -MF $(DEPDIR)/libisc_la-mem.Tpo -c -o libisc_la-mem.lo `test -f 'mem.c' || echo '$(srcdir)/'`mem.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-mem.Tpo $(DEPDIR)/libisc_la-mem.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mem.c' object='libisc_la-mem.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-mem.lo `test -f 'mem.c' || echo '$(srcdir)/'`mem.c + +libisc_la-meminfo.lo: meminfo.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-meminfo.lo -MD -MP -MF $(DEPDIR)/libisc_la-meminfo.Tpo -c -o libisc_la-meminfo.lo `test -f 'meminfo.c' || echo '$(srcdir)/'`meminfo.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-meminfo.Tpo $(DEPDIR)/libisc_la-meminfo.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='meminfo.c' object='libisc_la-meminfo.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-meminfo.lo `test -f 'meminfo.c' || echo '$(srcdir)/'`meminfo.c + +libisc_la-mutex.lo: mutex.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-mutex.lo -MD -MP -MF $(DEPDIR)/libisc_la-mutex.Tpo -c -o libisc_la-mutex.lo `test -f 'mutex.c' || echo '$(srcdir)/'`mutex.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-mutex.Tpo $(DEPDIR)/libisc_la-mutex.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mutex.c' object='libisc_la-mutex.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-mutex.lo `test -f 'mutex.c' || echo '$(srcdir)/'`mutex.c + +libisc_la-mutexblock.lo: mutexblock.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-mutexblock.lo -MD -MP -MF $(DEPDIR)/libisc_la-mutexblock.Tpo -c -o libisc_la-mutexblock.lo `test -f 'mutexblock.c' || echo '$(srcdir)/'`mutexblock.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-mutexblock.Tpo $(DEPDIR)/libisc_la-mutexblock.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mutexblock.c' object='libisc_la-mutexblock.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-mutexblock.lo `test -f 'mutexblock.c' || echo '$(srcdir)/'`mutexblock.c + +libisc_la-net.lo: net.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-net.lo -MD -MP -MF $(DEPDIR)/libisc_la-net.Tpo -c -o libisc_la-net.lo `test -f 'net.c' || echo '$(srcdir)/'`net.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-net.Tpo $(DEPDIR)/libisc_la-net.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='net.c' object='libisc_la-net.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-net.lo `test -f 'net.c' || echo '$(srcdir)/'`net.c + +libisc_la-netaddr.lo: netaddr.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-netaddr.lo -MD -MP -MF $(DEPDIR)/libisc_la-netaddr.Tpo -c -o libisc_la-netaddr.lo `test -f 'netaddr.c' || echo '$(srcdir)/'`netaddr.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-netaddr.Tpo $(DEPDIR)/libisc_la-netaddr.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='netaddr.c' object='libisc_la-netaddr.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-netaddr.lo `test -f 'netaddr.c' || echo '$(srcdir)/'`netaddr.c + +libisc_la-netscope.lo: netscope.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-netscope.lo -MD -MP -MF $(DEPDIR)/libisc_la-netscope.Tpo -c -o libisc_la-netscope.lo `test -f 'netscope.c' || echo '$(srcdir)/'`netscope.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-netscope.Tpo $(DEPDIR)/libisc_la-netscope.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='netscope.c' object='libisc_la-netscope.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-netscope.lo `test -f 'netscope.c' || echo '$(srcdir)/'`netscope.c + +libisc_la-nonce.lo: nonce.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-nonce.lo -MD -MP -MF $(DEPDIR)/libisc_la-nonce.Tpo -c -o libisc_la-nonce.lo `test -f 'nonce.c' || echo '$(srcdir)/'`nonce.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-nonce.Tpo $(DEPDIR)/libisc_la-nonce.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='nonce.c' object='libisc_la-nonce.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-nonce.lo `test -f 'nonce.c' || echo '$(srcdir)/'`nonce.c + +libisc_la-openssl_shim.lo: openssl_shim.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-openssl_shim.lo -MD -MP -MF $(DEPDIR)/libisc_la-openssl_shim.Tpo -c -o libisc_la-openssl_shim.lo `test -f 'openssl_shim.c' || echo '$(srcdir)/'`openssl_shim.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-openssl_shim.Tpo $(DEPDIR)/libisc_la-openssl_shim.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='openssl_shim.c' object='libisc_la-openssl_shim.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-openssl_shim.lo `test -f 'openssl_shim.c' || echo '$(srcdir)/'`openssl_shim.c + +libisc_la-os.lo: os.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-os.lo -MD -MP -MF $(DEPDIR)/libisc_la-os.Tpo -c -o libisc_la-os.lo `test -f 'os.c' || echo '$(srcdir)/'`os.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-os.Tpo $(DEPDIR)/libisc_la-os.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='os.c' object='libisc_la-os.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-os.lo `test -f 'os.c' || echo '$(srcdir)/'`os.c + +libisc_la-parseint.lo: parseint.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-parseint.lo -MD -MP -MF $(DEPDIR)/libisc_la-parseint.Tpo -c -o libisc_la-parseint.lo `test -f 'parseint.c' || echo '$(srcdir)/'`parseint.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-parseint.Tpo $(DEPDIR)/libisc_la-parseint.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='parseint.c' object='libisc_la-parseint.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-parseint.lo `test -f 'parseint.c' || echo '$(srcdir)/'`parseint.c + +libisc_la-pool.lo: pool.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-pool.lo -MD -MP -MF $(DEPDIR)/libisc_la-pool.Tpo -c -o libisc_la-pool.lo `test -f 'pool.c' || echo '$(srcdir)/'`pool.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-pool.Tpo $(DEPDIR)/libisc_la-pool.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='pool.c' object='libisc_la-pool.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-pool.lo `test -f 'pool.c' || echo '$(srcdir)/'`pool.c + +libisc_la-picohttpparser.lo: picohttpparser.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-picohttpparser.lo -MD -MP -MF $(DEPDIR)/libisc_la-picohttpparser.Tpo -c -o libisc_la-picohttpparser.lo `test -f 'picohttpparser.c' || echo '$(srcdir)/'`picohttpparser.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-picohttpparser.Tpo $(DEPDIR)/libisc_la-picohttpparser.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='picohttpparser.c' object='libisc_la-picohttpparser.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-picohttpparser.lo `test -f 'picohttpparser.c' || echo '$(srcdir)/'`picohttpparser.c + +libisc_la-portset.lo: portset.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-portset.lo -MD -MP -MF $(DEPDIR)/libisc_la-portset.Tpo -c -o libisc_la-portset.lo `test -f 'portset.c' || echo '$(srcdir)/'`portset.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-portset.Tpo $(DEPDIR)/libisc_la-portset.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='portset.c' object='libisc_la-portset.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-portset.lo `test -f 'portset.c' || echo '$(srcdir)/'`portset.c + +libisc_la-quota.lo: quota.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-quota.lo -MD -MP -MF $(DEPDIR)/libisc_la-quota.Tpo -c -o libisc_la-quota.lo `test -f 'quota.c' || echo '$(srcdir)/'`quota.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-quota.Tpo $(DEPDIR)/libisc_la-quota.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='quota.c' object='libisc_la-quota.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-quota.lo `test -f 'quota.c' || echo '$(srcdir)/'`quota.c + +libisc_la-radix.lo: radix.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-radix.lo -MD -MP -MF $(DEPDIR)/libisc_la-radix.Tpo -c -o libisc_la-radix.lo `test -f 'radix.c' || echo '$(srcdir)/'`radix.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-radix.Tpo $(DEPDIR)/libisc_la-radix.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='radix.c' object='libisc_la-radix.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-radix.lo `test -f 'radix.c' || echo '$(srcdir)/'`radix.c + +libisc_la-random.lo: random.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-random.lo -MD -MP -MF $(DEPDIR)/libisc_la-random.Tpo -c -o libisc_la-random.lo `test -f 'random.c' || echo '$(srcdir)/'`random.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-random.Tpo $(DEPDIR)/libisc_la-random.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='random.c' object='libisc_la-random.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-random.lo `test -f 'random.c' || echo '$(srcdir)/'`random.c + +libisc_la-ratelimiter.lo: ratelimiter.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-ratelimiter.lo -MD -MP -MF $(DEPDIR)/libisc_la-ratelimiter.Tpo -c -o libisc_la-ratelimiter.lo `test -f 'ratelimiter.c' || echo '$(srcdir)/'`ratelimiter.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-ratelimiter.Tpo $(DEPDIR)/libisc_la-ratelimiter.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ratelimiter.c' object='libisc_la-ratelimiter.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-ratelimiter.lo `test -f 'ratelimiter.c' || echo '$(srcdir)/'`ratelimiter.c + +libisc_la-regex.lo: regex.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-regex.lo -MD -MP -MF $(DEPDIR)/libisc_la-regex.Tpo -c -o libisc_la-regex.lo `test -f 'regex.c' || echo '$(srcdir)/'`regex.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-regex.Tpo $(DEPDIR)/libisc_la-regex.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='regex.c' object='libisc_la-regex.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-regex.lo `test -f 'regex.c' || echo '$(srcdir)/'`regex.c + +libisc_la-region.lo: region.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-region.lo -MD -MP -MF $(DEPDIR)/libisc_la-region.Tpo -c -o libisc_la-region.lo `test -f 'region.c' || echo '$(srcdir)/'`region.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-region.Tpo $(DEPDIR)/libisc_la-region.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='region.c' object='libisc_la-region.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-region.lo `test -f 'region.c' || echo '$(srcdir)/'`region.c + +libisc_la-resource.lo: resource.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-resource.lo -MD -MP -MF $(DEPDIR)/libisc_la-resource.Tpo -c -o libisc_la-resource.lo `test -f 'resource.c' || echo '$(srcdir)/'`resource.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-resource.Tpo $(DEPDIR)/libisc_la-resource.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='resource.c' object='libisc_la-resource.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-resource.lo `test -f 'resource.c' || echo '$(srcdir)/'`resource.c + +libisc_la-result.lo: result.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-result.lo -MD -MP -MF $(DEPDIR)/libisc_la-result.Tpo -c -o libisc_la-result.lo `test -f 'result.c' || echo '$(srcdir)/'`result.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-result.Tpo $(DEPDIR)/libisc_la-result.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='result.c' object='libisc_la-result.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-result.lo `test -f 'result.c' || echo '$(srcdir)/'`result.c + +libisc_la-rwlock.lo: rwlock.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-rwlock.lo -MD -MP -MF $(DEPDIR)/libisc_la-rwlock.Tpo -c -o libisc_la-rwlock.lo `test -f 'rwlock.c' || echo '$(srcdir)/'`rwlock.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-rwlock.Tpo $(DEPDIR)/libisc_la-rwlock.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='rwlock.c' object='libisc_la-rwlock.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-rwlock.lo `test -f 'rwlock.c' || echo '$(srcdir)/'`rwlock.c + +libisc_la-safe.lo: safe.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-safe.lo -MD -MP -MF $(DEPDIR)/libisc_la-safe.Tpo -c -o libisc_la-safe.lo `test -f 'safe.c' || echo '$(srcdir)/'`safe.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-safe.Tpo $(DEPDIR)/libisc_la-safe.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='safe.c' object='libisc_la-safe.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-safe.lo `test -f 'safe.c' || echo '$(srcdir)/'`safe.c + +libisc_la-serial.lo: serial.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-serial.lo -MD -MP -MF $(DEPDIR)/libisc_la-serial.Tpo -c -o libisc_la-serial.lo `test -f 'serial.c' || echo '$(srcdir)/'`serial.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-serial.Tpo $(DEPDIR)/libisc_la-serial.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='serial.c' object='libisc_la-serial.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-serial.lo `test -f 'serial.c' || echo '$(srcdir)/'`serial.c + +libisc_la-siphash.lo: siphash.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-siphash.lo -MD -MP -MF $(DEPDIR)/libisc_la-siphash.Tpo -c -o libisc_la-siphash.lo `test -f 'siphash.c' || echo '$(srcdir)/'`siphash.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-siphash.Tpo $(DEPDIR)/libisc_la-siphash.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='siphash.c' object='libisc_la-siphash.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-siphash.lo `test -f 'siphash.c' || echo '$(srcdir)/'`siphash.c + +libisc_la-sockaddr.lo: sockaddr.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-sockaddr.lo -MD -MP -MF $(DEPDIR)/libisc_la-sockaddr.Tpo -c -o libisc_la-sockaddr.lo `test -f 'sockaddr.c' || echo '$(srcdir)/'`sockaddr.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-sockaddr.Tpo $(DEPDIR)/libisc_la-sockaddr.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='sockaddr.c' object='libisc_la-sockaddr.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-sockaddr.lo `test -f 'sockaddr.c' || echo '$(srcdir)/'`sockaddr.c + +libisc_la-stats.lo: stats.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-stats.lo -MD -MP -MF $(DEPDIR)/libisc_la-stats.Tpo -c -o libisc_la-stats.lo `test -f 'stats.c' || echo '$(srcdir)/'`stats.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-stats.Tpo $(DEPDIR)/libisc_la-stats.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='stats.c' object='libisc_la-stats.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-stats.lo `test -f 'stats.c' || echo '$(srcdir)/'`stats.c + +libisc_la-stdio.lo: stdio.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-stdio.lo -MD -MP -MF $(DEPDIR)/libisc_la-stdio.Tpo -c -o libisc_la-stdio.lo `test -f 'stdio.c' || echo '$(srcdir)/'`stdio.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-stdio.Tpo $(DEPDIR)/libisc_la-stdio.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='stdio.c' object='libisc_la-stdio.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-stdio.lo `test -f 'stdio.c' || echo '$(srcdir)/'`stdio.c + +libisc_la-stdtime.lo: stdtime.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-stdtime.lo -MD -MP -MF $(DEPDIR)/libisc_la-stdtime.Tpo -c -o libisc_la-stdtime.lo `test -f 'stdtime.c' || echo '$(srcdir)/'`stdtime.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-stdtime.Tpo $(DEPDIR)/libisc_la-stdtime.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='stdtime.c' object='libisc_la-stdtime.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-stdtime.lo `test -f 'stdtime.c' || echo '$(srcdir)/'`stdtime.c + +libisc_la-string.lo: string.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-string.lo -MD -MP -MF $(DEPDIR)/libisc_la-string.Tpo -c -o libisc_la-string.lo `test -f 'string.c' || echo '$(srcdir)/'`string.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-string.Tpo $(DEPDIR)/libisc_la-string.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='string.c' object='libisc_la-string.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-string.lo `test -f 'string.c' || echo '$(srcdir)/'`string.c + +libisc_la-symtab.lo: symtab.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-symtab.lo -MD -MP -MF $(DEPDIR)/libisc_la-symtab.Tpo -c -o libisc_la-symtab.lo `test -f 'symtab.c' || echo '$(srcdir)/'`symtab.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-symtab.Tpo $(DEPDIR)/libisc_la-symtab.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='symtab.c' object='libisc_la-symtab.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-symtab.lo `test -f 'symtab.c' || echo '$(srcdir)/'`symtab.c + +libisc_la-syslog.lo: syslog.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-syslog.lo -MD -MP -MF $(DEPDIR)/libisc_la-syslog.Tpo -c -o libisc_la-syslog.lo `test -f 'syslog.c' || echo '$(srcdir)/'`syslog.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-syslog.Tpo $(DEPDIR)/libisc_la-syslog.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='syslog.c' object='libisc_la-syslog.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-syslog.lo `test -f 'syslog.c' || echo '$(srcdir)/'`syslog.c + +libisc_la-task.lo: task.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-task.lo -MD -MP -MF $(DEPDIR)/libisc_la-task.Tpo -c -o libisc_la-task.lo `test -f 'task.c' || echo '$(srcdir)/'`task.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-task.Tpo $(DEPDIR)/libisc_la-task.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='task.c' object='libisc_la-task.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-task.lo `test -f 'task.c' || echo '$(srcdir)/'`task.c + +libisc_la-taskpool.lo: taskpool.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-taskpool.lo -MD -MP -MF $(DEPDIR)/libisc_la-taskpool.Tpo -c -o libisc_la-taskpool.lo `test -f 'taskpool.c' || echo '$(srcdir)/'`taskpool.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-taskpool.Tpo $(DEPDIR)/libisc_la-taskpool.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='taskpool.c' object='libisc_la-taskpool.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-taskpool.lo `test -f 'taskpool.c' || echo '$(srcdir)/'`taskpool.c + +libisc_la-thread.lo: thread.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-thread.lo -MD -MP -MF $(DEPDIR)/libisc_la-thread.Tpo -c -o libisc_la-thread.lo `test -f 'thread.c' || echo '$(srcdir)/'`thread.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-thread.Tpo $(DEPDIR)/libisc_la-thread.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='thread.c' object='libisc_la-thread.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-thread.lo `test -f 'thread.c' || echo '$(srcdir)/'`thread.c + +libisc_la-time.lo: time.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-time.lo -MD -MP -MF $(DEPDIR)/libisc_la-time.Tpo -c -o libisc_la-time.lo `test -f 'time.c' || echo '$(srcdir)/'`time.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-time.Tpo $(DEPDIR)/libisc_la-time.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='time.c' object='libisc_la-time.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-time.lo `test -f 'time.c' || echo '$(srcdir)/'`time.c + +libisc_la-timer.lo: timer.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-timer.lo -MD -MP -MF $(DEPDIR)/libisc_la-timer.Tpo -c -o libisc_la-timer.lo `test -f 'timer.c' || echo '$(srcdir)/'`timer.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-timer.Tpo $(DEPDIR)/libisc_la-timer.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='timer.c' object='libisc_la-timer.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-timer.lo `test -f 'timer.c' || echo '$(srcdir)/'`timer.c + +libisc_la-tls.lo: tls.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-tls.lo -MD -MP -MF $(DEPDIR)/libisc_la-tls.Tpo -c -o libisc_la-tls.lo `test -f 'tls.c' || echo '$(srcdir)/'`tls.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-tls.Tpo $(DEPDIR)/libisc_la-tls.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tls.c' object='libisc_la-tls.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-tls.lo `test -f 'tls.c' || echo '$(srcdir)/'`tls.c + +libisc_la-tm.lo: tm.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-tm.lo -MD -MP -MF $(DEPDIR)/libisc_la-tm.Tpo -c -o libisc_la-tm.lo `test -f 'tm.c' || echo '$(srcdir)/'`tm.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-tm.Tpo $(DEPDIR)/libisc_la-tm.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tm.c' object='libisc_la-tm.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-tm.lo `test -f 'tm.c' || echo '$(srcdir)/'`tm.c + +libisc_la-trampoline.lo: trampoline.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-trampoline.lo -MD -MP -MF $(DEPDIR)/libisc_la-trampoline.Tpo -c -o libisc_la-trampoline.lo `test -f 'trampoline.c' || echo '$(srcdir)/'`trampoline.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-trampoline.Tpo $(DEPDIR)/libisc_la-trampoline.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='trampoline.c' object='libisc_la-trampoline.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-trampoline.lo `test -f 'trampoline.c' || echo '$(srcdir)/'`trampoline.c + +libisc_la-url.lo: url.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-url.lo -MD -MP -MF $(DEPDIR)/libisc_la-url.Tpo -c -o libisc_la-url.lo `test -f 'url.c' || echo '$(srcdir)/'`url.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-url.Tpo $(DEPDIR)/libisc_la-url.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='url.c' object='libisc_la-url.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-url.lo `test -f 'url.c' || echo '$(srcdir)/'`url.c + +libisc_la-utf8.lo: utf8.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-utf8.lo -MD -MP -MF $(DEPDIR)/libisc_la-utf8.Tpo -c -o libisc_la-utf8.lo `test -f 'utf8.c' || echo '$(srcdir)/'`utf8.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-utf8.Tpo $(DEPDIR)/libisc_la-utf8.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='utf8.c' object='libisc_la-utf8.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-utf8.lo `test -f 'utf8.c' || echo '$(srcdir)/'`utf8.c + +netmgr/libisc_la-http.lo: netmgr/http.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT netmgr/libisc_la-http.lo -MD -MP -MF netmgr/$(DEPDIR)/libisc_la-http.Tpo -c -o netmgr/libisc_la-http.lo `test -f 'netmgr/http.c' || echo '$(srcdir)/'`netmgr/http.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) netmgr/$(DEPDIR)/libisc_la-http.Tpo netmgr/$(DEPDIR)/libisc_la-http.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='netmgr/http.c' object='netmgr/libisc_la-http.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o netmgr/libisc_la-http.lo `test -f 'netmgr/http.c' || echo '$(srcdir)/'`netmgr/http.c + +netmgr/libisc_la-tlsstream.lo: netmgr/tlsstream.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT netmgr/libisc_la-tlsstream.lo -MD -MP -MF netmgr/$(DEPDIR)/libisc_la-tlsstream.Tpo -c -o netmgr/libisc_la-tlsstream.lo `test -f 'netmgr/tlsstream.c' || echo '$(srcdir)/'`netmgr/tlsstream.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) netmgr/$(DEPDIR)/libisc_la-tlsstream.Tpo netmgr/$(DEPDIR)/libisc_la-tlsstream.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='netmgr/tlsstream.c' object='netmgr/libisc_la-tlsstream.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o netmgr/libisc_la-tlsstream.lo `test -f 'netmgr/tlsstream.c' || echo '$(srcdir)/'`netmgr/tlsstream.c + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + -rm -rf netmgr/.libs netmgr/_libs +install-libisc_laHEADERS: $(libisc_la_HEADERS) + @$(NORMAL_INSTALL) + @list='$(libisc_la_HEADERS)'; test -n "$(libisc_ladir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(libisc_ladir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(libisc_ladir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(libisc_ladir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(libisc_ladir)" || exit $$?; \ + done + +uninstall-libisc_laHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(libisc_la_HEADERS)'; test -n "$(libisc_ladir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(libisc_ladir)'; $(am__uninstall_files_from_dir) +test-local: +unit-local: +doc-local: + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libisc_ladir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + -rm -f netmgr/$(DEPDIR)/$(am__dirstamp) + -rm -f netmgr/$(am__dirstamp) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/libisc_la-aes.Plo + -rm -f ./$(DEPDIR)/libisc_la-app.Plo + -rm -f ./$(DEPDIR)/libisc_la-assertions.Plo + -rm -f ./$(DEPDIR)/libisc_la-astack.Plo + -rm -f ./$(DEPDIR)/libisc_la-backtrace.Plo + -rm -f ./$(DEPDIR)/libisc_la-base32.Plo + -rm -f ./$(DEPDIR)/libisc_la-base64.Plo + -rm -f ./$(DEPDIR)/libisc_la-buffer.Plo + -rm -f ./$(DEPDIR)/libisc_la-commandline.Plo + -rm -f ./$(DEPDIR)/libisc_la-condition.Plo + -rm -f ./$(DEPDIR)/libisc_la-counter.Plo + -rm -f ./$(DEPDIR)/libisc_la-crc64.Plo + -rm -f ./$(DEPDIR)/libisc_la-dir.Plo + -rm -f ./$(DEPDIR)/libisc_la-entropy.Plo + -rm -f ./$(DEPDIR)/libisc_la-errno.Plo + -rm -f ./$(DEPDIR)/libisc_la-errno2result.Plo + -rm -f ./$(DEPDIR)/libisc_la-error.Plo + -rm -f ./$(DEPDIR)/libisc_la-event.Plo + -rm -f ./$(DEPDIR)/libisc_la-file.Plo + -rm -f ./$(DEPDIR)/libisc_la-glob.Plo + -rm -f ./$(DEPDIR)/libisc_la-hash.Plo + -rm -f ./$(DEPDIR)/libisc_la-heap.Plo + -rm -f ./$(DEPDIR)/libisc_la-hex.Plo + -rm -f ./$(DEPDIR)/libisc_la-hmac.Plo + -rm -f ./$(DEPDIR)/libisc_la-ht.Plo + -rm -f ./$(DEPDIR)/libisc_la-httpd.Plo + -rm -f ./$(DEPDIR)/libisc_la-interfaceiter.Plo + -rm -f ./$(DEPDIR)/libisc_la-iterated_hash.Plo + -rm -f ./$(DEPDIR)/libisc_la-lex.Plo + -rm -f ./$(DEPDIR)/libisc_la-lib.Plo + -rm -f ./$(DEPDIR)/libisc_la-log.Plo + -rm -f ./$(DEPDIR)/libisc_la-managers.Plo + -rm -f ./$(DEPDIR)/libisc_la-md.Plo + -rm -f ./$(DEPDIR)/libisc_la-mem.Plo + -rm -f ./$(DEPDIR)/libisc_la-meminfo.Plo + -rm -f ./$(DEPDIR)/libisc_la-mutex.Plo + -rm -f ./$(DEPDIR)/libisc_la-mutexblock.Plo + -rm -f ./$(DEPDIR)/libisc_la-net.Plo + -rm -f ./$(DEPDIR)/libisc_la-netaddr.Plo + -rm -f ./$(DEPDIR)/libisc_la-netscope.Plo + -rm -f ./$(DEPDIR)/libisc_la-nonce.Plo + -rm -f ./$(DEPDIR)/libisc_la-openssl_shim.Plo + -rm -f ./$(DEPDIR)/libisc_la-os.Plo + -rm -f ./$(DEPDIR)/libisc_la-parseint.Plo + -rm -f ./$(DEPDIR)/libisc_la-picohttpparser.Plo + -rm -f ./$(DEPDIR)/libisc_la-pool.Plo + -rm -f ./$(DEPDIR)/libisc_la-portset.Plo + -rm -f ./$(DEPDIR)/libisc_la-quota.Plo + -rm -f ./$(DEPDIR)/libisc_la-radix.Plo + -rm -f ./$(DEPDIR)/libisc_la-random.Plo + -rm -f ./$(DEPDIR)/libisc_la-ratelimiter.Plo + -rm -f ./$(DEPDIR)/libisc_la-regex.Plo + -rm -f ./$(DEPDIR)/libisc_la-region.Plo + -rm -f ./$(DEPDIR)/libisc_la-resource.Plo + -rm -f ./$(DEPDIR)/libisc_la-result.Plo + -rm -f ./$(DEPDIR)/libisc_la-rwlock.Plo + -rm -f ./$(DEPDIR)/libisc_la-safe.Plo + -rm -f ./$(DEPDIR)/libisc_la-serial.Plo + -rm -f ./$(DEPDIR)/libisc_la-siphash.Plo + -rm -f ./$(DEPDIR)/libisc_la-sockaddr.Plo + -rm -f ./$(DEPDIR)/libisc_la-stats.Plo + -rm -f ./$(DEPDIR)/libisc_la-stdio.Plo + -rm -f ./$(DEPDIR)/libisc_la-stdtime.Plo + -rm -f ./$(DEPDIR)/libisc_la-string.Plo + -rm -f ./$(DEPDIR)/libisc_la-symtab.Plo + -rm -f ./$(DEPDIR)/libisc_la-syslog.Plo + -rm -f ./$(DEPDIR)/libisc_la-task.Plo + -rm -f ./$(DEPDIR)/libisc_la-taskpool.Plo + -rm -f ./$(DEPDIR)/libisc_la-thread.Plo + -rm -f ./$(DEPDIR)/libisc_la-time.Plo + -rm -f ./$(DEPDIR)/libisc_la-timer.Plo + -rm -f ./$(DEPDIR)/libisc_la-tls.Plo + -rm -f ./$(DEPDIR)/libisc_la-tm.Plo + -rm -f ./$(DEPDIR)/libisc_la-trampoline.Plo + -rm -f ./$(DEPDIR)/libisc_la-url.Plo + -rm -f ./$(DEPDIR)/libisc_la-utf8.Plo + -rm -f netmgr/$(DEPDIR)/libisc_la-http.Plo + -rm -f netmgr/$(DEPDIR)/libisc_la-netmgr.Plo + -rm -f netmgr/$(DEPDIR)/libisc_la-tcp.Plo + -rm -f netmgr/$(DEPDIR)/libisc_la-tcpdns.Plo + -rm -f netmgr/$(DEPDIR)/libisc_la-timer.Plo + -rm -f netmgr/$(DEPDIR)/libisc_la-tlsdns.Plo + -rm -f netmgr/$(DEPDIR)/libisc_la-tlsstream.Plo + -rm -f netmgr/$(DEPDIR)/libisc_la-udp.Plo + -rm -f netmgr/$(DEPDIR)/libisc_la-uv-compat.Plo + -rm -f netmgr/$(DEPDIR)/libisc_la-uverr2result.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +doc: doc-am + +doc-am: doc-local + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-libisc_laHEADERS + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-libLTLIBRARIES + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/libisc_la-aes.Plo + -rm -f ./$(DEPDIR)/libisc_la-app.Plo + -rm -f ./$(DEPDIR)/libisc_la-assertions.Plo + -rm -f ./$(DEPDIR)/libisc_la-astack.Plo + -rm -f ./$(DEPDIR)/libisc_la-backtrace.Plo + -rm -f ./$(DEPDIR)/libisc_la-base32.Plo + -rm -f ./$(DEPDIR)/libisc_la-base64.Plo + -rm -f ./$(DEPDIR)/libisc_la-buffer.Plo + -rm -f ./$(DEPDIR)/libisc_la-commandline.Plo + -rm -f ./$(DEPDIR)/libisc_la-condition.Plo + -rm -f ./$(DEPDIR)/libisc_la-counter.Plo + -rm -f ./$(DEPDIR)/libisc_la-crc64.Plo + -rm -f ./$(DEPDIR)/libisc_la-dir.Plo + -rm -f ./$(DEPDIR)/libisc_la-entropy.Plo + -rm -f ./$(DEPDIR)/libisc_la-errno.Plo + -rm -f ./$(DEPDIR)/libisc_la-errno2result.Plo + -rm -f ./$(DEPDIR)/libisc_la-error.Plo + -rm -f ./$(DEPDIR)/libisc_la-event.Plo + -rm -f ./$(DEPDIR)/libisc_la-file.Plo + -rm -f ./$(DEPDIR)/libisc_la-glob.Plo + -rm -f ./$(DEPDIR)/libisc_la-hash.Plo + -rm -f ./$(DEPDIR)/libisc_la-heap.Plo + -rm -f ./$(DEPDIR)/libisc_la-hex.Plo + -rm -f ./$(DEPDIR)/libisc_la-hmac.Plo + -rm -f ./$(DEPDIR)/libisc_la-ht.Plo + -rm -f ./$(DEPDIR)/libisc_la-httpd.Plo + -rm -f ./$(DEPDIR)/libisc_la-interfaceiter.Plo + -rm -f ./$(DEPDIR)/libisc_la-iterated_hash.Plo + -rm -f ./$(DEPDIR)/libisc_la-lex.Plo + -rm -f ./$(DEPDIR)/libisc_la-lib.Plo + -rm -f ./$(DEPDIR)/libisc_la-log.Plo + -rm -f ./$(DEPDIR)/libisc_la-managers.Plo + -rm -f ./$(DEPDIR)/libisc_la-md.Plo + -rm -f ./$(DEPDIR)/libisc_la-mem.Plo + -rm -f ./$(DEPDIR)/libisc_la-meminfo.Plo + -rm -f ./$(DEPDIR)/libisc_la-mutex.Plo + -rm -f ./$(DEPDIR)/libisc_la-mutexblock.Plo + -rm -f ./$(DEPDIR)/libisc_la-net.Plo + -rm -f ./$(DEPDIR)/libisc_la-netaddr.Plo + -rm -f ./$(DEPDIR)/libisc_la-netscope.Plo + -rm -f ./$(DEPDIR)/libisc_la-nonce.Plo + -rm -f ./$(DEPDIR)/libisc_la-openssl_shim.Plo + -rm -f ./$(DEPDIR)/libisc_la-os.Plo + -rm -f ./$(DEPDIR)/libisc_la-parseint.Plo + -rm -f ./$(DEPDIR)/libisc_la-picohttpparser.Plo + -rm -f ./$(DEPDIR)/libisc_la-pool.Plo + -rm -f ./$(DEPDIR)/libisc_la-portset.Plo + -rm -f ./$(DEPDIR)/libisc_la-quota.Plo + -rm -f ./$(DEPDIR)/libisc_la-radix.Plo + -rm -f ./$(DEPDIR)/libisc_la-random.Plo + -rm -f ./$(DEPDIR)/libisc_la-ratelimiter.Plo + -rm -f ./$(DEPDIR)/libisc_la-regex.Plo + -rm -f ./$(DEPDIR)/libisc_la-region.Plo + -rm -f ./$(DEPDIR)/libisc_la-resource.Plo + -rm -f ./$(DEPDIR)/libisc_la-result.Plo + -rm -f ./$(DEPDIR)/libisc_la-rwlock.Plo + -rm -f ./$(DEPDIR)/libisc_la-safe.Plo + -rm -f ./$(DEPDIR)/libisc_la-serial.Plo + -rm -f ./$(DEPDIR)/libisc_la-siphash.Plo + -rm -f ./$(DEPDIR)/libisc_la-sockaddr.Plo + -rm -f ./$(DEPDIR)/libisc_la-stats.Plo + -rm -f ./$(DEPDIR)/libisc_la-stdio.Plo + -rm -f ./$(DEPDIR)/libisc_la-stdtime.Plo + -rm -f ./$(DEPDIR)/libisc_la-string.Plo + -rm -f ./$(DEPDIR)/libisc_la-symtab.Plo + -rm -f ./$(DEPDIR)/libisc_la-syslog.Plo + -rm -f ./$(DEPDIR)/libisc_la-task.Plo + -rm -f ./$(DEPDIR)/libisc_la-taskpool.Plo + -rm -f ./$(DEPDIR)/libisc_la-thread.Plo + -rm -f ./$(DEPDIR)/libisc_la-time.Plo + -rm -f ./$(DEPDIR)/libisc_la-timer.Plo + -rm -f ./$(DEPDIR)/libisc_la-tls.Plo + -rm -f ./$(DEPDIR)/libisc_la-tm.Plo + -rm -f ./$(DEPDIR)/libisc_la-trampoline.Plo + -rm -f ./$(DEPDIR)/libisc_la-url.Plo + -rm -f ./$(DEPDIR)/libisc_la-utf8.Plo + -rm -f netmgr/$(DEPDIR)/libisc_la-http.Plo + -rm -f netmgr/$(DEPDIR)/libisc_la-netmgr.Plo + -rm -f netmgr/$(DEPDIR)/libisc_la-tcp.Plo + -rm -f netmgr/$(DEPDIR)/libisc_la-tcpdns.Plo + -rm -f netmgr/$(DEPDIR)/libisc_la-timer.Plo + -rm -f netmgr/$(DEPDIR)/libisc_la-tlsdns.Plo + -rm -f netmgr/$(DEPDIR)/libisc_la-tlsstream.Plo + -rm -f netmgr/$(DEPDIR)/libisc_la-udp.Plo + -rm -f netmgr/$(DEPDIR)/libisc_la-uv-compat.Plo + -rm -f netmgr/$(DEPDIR)/libisc_la-uverr2result.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +test: test-am + +test-am: test-local + +uninstall-am: uninstall-libLTLIBRARIES uninstall-libisc_laHEADERS + +unit: unit-am + +unit-am: unit-local + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libLTLIBRARIES clean-libtool cscopelist-am \ + ctags ctags-am distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir doc-am doc-local dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-libLTLIBRARIES \ + install-libisc_laHEADERS install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am test-am test-local uninstall uninstall-am \ + uninstall-libLTLIBRARIES uninstall-libisc_laHEADERS unit-am \ + unit-local + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/lib/isc/aes.c b/lib/isc/aes.c new file mode 100644 index 0000000..d136bd4 --- /dev/null +++ b/lib/isc/aes.c @@ -0,0 +1,71 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/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..da438d7 --- /dev/null +++ b/lib/isc/app.c @@ -0,0 +1,421 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 <pthread.h> +#include <signal.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdlib.h> +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> + +#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/string.h> +#include <isc/task.h> +#include <isc/thread.h> +#include <isc/time.h> +#include <isc/util.h> + +/*% + * For BIND9 applications built with threads, we use a single app + * context and let multiple taskmgr and netmgr threads do actual jobs. + */ + +static isc_thread_t blockedthread; +static atomic_bool is_running = 0; + +/* + * 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) + +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; + isc_mutex_t readylock; + isc_condition_t ready; +}; + +static isc_appctx_t isc_g_appctx; + +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) { + FATAL_SYSERROR(errno, "signal %d", sig); + } +} + +isc_result_t +isc_app_ctxstart(isc_appctx_t *ctx) { + int presult; + sigset_t sset; + + REQUIRE(VALID_APPCTX(ctx)); + + /* + * Start an ISC library application. + */ + + isc_mutex_init(&ctx->lock); + + isc_mutex_init(&ctx->readylock); + isc_condition_init(&ctx->ready); + + 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); + + /* + * 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) + { + FATAL_SYSERROR(errno, "sigsetops"); + } + presult = pthread_sigmask(SIG_BLOCK, &sset, NULL); + if (presult != 0) { + FATAL_SYSERROR(presult, "pthread_sigmask()"); + } + + 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)); + + 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); + } + + /* + * 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)) { + if (ctx == &isc_g_appctx) { + sigset_t sset; + int sig; + /* + * Wait for SIGHUP, SIGINT, or SIGTERM. + */ + if (sigemptyset(&sset) != 0 || + sigaddset(&sset, SIGHUP) != 0 || + sigaddset(&sset, SIGINT) != 0 || + sigaddset(&sset, SIGTERM) != 0) + { + FATAL_SYSERROR(errno, "sigsetops"); + } + + 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 { + /* + * Tools using multiple contexts don't + * rely on a signal, just 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); + } + } + 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; + + atomic_compare_exchange_enforced(&is_running, &(bool){ false }, true); + + result = isc_app_ctxrun(&isc_g_appctx); + atomic_store_release(&is_running, false); + + return (result); +} + +bool +isc_app_isrunning(void) { + 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)) + { + if (ctx != &isc_g_appctx) { + /* Tool using multiple contexts */ + atomic_store_release(&ctx->want_shutdown, true); + SIGNAL(&ctx->ready); + } else { + /* Normal single BIND9 context */ + if (kill(getpid(), SIGTERM) < 0) { + FATAL_SYSERROR(errno, "kill"); + } + } + } +} + +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)) { + if (ctx != &isc_g_appctx) { + /* Tool using multiple contexts */ + atomic_store_release(&ctx->want_reload, true); + SIGNAL(&ctx->ready); + } else { + /* Normal single BIND9 context */ + if (kill(getpid(), SIGHUP) < 0) { + FATAL_SYSERROR(errno, "kill"); + } + } + } +} + +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); + isc_mutex_destroy(&ctx->readylock); + isc_condition_destroy(&ctx->ready); +} + +void +isc_app_finish(void) { + isc_app_ctxfinish(&isc_g_appctx); +} + +void +isc_app_block(void) { + sigset_t sset; + + REQUIRE(atomic_load_acquire(&isc_g_appctx.running)); + + atomic_compare_exchange_enforced(&isc_g_appctx.blocked, + &(bool){ false }, true); + + 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); +} + +void +isc_app_unblock(void) { + sigset_t sset; + + REQUIRE(atomic_load_acquire(&isc_g_appctx.running)); + REQUIRE(blockedthread == pthread_self()); + + atomic_compare_exchange_enforced(&isc_g_appctx.blocked, &(bool){ true }, + false); + + RUNTIME_CHECK(sigemptyset(&sset) == 0 && + sigaddset(&sset, SIGINT) == 0 && + sigaddset(&sset, SIGTERM) == 0); + RUNTIME_CHECK(pthread_sigmask(SIG_BLOCK, &sset, NULL) == 0); +} + +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 = (isc_appctx_t){ .magic = 0 }; + + isc_mem_attach(mctx, &ctx->mctx); + ctx->magic = APPCTX_MAGIC; + + *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..e9a7935 --- /dev/null +++ b/lib/isc/assertions.c @@ -0,0 +1,110 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 nframes = isc_backtrace(tracebuf, BACKTRACE_MAXFRAME); + + fprintf(stderr, "%s:%d: %s(%s) failed%s\n", file, line, + isc_assertion_typetotext(type), cond, + (nframes > 0) ? ", back trace" : "."); + + if (nframes > 0) { + isc_backtrace_symbols_fd(tracebuf, nframes, fileno(stderr)); + } + + 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.c b/lib/isc/backtrace.c new file mode 100644 index 0000000..3ac23c8 --- /dev/null +++ b/lib/isc/backtrace.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. + */ + +/*! \file */ + +#include <stdlib.h> +#include <string.h> +#ifdef HAVE_BACKTRACE_SYMBOLS +#include <execinfo.h> +#endif /* HAVE_BACKTRACE_SYMBOLS */ + +#include <isc/backtrace.h> +#include <isc/print.h> +#include <isc/result.h> +#include <isc/util.h> + +#if HAVE_BACKTRACE_SYMBOLS +int +isc_backtrace(void **addrs, int maxaddrs) { + int n; + + /* + * Validate the arguments: intentionally avoid using REQUIRE(). + * See notes in backtrace.h. + */ + if (addrs == NULL || maxaddrs <= 0) { + return (-1); + } + + /* + * 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 (-1); + } + n--; + memmove(addrs, &addrs[1], sizeof(addrs[0]) * n); + + return (n); +} + +char ** +isc_backtrace_symbols(void *const *buffer, int size) { + return (backtrace_symbols(buffer, size)); +} + +void +isc_backtrace_symbols_fd(void *const *buffer, int size, int fd) { + backtrace_symbols_fd(buffer, size, fd); +} + +#else /* HAVE_BACKTRACE_SYMBOLS */ + +int +isc_backtrace(void **addrs, int maxaddrs) { + UNUSED(addrs); + UNUSED(maxaddrs); + + return (-1); +} + +char ** +isc_backtrace_symbols(void *const *buffer, int size) { + UNUSED(buffer); + UNUSED(size); + + return (NULL); +} + +void +isc_backtrace_symbols_fd(void *const *buffer, int size, int fd) { + UNUSED(buffer); + UNUSED(size); + UNUSED(fd); +} + +#endif /* HAVE_BACKTRACE_SYMBOLS */ 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/buffer.c b/lib/isc/buffer.c new file mode 100644 index 0000000..e7e84df --- /dev/null +++ b/lib/isc/buffer.c @@ -0,0 +1,352 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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_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_setautorealloc(isc_buffer_t *b, bool enable) { + REQUIRE(ISC_BUFFER_VALID(b)); + REQUIRE(b->mctx != NULL); + b->autore = enable; +} + +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); +} + +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); +} + +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); +} + +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_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 (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 (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) { + size_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); + } + + (*dynbuffer)->base = isc_mem_reget((*dynbuffer)->mctx, + (*dynbuffer)->base, + (*dynbuffer)->length, len); + (*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 (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/commandline.c b/lib/isc/commandline.c new file mode 100644 index 0000000..1b4a33a --- /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. */ +int isc_commandline_index = 1; +/*% Character checked for validity. */ +int isc_commandline_option; +/*% Argument associated with option. */ +char *isc_commandline_argument; +/*% For printing error messages. */ +char *isc_commandline_progname; +/*% Print error messages. */ +bool isc_commandline_errprint = true; +/*% Reset processing. */ +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/condition.c b/lib/isc/condition.c new file mode 100644 index 0000000..18fab69 --- /dev/null +++ b/lib/isc/condition.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. + */ + +/*! \file */ + +#include <errno.h> + +#include <isc/condition.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; + + 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 { + presult = pthread_cond_timedwait(c, m, &ts); + if (presult == 0) { + return (ISC_R_SUCCESS); + } + if (presult == ETIMEDOUT) { + return (ISC_R_TIMEDOUT); + } + } while (presult == EINTR); + + UNEXPECTED_SYSERROR(presult, "pthread_cond_timedwait()"); + return (ISC_R_UNEXPECTED); +} 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/dir.c b/lib/isc/dir.c new file mode 100644 index 0000000..b7eabe9 --- /dev/null +++ b/lib/isc/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/entropy.c b/lib/isc/entropy.c new file mode 100644 index 0000000..38fcfa0 --- /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("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/errno.c b/lib/isc/errno.c new file mode 100644 index 0000000..5875f3b --- /dev/null +++ b/lib/isc/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/errno2result.c b/lib/isc/errno2result.c new file mode 100644 index 0000000..1ef0c88 --- /dev/null +++ b/lib/isc/errno2result.c @@ -0,0 +1,133 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/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 EISDIR: + return (ISC_R_NOTFILE); + case ENOENT: + return (ISC_R_FILENOTFOUND); + case EACCES: + case EPERM: + case EROFS: + 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/errno2result.h b/lib/isc/errno2result.h new file mode 100644 index 0000000..80fee69 --- /dev/null +++ b/lib/isc/errno2result.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 */ + +/* 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 diff --git a/lib/isc/error.c b/lib/isc/error.c new file mode 100644 index 0000000..051a6c4 --- /dev/null +++ b/lib/isc/error.c @@ -0,0 +1,91 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 *, const char *, + va_list) ISC_FORMAT_PRINTF(4, 0); + +/*% Default fatal callback. */ +static void +default_fatal_callback(const char *, int, const char *, 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 *func, + const char *format, ...) { + va_list args; + + va_start(args, format); + (unexpected_callback)(file, line, func, format, args); + va_end(args); +} + +void +isc_error_fatal(const char *file, int line, const char *func, + const char *format, ...) { + va_list args; + + va_start(args, format); + (fatal_callback)(file, line, func, format, args); + va_end(args); + abort(); +} + +static void +default_unexpected_callback(const char *file, int line, const char *func, + const char *format, va_list args) { + fprintf(stderr, "%s:%d:%s(): ", file, line, func); + vfprintf(stderr, format, args); + fprintf(stderr, "\n"); + fflush(stderr); +} + +static void +default_fatal_callback(const char *file, int line, const char *func, + const char *format, va_list args) { + fprintf(stderr, "%s:%d:%s(): fatal error: ", file, line, func); + 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/file.c b/lib/isc/file.c new file mode 100644 index 0000000..e3a61de --- /dev/null +++ b/lib/isc/file.c @@ -0,0 +1,792 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/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); +} + +#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/glob.c b/lib/isc/glob.c new file mode 100644 index 0000000..66427b3 --- /dev/null +++ b/lib/isc/glob.c @@ -0,0 +1,53 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 <glob.h> +#include <stdio.h> +#include <string.h> + +#include <isc/errno.h> +#include <isc/glob.h> +#include <isc/print.h> +#include <isc/result.h> +#include <isc/types.h> +#include <isc/util.h> + +isc_result_t +isc_glob(const char *pattern, glob_t *pglob) { + REQUIRE(pattern != NULL); + REQUIRE(*pattern != '\0'); + REQUIRE(pglob != NULL); + + int rc = glob(pattern, GLOB_ERR, NULL, pglob); + + switch (rc) { + case 0: + return (ISC_R_SUCCESS); + + case GLOB_NOMATCH: + return (ISC_R_FILENOTFOUND); + + case GLOB_NOSPACE: + return (ISC_R_NOMEMORY); + + default: + return (errno != 0 ? isc_errno_toresult(errno) : ISC_R_IOERROR); + } +} + +void +isc_globfree(glob_t *pglob) { + REQUIRE(pglob != NULL); + globfree(pglob); +} diff --git a/lib/isc/hash.c b/lib/isc/hash.c new file mode 100644 index 0000000..522e140 --- /dev/null +++ b/lib/isc/hash.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. + */ + +#include <inttypes.h> +#include <stdbool.h> +#include <stddef.h> + +#include "entropy_private.h" +#include "isc/hash.h" /* IWYU pragma: keep */ +#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 (!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 (!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..bc35bef --- /dev/null +++ b/lib/isc/hmac.c @@ -0,0 +1,167 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/evp.h> +#include <openssl/opensslv.h> + +#include <isc/assertions.h> +#include <isc/hmac.h> +#include <isc/md.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) { + EVP_MD_CTX *hmac = EVP_MD_CTX_new(); + RUNTIME_CHECK(hmac != NULL); + return ((isc_hmac_t *)hmac); +} + +void +isc_hmac_free(isc_hmac_t *hmac) { + if (hmac == NULL) { + return; + } + + EVP_MD_CTX_free((EVP_MD_CTX *)hmac); +} + +isc_result_t +isc_hmac_init(isc_hmac_t *hmac, const void *key, const size_t keylen, + const isc_md_type_t *md_type) { + EVP_PKEY *pkey; + + REQUIRE(hmac != NULL); + REQUIRE(key != NULL); + REQUIRE(keylen <= INT_MAX); + + if (md_type == NULL) { + return (ISC_R_NOTIMPLEMENTED); + } + + pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_HMAC, NULL, key, keylen); + if (pkey == NULL) { + ERR_clear_error(); + return (ISC_R_CRYPTOFAILURE); + } + + if (EVP_DigestSignInit(hmac, NULL, md_type, NULL, pkey) != 1) { + EVP_PKEY_free(pkey); + ERR_clear_error(); + return (ISC_R_CRYPTOFAILURE); + } + + EVP_PKEY_free(pkey); + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_hmac_reset(isc_hmac_t *hmac) { + REQUIRE(hmac != NULL); + + if (EVP_MD_CTX_reset(hmac) != 1) { + ERR_clear_error(); + 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 (buf == NULL || len == 0) { + return (ISC_R_SUCCESS); + } + + if (EVP_DigestSignUpdate(hmac, buf, len) != 1) { + ERR_clear_error(); + 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); + REQUIRE(digestlen != NULL); + + size_t len = *digestlen; + + if (EVP_DigestSignFinal(hmac, digest, &len) != 1) { + ERR_clear_error(); + return (ISC_R_CRYPTOFAILURE); + } + + *digestlen = (unsigned int)len; + + return (ISC_R_SUCCESS); +} + +const isc_md_type_t * +isc_hmac_get_md_type(isc_hmac_t *hmac) { + REQUIRE(hmac != NULL); + + return (EVP_MD_CTX_get0_md(hmac)); +} + +size_t +isc_hmac_get_size(isc_hmac_t *hmac) { + REQUIRE(hmac != NULL); + + return ((size_t)EVP_MD_CTX_size(hmac)); +} + +int +isc_hmac_get_block_size(isc_hmac_t *hmac) { + REQUIRE(hmac != NULL); + + return (EVP_MD_CTX_block_size(hmac)); +} + +isc_result_t +isc_hmac(const isc_md_type_t *type, const void *key, const size_t 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..eaf2b3c --- /dev/null +++ b/lib/isc/ht.c @@ -0,0 +1,573 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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) + +#define HT_NO_BITS 0 +#define HT_MIN_BITS 1 +#define HT_MAX_BITS 32 +#define HT_OVERCOMMIT 3 + +#define HT_NEXTTABLE(idx) ((idx == 0) ? 1 : 0) +#define TRY_NEXTTABLE(idx, ht) (idx == ht->hindex && rehashing_in_progress(ht)) + +#define GOLDEN_RATIO_32 0x61C88647 + +#define HASHSIZE(bits) (UINT64_C(1) << (bits)) + +struct isc_ht_node { + void *value; + isc_ht_node_t *next; + uint32_t hashval; + size_t keysize; + unsigned char key[]; +}; + +struct isc_ht { + unsigned int magic; + isc_mem_t *mctx; + size_t count; + bool case_sensitive; + size_t size[2]; + uint8_t hashbits[2]; + isc_ht_node_t **table[2]; + uint8_t hindex; + uint32_t hiter; /* rehashing iterator */ +}; + +struct isc_ht_iter { + isc_ht_t *ht; + size_t i; + uint8_t hindex; + isc_ht_node_t *cur; +}; + +static isc_ht_node_t * +isc__ht_find(const isc_ht_t *ht, const unsigned char *key, + const uint32_t keysize, const uint32_t hashval, const uint8_t idx); +static void +isc__ht_add(isc_ht_t *ht, const unsigned char *key, const uint32_t keysize, + const uint32_t hashval, const uint8_t idx, void *value); +static isc_result_t +isc__ht_delete(isc_ht_t *ht, const unsigned char *key, const uint32_t keysize, + const uint32_t hashval, const uint8_t idx); + +static uint32_t +rehash_bits(isc_ht_t *ht, size_t newcount); + +static void +hashtable_new(isc_ht_t *ht, const uint8_t idx, const uint8_t bits); +static void +hashtable_free(isc_ht_t *ht, const uint8_t idx); +static void +hashtable_rehash(isc_ht_t *ht, uint32_t newbits); +static void +hashtable_rehash_one(isc_ht_t *ht); +static void +maybe_rehash(isc_ht_t *ht, size_t newcount); + +static isc_result_t +isc__ht_iter_next(isc_ht_iter_t *it); + +static bool +isc__ht_node_match(isc_ht_node_t *node, const uint32_t hashval, + const uint8_t *key, uint32_t keysize) { + return (node->hashval == hashval && node->keysize == keysize && + memcmp(node->key, key, keysize) == 0); +} + +static uint32_t +hash_32(uint32_t val, unsigned int bits) { + REQUIRE(bits <= HT_MAX_BITS); + /* High bits are more random. */ + return (val * GOLDEN_RATIO_32 >> (32 - bits)); +} + +static bool +rehashing_in_progress(const isc_ht_t *ht) { + return (ht->table[HT_NEXTTABLE(ht->hindex)] != NULL); +} + +static bool +hashtable_is_overcommited(isc_ht_t *ht) { + return (ht->count >= (ht->size[ht->hindex] * HT_OVERCOMMIT)); +} + +static uint32_t +rehash_bits(isc_ht_t *ht, size_t newcount) { + uint32_t newbits = ht->hashbits[ht->hindex]; + + while (newcount >= HASHSIZE(newbits) && newbits <= HT_MAX_BITS) { + newbits += 1; + } + + return (newbits); +} + +/* + * Rebuild the hashtable to reduce the load factor + */ +static void +hashtable_rehash(isc_ht_t *ht, uint32_t newbits) { + uint8_t oldindex = ht->hindex; + uint32_t oldbits = ht->hashbits[oldindex]; + uint8_t newindex = HT_NEXTTABLE(oldindex); + + REQUIRE(ht->hashbits[oldindex] >= HT_MIN_BITS); + REQUIRE(ht->hashbits[oldindex] <= HT_MAX_BITS); + REQUIRE(ht->table[oldindex] != NULL); + + REQUIRE(newbits <= HT_MAX_BITS); + REQUIRE(ht->hashbits[newindex] == HT_NO_BITS); + REQUIRE(ht->table[newindex] == NULL); + + REQUIRE(newbits > oldbits); + + hashtable_new(ht, newindex, newbits); + + ht->hindex = newindex; + + hashtable_rehash_one(ht); +} + +static void +hashtable_rehash_one(isc_ht_t *ht) { + isc_ht_node_t **newtable = ht->table[ht->hindex]; + uint32_t oldsize = ht->size[HT_NEXTTABLE(ht->hindex)]; + isc_ht_node_t **oldtable = ht->table[HT_NEXTTABLE(ht->hindex)]; + isc_ht_node_t *node = NULL; + isc_ht_node_t *nextnode; + + /* Find first non-empty node */ + while (ht->hiter < oldsize && oldtable[ht->hiter] == NULL) { + ht->hiter++; + } + + /* Rehashing complete */ + if (ht->hiter == oldsize) { + hashtable_free(ht, HT_NEXTTABLE(ht->hindex)); + ht->hiter = 0; + return; + } + + /* Move the first non-empty node from old hashtable to new hashtable */ + for (node = oldtable[ht->hiter]; node != NULL; node = nextnode) { + uint32_t hash = hash_32(node->hashval, + ht->hashbits[ht->hindex]); + nextnode = node->next; + node->next = newtable[hash]; + newtable[hash] = node; + } + + oldtable[ht->hiter] = NULL; + + ht->hiter++; +} + +static void +maybe_rehash(isc_ht_t *ht, size_t newcount) { + uint32_t newbits = rehash_bits(ht, newcount); + + if (ht->hashbits[ht->hindex] < newbits && newbits <= HT_MAX_BITS) { + hashtable_rehash(ht, newbits); + } +} + +static void +hashtable_new(isc_ht_t *ht, const uint8_t idx, const uint8_t bits) { + size_t size; + REQUIRE(ht->hashbits[idx] == HT_NO_BITS); + REQUIRE(ht->table[idx] == NULL); + REQUIRE(bits >= HT_MIN_BITS); + REQUIRE(bits <= HT_MAX_BITS); + + ht->hashbits[idx] = bits; + ht->size[idx] = HASHSIZE(ht->hashbits[idx]); + + size = ht->size[idx] * sizeof(isc_ht_node_t *); + + ht->table[idx] = isc_mem_get(ht->mctx, size); + memset(ht->table[idx], 0, size); +} + +static void +hashtable_free(isc_ht_t *ht, const uint8_t idx) { + size_t size = ht->size[idx] * sizeof(isc_ht_node_t *); + + for (size_t i = 0; i < ht->size[idx]; i++) { + isc_ht_node_t *node = ht->table[idx][i]; + while (node != NULL) { + isc_ht_node_t *next = node->next; + ht->count--; + isc_mem_put(ht->mctx, node, + sizeof(*node) + node->keysize); + node = next; + } + } + + isc_mem_put(ht->mctx, ht->table[idx], size); + ht->hashbits[idx] = HT_NO_BITS; + ht->table[idx] = NULL; +} + +void +isc_ht_init(isc_ht_t **htp, isc_mem_t *mctx, uint8_t bits, + unsigned int options) { + isc_ht_t *ht = NULL; + bool case_sensitive = ((options & ISC_HT_CASE_INSENSITIVE) == 0); + + REQUIRE(htp != NULL && *htp == NULL); + REQUIRE(mctx != NULL); + REQUIRE(bits >= 1 && bits <= HT_MAX_BITS); + + ht = isc_mem_get(mctx, sizeof(*ht)); + *ht = (isc_ht_t){ + .case_sensitive = case_sensitive, + }; + + isc_mem_attach(mctx, &ht->mctx); + + hashtable_new(ht, 0, bits); + + ht->magic = ISC_HT_MAGIC; + + *htp = ht; +} + +void +isc_ht_destroy(isc_ht_t **htp) { + isc_ht_t *ht; + + REQUIRE(htp != NULL); + REQUIRE(ISC_HT_VALID(*htp)); + + ht = *htp; + *htp = NULL; + ht->magic = 0; + + for (size_t i = 0; i <= 1; i++) { + if (ht->table[i] != NULL) { + hashtable_free(ht, i); + } + } + + INSIST(ht->count == 0); + + isc_mem_putanddetach(&ht->mctx, ht, sizeof(*ht)); +} + +static void +isc__ht_add(isc_ht_t *ht, const unsigned char *key, const uint32_t keysize, + const uint32_t hashval, const uint8_t idx, void *value) { + isc_ht_node_t *node; + uint32_t hash; + + hash = hash_32(hashval, ht->hashbits[idx]); + + node = isc_mem_get(ht->mctx, sizeof(*node) + keysize); + *node = (isc_ht_node_t){ + .keysize = keysize, + .hashval = hashval, + .next = ht->table[idx][hash], + .value = value, + }; + + memmove(node->key, key, keysize); + + ht->count++; + ht->table[idx][hash] = node; +} + +isc_result_t +isc_ht_add(isc_ht_t *ht, const unsigned char *key, const uint32_t keysize, + void *value) { + uint32_t hashval; + + REQUIRE(ISC_HT_VALID(ht)); + REQUIRE(key != NULL && keysize > 0); + + if (rehashing_in_progress(ht)) { + /* Rehash in progress */ + hashtable_rehash_one(ht); + } else if (hashtable_is_overcommited(ht)) { + /* Rehash requested */ + maybe_rehash(ht, ht->count); + } + + hashval = isc_hash32(key, keysize, ht->case_sensitive); + + if (isc__ht_find(ht, key, keysize, hashval, ht->hindex) != NULL) { + return (ISC_R_EXISTS); + } + + isc__ht_add(ht, key, keysize, hashval, ht->hindex, value); + + return (ISC_R_SUCCESS); +} + +static isc_ht_node_t * +isc__ht_find(const isc_ht_t *ht, const unsigned char *key, + const uint32_t keysize, const uint32_t hashval, + const uint8_t idx) { + uint32_t hash; + uint8_t findex = idx; + +nexttable: + hash = hash_32(hashval, ht->hashbits[findex]); + for (isc_ht_node_t *node = ht->table[findex][hash]; node != NULL; + node = node->next) + { + if (isc__ht_node_match(node, hashval, key, keysize)) { + return (node); + } + } + if (TRY_NEXTTABLE(findex, ht)) { + /* + * Rehashing in progress, check the other table + */ + findex = HT_NEXTTABLE(findex); + goto nexttable; + } + + return (NULL); +} + +isc_result_t +isc_ht_find(const isc_ht_t *ht, const unsigned char *key, + const uint32_t keysize, void **valuep) { + uint32_t hashval; + isc_ht_node_t *node; + + REQUIRE(ISC_HT_VALID(ht)); + REQUIRE(key != NULL && keysize > 0); + REQUIRE(valuep == NULL || *valuep == NULL); + + hashval = isc_hash32(key, keysize, ht->case_sensitive); + + node = isc__ht_find(ht, key, keysize, hashval, ht->hindex); + if (node == NULL) { + return (ISC_R_NOTFOUND); + } + + if (valuep != NULL) { + *valuep = node->value; + } + return (ISC_R_SUCCESS); +} + +static isc_result_t +isc__ht_delete(isc_ht_t *ht, const unsigned char *key, const uint32_t keysize, + const uint32_t hashval, const uint8_t idx) { + isc_ht_node_t *prev = NULL; + uint32_t hash; + + hash = hash_32(hashval, ht->hashbits[idx]); + + for (isc_ht_node_t *node = ht->table[idx][hash]; node != NULL; + prev = node, node = node->next) + { + if (isc__ht_node_match(node, hashval, key, keysize)) { + if (prev == NULL) { + ht->table[idx][hash] = node->next; + } else { + prev->next = node->next; + } + isc_mem_put(ht->mctx, node, + sizeof(*node) + node->keysize); + ht->count--; + + return (ISC_R_SUCCESS); + } + } + + return (ISC_R_NOTFOUND); +} + +isc_result_t +isc_ht_delete(isc_ht_t *ht, const unsigned char *key, const uint32_t keysize) { + uint32_t hashval; + uint8_t hindex; + isc_result_t result; + + REQUIRE(ISC_HT_VALID(ht)); + REQUIRE(key != NULL && keysize > 0); + + if (rehashing_in_progress(ht)) { + /* Rehash in progress */ + hashtable_rehash_one(ht); + } + + hindex = ht->hindex; + hashval = isc_hash32(key, keysize, ht->case_sensitive); +nexttable: + result = isc__ht_delete(ht, key, keysize, hashval, hindex); + + if (result == ISC_R_NOTFOUND && TRY_NEXTTABLE(hindex, ht)) { + /* + * Rehashing in progress, check the other table + */ + hindex = HT_NEXTTABLE(hindex); + goto nexttable; + } + + return (result); +} + +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 = (isc_ht_iter_t){ + .ht = ht, + .hindex = ht->hindex, + }; + + *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(*it)); +} + +isc_result_t +isc_ht_iter_first(isc_ht_iter_t *it) { + isc_ht_t *ht; + + REQUIRE(it != NULL); + + ht = it->ht; + + it->hindex = ht->hindex; + it->i = 0; + + return (isc__ht_iter_next(it)); +} + +static isc_result_t +isc__ht_iter_next(isc_ht_iter_t *it) { + isc_ht_t *ht = it->ht; + + while (it->i < ht->size[it->hindex] && + ht->table[it->hindex][it->i] == NULL) + { + it->i++; + } + + if (it->i < ht->size[it->hindex]) { + it->cur = ht->table[it->hindex][it->i]; + + return (ISC_R_SUCCESS); + } + + if (TRY_NEXTTABLE(it->hindex, ht)) { + it->hindex = HT_NEXTTABLE(it->hindex); + it->i = 0; + return (isc__ht_iter_next(it)); + } + + return (ISC_R_NOMORE); +} + +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) { + return (ISC_R_SUCCESS); + } + + it->i++; + + return (isc__ht_iter_next(it)); +} + +isc_result_t +isc_ht_iter_delcurrent_next(isc_ht_iter_t *it) { + isc_result_t result = ISC_R_SUCCESS; + isc_ht_node_t *dnode = NULL; + uint8_t dindex; + isc_ht_t *ht; + isc_result_t dresult; + + REQUIRE(it != NULL); + REQUIRE(it->cur != NULL); + + ht = it->ht; + dnode = it->cur; + dindex = it->hindex; + + result = isc_ht_iter_next(it); + + dresult = isc__ht_delete(ht, dnode->key, dnode->keysize, dnode->hashval, + dindex); + INSIST(dresult == ISC_R_SUCCESS); + + 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; +} + +size_t +isc_ht_count(const 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..b15cc45 --- /dev/null +++ b/lib/isc/httpd.c @@ -0,0 +1,1147 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 <inttypes.h> +#include <stdbool.h> +#include <string.h> + +#include <isc/buffer.h> +#include <isc/httpd.h> +#include <isc/mem.h> +#include <isc/netmgr.h> +#include <isc/print.h> +#include <isc/refcount.h> +#include <isc/sockaddr.h> +#include <isc/string.h> +#include <isc/time.h> +#include <isc/url.h> +#include <isc/util.h> + +#include "netmgr/netmgr-int.h" +#include "picohttpparser.h" + +#ifdef HAVE_ZLIB +#include <zlib.h> +#endif /* ifdef HAVE_ZLIB */ + +#define CHECK(m) \ + do { \ + result = (m); \ + if (result != ISC_R_SUCCESS) { \ + goto cleanup; \ + } \ + } while (0) + +/* + * Size the recv buffer to hold at maximum two full buffers from isc_nm_read(), + * so we don't have to handle the truncation. + */ +#define HTTP_RECVLEN ISC_NETMGR_TCP_RECVBUF_SIZE * 2 +#define HTTP_SENDLEN ISC_NETMGR_TCP_RECVBUF_SIZE +#define HTTP_HEADERS_NUM 100 +#define HTTP_MAX_REQUEST_LEN 4096 + +typedef enum httpd_flags { + CONNECTION_CLOSE = 1 << 0, /* connection must close */ + CONNECTION_KEEP_ALIVE = 1 << 1, /* response needs a keep-alive header */ + ACCEPT_DEFLATE = 1 << 2, /* response can be compressed */ +} httpd_flags_t; + +#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 methods. + */ +typedef enum { METHOD_UNKNOWN = 0, METHOD_GET = 1, METHOD_POST = 2 } method_t; + +/*% + * 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; +}; + +/*% http client */ +struct isc_httpd { + unsigned int magic; /* HTTPD_MAGIC */ + + isc_httpdmgr_t *mgr; /*%< our parent */ + ISC_LINK(isc_httpd_t) link; + + isc_nmhandle_t *handle; /* Permanent pointer to handle */ + isc_nmhandle_t *readhandle; /* Waiting for a read callback */ + + /*% + * Received data state. + */ + char recvbuf[HTTP_RECVLEN]; /*%< receive buffer */ + size_t recvlen; /*%< length recv'd */ + size_t consume; /*%< length of last command */ + + method_t method; + int minor_version; + httpd_flags_t flags; + const char *path; + isc_url_parser_t up; + isc_time_t if_modified_since; +}; + +struct isc_httpdmgr { + unsigned int magic; /* HTTPDMGR_MAGIC */ + isc_refcount_t references; + isc_mem_t *mctx; + isc_nmsocket_t *sock; + + 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; +}; + +typedef struct isc_httpd_sendreq { + isc_mem_t *mctx; + isc_httpd_t *httpd; + isc_nmhandle_t *handle; + + /*% + * 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. + * + * We currently use three buffers total: + * + * sendbuffer - gets filled as we gather the data + * + * bodybuffer - 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. we only allocate the buffer + * itself, not the backing store. + * + * compbuffer - managed by us, that contains the compressed HTTP data, + * if compression is used. + */ + isc_buffer_t *sendbuffer; + isc_buffer_t *compbuffer; + + isc_buffer_t bodybuffer; + + const char *mimetype; + unsigned int retcode; + const char *retmsg; + isc_httpdfree_t *freecb; + void *freecb_arg; + +} isc_httpd_sendreq_t; + +static isc_result_t +httpd_newconn(isc_nmhandle_t *, isc_result_t, void *); +static void +httpd_request(isc_nmhandle_t *, isc_result_t, isc_region_t *, void *); +static void +httpd_senddone(isc_nmhandle_t *, isc_result_t, void *); +static void +httpd_reset(void *); +static void +httpd_put(void *); + +static void +httpd_addheader(isc_httpd_sendreq_t *, const char *, const char *); +static void +httpd_addheaderuint(isc_httpd_sendreq_t *, const char *, int); +static void +httpd_endheaders(isc_httpd_sendreq_t *); +static void +httpd_response(isc_httpd_t *, isc_httpd_sendreq_t *); + +static isc_result_t +process_request(isc_httpd_t *, size_t); + +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 +destroy_httpdmgr(isc_httpdmgr_t *); + +static void +httpdmgr_attach(isc_httpdmgr_t *, isc_httpdmgr_t **); +static void +httpdmgr_detach(isc_httpdmgr_t **); + +isc_result_t +isc_httpdmgr_create(isc_nm_t *nm, isc_mem_t *mctx, isc_sockaddr_t *addr, + isc_httpdclientok_t *client_ok, + isc_httpdondestroy_t *ondestroy, void *cb_arg, + isc_httpdmgr_t **httpdmgrp) { + isc_result_t result; + isc_httpdmgr_t *httpdmgr = NULL; + + REQUIRE(nm != NULL); + REQUIRE(mctx != NULL); + REQUIRE(httpdmgrp != NULL && *httpdmgrp == NULL); + + httpdmgr = isc_mem_get(mctx, sizeof(isc_httpdmgr_t)); + *httpdmgr = (isc_httpdmgr_t){ .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_LIST_INIT(httpdmgr->running); + ISC_LIST_INIT(httpdmgr->urls); + + isc_refcount_init(&httpdmgr->references, 1); + + CHECK(isc_nm_listentcp(nm, addr, httpd_newconn, httpdmgr, + sizeof(isc_httpd_t), 5, NULL, &httpdmgr->sock)); + + httpdmgr->magic = HTTPDMGR_MAGIC; + *httpdmgrp = httpdmgr; + + return (ISC_R_SUCCESS); + +cleanup: + httpdmgr->magic = 0; + isc_refcount_decrementz(&httpdmgr->references); + isc_refcount_destroy(&httpdmgr->references); + isc_mem_detach(&httpdmgr->mctx); + isc_mutex_destroy(&httpdmgr->lock); + isc_mem_put(mctx, httpdmgr, sizeof(isc_httpdmgr_t)); + + return (result); +} + +static void +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 +httpdmgr_detach(isc_httpdmgr_t **httpdmgrp) { + isc_httpdmgr_t *httpdmgr = NULL; + + REQUIRE(httpdmgrp != NULL); + REQUIRE(VALID_HTTPDMGR(*httpdmgrp)); + + httpdmgr = *httpdmgrp; + *httpdmgrp = NULL; + + 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); + + REQUIRE((httpdmgr->flags & ISC_HTTPDMGR_SHUTTINGDOWN) != 0); + REQUIRE(ISC_LIST_EMPTY(httpdmgr->running)); + + httpdmgr->magic = 0; + + if (httpdmgr->sock != NULL) { + isc_nmsocket_close(&httpdmgr->sock); + } + + /* + * 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)); +} + +static bool +name_match(const struct phr_header *header, const char *match) { + size_t match_len = strlen(match); + if (match_len != header->name_len) { + return (false); + } + return (strncasecmp(header->name, match, match_len) == 0); +} + +static bool +value_match(const struct phr_header *header, const char *match) { + size_t match_len = strlen(match); + size_t limit; + + if (match_len > header->value_len) { + return (false); + } + + limit = header->value_len - match_len + 1; + + for (size_t i = 0; i < limit; i++) { + if (isspace(header->value[i])) { + while (i < limit && isspace(header->value[i])) { + i++; + } + continue; + } + + if (strncasecmp(&header->value[i], match, match_len) == 0) { + i += match_len; + /* + * Sanity check; f.e. for 'deflate' match only + * 'deflate[,;]', but not 'deflateyou' + */ + if (i == header->value_len || header->value[i] == ',' || + header->value[i] == ';') + { + return (true); + } + } + + while (i < limit && header->value[i] != ',') { + i++; + } + } + return (false); +} + +static isc_result_t +process_request(isc_httpd_t *httpd, size_t last_len) { + int pret; + const char *method = NULL; + size_t method_len = 0; + const char *path; + size_t path_len = 0; + struct phr_header headers[HTTP_HEADERS_NUM]; + size_t num_headers; + isc_result_t result; + + num_headers = ARRAY_SIZE(headers); + + pret = phr_parse_request(httpd->recvbuf, httpd->recvlen, &method, + &method_len, &path, &path_len, + &httpd->minor_version, headers, &num_headers, + last_len); + + if (pret == -1) { + /* Parse Error */ + return (ISC_R_UNEXPECTED); + } else if (pret == -2) { + /* Need more data */ + return (ISC_R_NOMORE); + } + + INSIST(pret > 0); + + if (pret > HTTP_MAX_REQUEST_LEN) { + return (ISC_R_RANGE); + } + + httpd->consume = pret; + + /* + * Determine if this is a POST or GET method. Any other values will + * cause an error to be returned. + */ + if (strncmp(method, "GET ", method_len) == 0) { + httpd->method = METHOD_GET; + } else if (strncmp(method, "POST ", method_len) == 0) { + httpd->method = METHOD_POST; + } else { + return (ISC_R_RANGE); + } + + /* + * Parse the URL + */ + result = isc_url_parse(path, path_len, 0, &httpd->up); + if (result != ISC_R_SUCCESS) { + return (result); + } + httpd->path = path; + + /* + * Examine headers that can affect this request's response + */ + httpd->flags = 0; + + size_t content_len = 0; + bool keep_alive = false; + bool host_header = false; + + isc_time_set(&httpd->if_modified_since, 0, 0); + + for (size_t i = 0; i < num_headers; i++) { + struct phr_header *header = &headers[i]; + + if (name_match(header, "Content-Length")) { + char *endptr; + long val = strtol(header->value, &endptr, 10); + + errno = 0; + + /* ensure we consumed all digits */ + if ((header->value + header->value_len) != endptr) { + return (ISC_R_BADNUMBER); + } + /* ensure there was no minus sign */ + if (val < 0) { + return (ISC_R_BADNUMBER); + } + /* ensure it did not overflow */ + if (errno != 0) { + return (ISC_R_RANGE); + } + content_len = val; + } else if (name_match(header, "Connection")) { + /* multiple fields in a connection header are allowed */ + if (value_match(header, "close")) { + httpd->flags |= CONNECTION_CLOSE; + } + if (value_match(header, "keep-alive")) { + keep_alive = true; + } + } else if (name_match(header, "Host")) { + host_header = true; + } else if (name_match(header, "Accept-Encoding")) { + if (value_match(header, "deflate")) { + httpd->flags |= ACCEPT_DEFLATE; + } + } else if (name_match(header, "If-Modified-Since") && + header->value_len < ISC_FORMATHTTPTIMESTAMP_SIZE) + { + char timestamp[ISC_FORMATHTTPTIMESTAMP_SIZE + 1]; + memmove(timestamp, header->value, header->value_len); + timestamp[header->value_len] = 0; + + /* Ignore the value if it can't be parsed */ + (void)isc_time_parsehttptimestamp( + timestamp, &httpd->if_modified_since); + } + } + + /* + * The Content-Length is optional in an HTTP request. + * For a GET the length must be zero. + */ + if (httpd->method == METHOD_GET && content_len != 0) { + return (ISC_R_BADNUMBER); + } + + if (content_len >= HTTP_MAX_REQUEST_LEN) { + return (ISC_R_RANGE); + } + + size_t consume = httpd->consume + content_len; + if (consume > httpd->recvlen) { + /* The request data isn't complete yet. */ + return (ISC_R_NOMORE); + } + + /* Consume the request's data, which we do not use. */ + httpd->consume = consume; + + switch (httpd->minor_version) { + case 0: + /* + * RFC 9112 section 9.3 says close takes priority if + * keep-alive is also present + */ + if ((httpd->flags & CONNECTION_CLOSE) == 0 && keep_alive) { + httpd->flags |= CONNECTION_KEEP_ALIVE; + } else { + httpd->flags |= CONNECTION_CLOSE; + } + break; + case 1: + if (!host_header) { + return (ISC_R_RANGE); + } + break; + default: + return (ISC_R_UNEXPECTED); + } + + /* + * Looks like a a valid request, so now we know we won't have + * to process this buffer again. We can NULL-terminate the + * URL for the caller's benefit, and set recvlen to 0 so + * the next read will overwrite this one instead of appending + * to the buffer. + */ + + return (ISC_R_SUCCESS); +} + +static void +httpd_reset(void *arg) { + isc_httpd_t *httpd = (isc_httpd_t *)arg; + isc_httpdmgr_t *httpdmgr = NULL; + + REQUIRE(VALID_HTTPD(httpd)); + + httpdmgr = httpd->mgr; + + REQUIRE(VALID_HTTPDMGR(httpdmgr)); + + LOCK(&httpdmgr->lock); + ISC_LIST_UNLINK(httpdmgr->running, httpd, link); + UNLOCK(&httpdmgr->lock); + + httpd->recvbuf[0] = 0; + httpd->recvlen = 0; + httpd->consume = 0; + httpd->method = METHOD_UNKNOWN; + httpd->flags = 0; + + httpd->minor_version = -1; + httpd->path = NULL; + httpd->up = (isc_url_parser_t){ 0 }; + isc_time_set(&httpd->if_modified_since, 0, 0); +} + +static void +isc__httpd_sendreq_free(isc_httpd_sendreq_t *req) { + /* Clean up buffers */ + + isc_buffer_free(&req->sendbuffer); + + isc_mem_putanddetach(&req->mctx, req, sizeof(*req)); +} + +static isc_httpd_sendreq_t * +isc__httpd_sendreq_new(isc_httpd_t *httpd) { + isc_httpdmgr_t *httpdmgr = httpd->mgr; + isc_httpd_sendreq_t *req; + + REQUIRE(VALID_HTTPDMGR(httpdmgr)); + + req = isc_mem_get(httpdmgr->mctx, sizeof(*req)); + *req = (isc_httpd_sendreq_t){ 0 }; + + isc_mem_attach(httpdmgr->mctx, &req->mctx); + + /* + * Initialize the buffer for our headers. + */ + isc_buffer_allocate(req->mctx, &req->sendbuffer, HTTP_SENDLEN); + isc_buffer_clear(req->sendbuffer); + isc_buffer_setautorealloc(req->sendbuffer, true); + + isc_buffer_initnull(&req->bodybuffer); + + return (req); +} + +static void +httpd_put(void *arg) { + isc_httpd_t *httpd = (isc_httpd_t *)arg; + isc_httpdmgr_t *mgr = NULL; + + REQUIRE(VALID_HTTPD(httpd)); + + mgr = httpd->mgr; + REQUIRE(VALID_HTTPDMGR(mgr)); + + httpd->magic = 0; + httpd->mgr = NULL; + + isc_mem_put(mgr->mctx, httpd, sizeof(*httpd)); + + httpdmgr_detach(&mgr); + +#if ENABLE_AFL + if (finishhook != NULL) { + finishhook(); + } +#endif /* ENABLE_AFL */ +} + +static void +new_httpd(isc_httpdmgr_t *httpdmgr, isc_nmhandle_t *handle) { + isc_httpd_t *httpd = NULL; + + REQUIRE(VALID_HTTPDMGR(httpdmgr)); + + httpd = isc_nmhandle_getdata(handle); + if (httpd == NULL) { + httpd = isc_mem_get(httpdmgr->mctx, sizeof(*httpd)); + *httpd = (isc_httpd_t){ .handle = NULL }; + httpdmgr_attach(httpdmgr, &httpd->mgr); + } + + if (httpd->handle == NULL) { + isc_nmhandle_setdata(handle, httpd, httpd_reset, httpd_put); + httpd->handle = handle; + } else { + INSIST(httpd->handle == handle); + } + + ISC_LINK_INIT(httpd, link); + + httpd->magic = HTTPD_MAGIC; + + LOCK(&httpdmgr->lock); + ISC_LIST_APPEND(httpdmgr->running, httpd, link); + UNLOCK(&httpdmgr->lock); + + isc_nmhandle_attach(httpd->handle, &httpd->readhandle); + isc_nm_read(handle, httpd_request, httpdmgr); +} + +static isc_result_t +httpd_newconn(isc_nmhandle_t *handle, isc_result_t result, void *arg) { + isc_httpdmgr_t *httpdmgr = (isc_httpdmgr_t *)arg; + isc_sockaddr_t peeraddr; + + REQUIRE(VALID_HTTPDMGR(httpdmgr)); + + if ((httpdmgr->flags & ISC_HTTPDMGR_SHUTTINGDOWN) != 0) { + return (ISC_R_CANCELED); + } else if (result == ISC_R_CANCELED) { + isc_httpdmgr_shutdown(&httpdmgr); + return (result); + } else if (result != ISC_R_SUCCESS) { + return (result); + } + + peeraddr = isc_nmhandle_peeraddr(handle); + if (httpdmgr->client_ok != NULL && + !(httpdmgr->client_ok)(&peeraddr, httpdmgr->cb_arg)) + { + return (ISC_R_FAILURE); + } + + new_httpd(httpdmgr, handle); + + return (ISC_R_SUCCESS); +} + +static isc_result_t +render_404(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, 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(httpd); + UNUSED(urlinfo); + 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 isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, 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(httpd); + UNUSED(urlinfo); + 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 +/*%< + * 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 +httpd_compress(isc_httpd_sendreq_t *req) { + z_stream zstr; + int ret, inputlen; + + /* + * We're setting output buffer size to input size so it fails if the + * compressed data size would be bigger than the input size. + */ + inputlen = isc_buffer_usedlength(&req->bodybuffer); + if (inputlen == 0) { + return (ISC_R_FAILURE); + } + + isc_buffer_allocate(req->mctx, &req->compbuffer, inputlen); + isc_buffer_clear(req->compbuffer); + + zstr = (z_stream){ + .total_in = inputlen, + .avail_out = inputlen, + .avail_in = inputlen, + .next_in = isc_buffer_base(&req->bodybuffer), + .next_out = isc_buffer_base(req->compbuffer), + }; + + 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(req->compbuffer, zstr.total_out); + return (ISC_R_SUCCESS); + } else { + isc_buffer_free(&req->compbuffer); + return (ISC_R_FAILURE); + } +} +#endif /* ifdef HAVE_ZLIB */ + +static void +prepare_response(isc_httpdmgr_t *mgr, isc_httpd_t *httpd, + isc_httpd_sendreq_t **reqp) { + isc_httpd_sendreq_t *req = NULL; + isc_time_t now; + char datebuf[ISC_FORMATHTTPTIMESTAMP_SIZE]; + const char *path = "/"; + size_t path_len = 1; + bool is_compressed = false; + isc_httpdurl_t *url = NULL; + isc_result_t result; + + REQUIRE(VALID_HTTPD(httpd)); + REQUIRE(reqp != NULL && *reqp == NULL); + + isc_time_now(&now); + isc_time_formathttptimestamp(&now, datebuf, sizeof(datebuf)); + + if (httpd->up.field_set & (1 << ISC_UF_PATH)) { + path = &httpd->path[httpd->up.field_data[ISC_UF_PATH].off]; + path_len = httpd->up.field_data[ISC_UF_PATH].len; + } + + LOCK(&mgr->lock); + url = ISC_LIST_HEAD(mgr->urls); + while (url != NULL) { + if (strncmp(path, url->url, path_len) == 0) { + break; + } + url = ISC_LIST_NEXT(url, link); + } + UNLOCK(&mgr->lock); + + req = isc__httpd_sendreq_new(httpd); + + if (url == NULL) { + result = mgr->render_404(httpd, NULL, NULL, &req->retcode, + &req->retmsg, &req->mimetype, + &req->bodybuffer, &req->freecb, + &req->freecb_arg); + } else { + result = url->action(httpd, url, url->action_arg, &req->retcode, + &req->retmsg, &req->mimetype, + &req->bodybuffer, &req->freecb, + &req->freecb_arg); + } + if (result != ISC_R_SUCCESS) { + result = mgr->render_500(httpd, url, NULL, &req->retcode, + &req->retmsg, &req->mimetype, + &req->bodybuffer, &req->freecb, + &req->freecb_arg); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } + +#ifdef HAVE_ZLIB + if ((httpd->flags & ACCEPT_DEFLATE) != 0) { + result = httpd_compress(req); + if (result == ISC_R_SUCCESS) { + is_compressed = true; + } + } +#endif /* ifdef HAVE_ZLIB */ + + httpd_response(httpd, req); + /* RFC 9112 § 9.6: SHOULD send Connection: close in last response */ + if ((httpd->flags & CONNECTION_CLOSE) != 0) { + httpd_addheader(req, "Connection", "close"); + } else if ((httpd->flags & CONNECTION_KEEP_ALIVE) != 0) { + httpd_addheader(req, "Connection", "Keep-Alive"); + } + httpd_addheader(req, "Content-Type", req->mimetype); + httpd_addheader(req, "Date", datebuf); + httpd_addheader(req, "Expires", datebuf); + + if (url != NULL && url->isstatic) { + char loadbuf[ISC_FORMATHTTPTIMESTAMP_SIZE]; + isc_time_formathttptimestamp(&url->loadtime, loadbuf, + sizeof(loadbuf)); + httpd_addheader(req, "Last-Modified", loadbuf); + httpd_addheader(req, "Cache-Control: public", NULL); + } else { + httpd_addheader(req, "Last-Modified", datebuf); + httpd_addheader(req, "Pragma: no-cache", NULL); + httpd_addheader(req, "Cache-Control: no-cache", NULL); + } + + httpd_addheader(req, "Server: libisc", NULL); + + if (is_compressed) { + httpd_addheader(req, "Content-Encoding", "deflate"); + httpd_addheaderuint(req, "Content-Length", + isc_buffer_usedlength(req->compbuffer)); + } else { + httpd_addheaderuint(req, "Content-Length", + isc_buffer_usedlength(&req->bodybuffer)); + } + + httpd_endheaders(req); /* done */ + + /* + * Append either the compressed or the non-compressed response body to + * the response headers and store the result in httpd->sendbuffer. + */ + if (is_compressed) { + isc_buffer_putmem(req->sendbuffer, + isc_buffer_base(req->compbuffer), + isc_buffer_usedlength(req->compbuffer)); + isc_buffer_free(&req->compbuffer); + } else { + isc_buffer_putmem(req->sendbuffer, + isc_buffer_base(&req->bodybuffer), + isc_buffer_usedlength(&req->bodybuffer)); + } + + /* Free the bodybuffer */ + if (req->freecb != NULL && isc_buffer_length(&req->bodybuffer) > 0) { + req->freecb(&req->bodybuffer, req->freecb_arg); + } + + /* Consume the request from the recv buffer. */ + INSIST(httpd->consume != 0); + INSIST(httpd->consume <= httpd->recvlen); + if (httpd->consume < httpd->recvlen) { + memmove(httpd->recvbuf, httpd->recvbuf + httpd->consume, + httpd->recvlen - httpd->consume); + } + httpd->recvlen -= httpd->consume; + httpd->consume = 0; + + /* + * We don't need to attach to httpd here because it gets only cleaned + * when the last handle has been detached + */ + req->httpd = httpd; + + *reqp = req; +} + +static void +httpd_request(isc_nmhandle_t *handle, isc_result_t eresult, + isc_region_t *region, void *arg) { + isc_result_t result; + isc_httpdmgr_t *mgr = arg; + isc_httpd_t *httpd = NULL; + isc_httpd_sendreq_t *req = NULL; + isc_region_t r; + size_t last_len = 0; + + httpd = isc_nmhandle_getdata(handle); + + REQUIRE(VALID_HTTPD(httpd)); + + REQUIRE(httpd->handle == handle); + + if (httpd->readhandle == NULL) { + /* The channel has been already closed, just bail out */ + return; + } + + if (eresult != ISC_R_SUCCESS) { + goto close_readhandle; + } + + REQUIRE(httpd->readhandle == handle); + + isc_nm_pauseread(httpd->readhandle); + + /* + * If we are being called from httpd_senddone(), the last HTTP request + * was processed successfully, reset the last_len to 0, even if there's + * data in the httpd->recvbuf. + */ + last_len = (region == NULL) ? 0 : httpd->recvlen; + + /* Store the received data into the recvbuf */ + if (region != NULL) { + if (httpd->recvlen + region->length > sizeof(httpd->recvbuf)) { + goto close_readhandle; + } + + memmove(httpd->recvbuf + httpd->recvlen, region->base, + region->length); + httpd->recvlen += region->length; + } + + result = process_request(httpd, last_len); + + if (result == ISC_R_NOMORE) { + if (httpd->recvlen > HTTP_MAX_REQUEST_LEN) { + goto close_readhandle; + } + + /* Wait for more data, the readhandle is still attached */ + isc_nm_resumeread(httpd->readhandle); + return; + } + + /* XXXFANF it would be more polite to reply 400 bad request */ + if (result != ISC_R_SUCCESS) { + goto close_readhandle; + } + + prepare_response(mgr, httpd, &req); + + /* + * Determine total response size. + */ + isc_buffer_usedregion(req->sendbuffer, &r); + + isc_nmhandle_attach(httpd->handle, &req->handle); + isc_nm_send(httpd->handle, &r, httpd_senddone, req); + return; + +close_readhandle: + isc_nm_pauseread(httpd->readhandle); + isc_nmhandle_detach(&httpd->readhandle); +} + +void +isc_httpdmgr_shutdown(isc_httpdmgr_t **httpdmgrp) { + isc_httpdmgr_t *httpdmgr; + isc_httpd_t *httpd; + + REQUIRE(httpdmgrp != NULL); + REQUIRE(VALID_HTTPDMGR(*httpdmgrp)); + + httpdmgr = *httpdmgrp; + *httpdmgrp = NULL; + + isc_nm_stoplistening(httpdmgr->sock); + + LOCK(&httpdmgr->lock); + httpdmgr->flags |= ISC_HTTPDMGR_SHUTTINGDOWN; + + httpd = ISC_LIST_HEAD(httpdmgr->running); + while (httpd != NULL) { + isc_nm_cancelread(httpd->readhandle); + httpd = ISC_LIST_NEXT(httpd, link); + } + UNLOCK(&httpdmgr->lock); + + isc_nmsocket_close(&httpdmgr->sock); + + httpdmgr_detach(&httpdmgr); +} + +static void +httpd_response(isc_httpd_t *httpd, isc_httpd_sendreq_t *req) { + isc_result_t result; + + result = isc_buffer_printf(req->sendbuffer, "HTTP/1.%u %03u %s\r\n", + httpd->minor_version, req->retcode, + req->retmsg); + + RUNTIME_CHECK(result == ISC_R_SUCCESS); +} + +static void +httpd_addheader(isc_httpd_sendreq_t *req, const char *name, const char *val) { + isc_result_t result; + + if (val != NULL) { + result = isc_buffer_printf(req->sendbuffer, "%s: %s\r\n", name, + val); + } else { + result = isc_buffer_printf(req->sendbuffer, "%s\r\n", name); + } + + RUNTIME_CHECK(result == ISC_R_SUCCESS); +} + +static void +httpd_endheaders(isc_httpd_sendreq_t *req) { + isc_result_t result; + + result = isc_buffer_printf(req->sendbuffer, "\r\n"); + + RUNTIME_CHECK(result == ISC_R_SUCCESS); +} + +static void +httpd_addheaderuint(isc_httpd_sendreq_t *req, const char *name, int val) { + isc_result_t result; + + result = isc_buffer_printf(req->sendbuffer, "%s: %d\r\n", name, val); + + RUNTIME_CHECK(result == ISC_R_SUCCESS); +} + +static void +httpd_senddone(isc_nmhandle_t *handle, isc_result_t eresult, void *arg) { + isc_httpd_sendreq_t *req = (isc_httpd_sendreq_t *)arg; + isc_httpd_t *httpd = req->httpd; + + REQUIRE(VALID_HTTPD(httpd)); + + if (httpd->readhandle == NULL) { + goto detach; + } + + if (eresult == ISC_R_SUCCESS && (httpd->flags & CONNECTION_CLOSE) == 0) + { + /* + * Calling httpd_request() with region NULL restarts + * reading. + */ + httpd_request(handle, ISC_R_SUCCESS, NULL, httpd->mgr); + } else { + isc_nm_cancelread(httpd->readhandle); + } + +detach: + isc_nmhandle_detach(&handle); + + isc__httpd_sendreq_free(req); +} + +isc_result_t +isc_httpdmgr_addurl(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 */ +} + +bool +isc_httpdurl_isstatic(const isc_httpdurl_t *url) { + return (url->isstatic); +} + +const isc_time_t * +isc_httpdurl_loadtime(const isc_httpdurl_t *url) { + return (&url->loadtime); +} + +const isc_time_t * +isc_httpd_if_modified_since(const isc_httpd_t *httpd) { + return ((const isc_time_t *)&httpd->if_modified_since); +} diff --git a/lib/isc/include/isc/aes.h b/lib/isc/include/isc/aes.h new file mode 100644 index 0000000..9657494 --- /dev/null +++ b/lib/isc/include/isc/aes.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. + */ + +/*! \file isc/aes.h */ + +#pragma once + +#include <isc/lang.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 diff --git a/lib/isc/include/isc/align.h b/lib/isc/include/isc/align.h new file mode 100644 index 0000000..7b72e9d --- /dev/null +++ b/lib/isc/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/include/isc/app.h b/lib/isc/include/isc/app.h new file mode 100644 index 0000000..1a42bd0 --- /dev/null +++ b/lib/isc/include/isc/app.h @@ -0,0 +1,281 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/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> +#include <isc/types.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 diff --git a/lib/isc/include/isc/assertions.h b/lib/isc/include/isc/assertions.h new file mode 100644 index 0000000..7eafa16 --- /dev/null +++ b/lib/isc/include/isc/assertions.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. + */ + +/*! \file isc/assertions.h + */ + +#pragma once + +#include <isc/attributes.h> +#include <isc/lang.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] */ +noreturn void +isc_assertion_failed(const char *, int, isc_assertiontype_t, const char *); + +void isc_assertion_setcallback(isc_assertioncallback_t); + +const char * +isc_assertion_typetotext(isc_assertiontype_t type); + +#define ISC_REQUIRE(cond) \ + ((void)((cond) || \ + ((isc_assertion_failed)(__FILE__, __LINE__, \ + isc_assertiontype_require, #cond), \ + 0))) + +#define ISC_ENSURE(cond) \ + ((void)((cond) || \ + ((isc_assertion_failed)(__FILE__, __LINE__, \ + isc_assertiontype_ensure, #cond), \ + 0))) + +#define ISC_INSIST(cond) \ + ((void)((cond) || \ + ((isc_assertion_failed)(__FILE__, __LINE__, \ + isc_assertiontype_insist, #cond), \ + 0))) + +#define ISC_INVARIANT(cond) \ + ((void)((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 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..5a415bc --- /dev/null +++ b/lib/isc/include/isc/atomic.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. + */ + +#pragma once + +#if HAVE_STDATOMIC_H +#include <stdatomic.h> +#else +#include <isc/stdatomic.h> +#endif + +#include <isc/util.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) + +/* compare/exchange that MUST succeed */ +#define atomic_compare_exchange_enforced(o, e, d) \ + RUNTIME_CHECK(atomic_compare_exchange_strong((o), (e), (d))) diff --git a/lib/isc/include/isc/attributes.h b/lib/isc/include/isc/attributes.h new file mode 100644 index 0000000..abe6152 --- /dev/null +++ b/lib/isc/include/isc/attributes.h @@ -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. + */ + +#pragma once + +#ifdef HAVE_STDNORETURN_H +#include <stdnoreturn.h> +#elif HAVE_FUNC_ATTRIBUTE_NORETURN +#define noreturn __attribute__((noreturn)) +#else +#define noreturn +#endif + +#if HAVE_FUNC_ATTRIBUTE_RETURNS_NONNULL +#define ISC_ATTR_RETURNS_NONNULL __attribute__((returns_nonnull)) +#else +#define ISC_ATTR_RETURNS_NONNULL +#endif + +#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 */ diff --git a/lib/isc/include/isc/backtrace.h b/lib/isc/include/isc/backtrace.h new file mode 100644 index 0000000..f818c3b --- /dev/null +++ b/lib/isc/include/isc/backtrace.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. + */ + +/*! \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). + */ + +#pragma once + +/*** + *** Imports + ***/ +#include <isc/types.h> + +/*** + *** Functions + ***/ + +ISC_LANG_BEGINDECLS +int +isc_backtrace(void **addrs, int maxaddrs); +/*%< + * 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 + */ + +char ** +isc_backtrace_symbols(void *const *buffer, int size); +/* + * isc_backtrace_symbols() attempts to transform a call stack obtained by + * backtrace() into an array of human-readable strings using dladdr(). The + * array of strings returned has size elements. It is allocated using + * malloc() and should be released using free(). There is no need to free + * the individual strings in the array. + * + * Notes: + * + *\li On Windows, this is shim implementation using SymFromAddr() + *\li On systems with backtrace_symbols(), it's just a thin wrapper + *\li Otherwise, it returns NULL + *\li See platform NOTES for backtrace_symbols + * + * Returns: + * + *\li On success, backtrace_symbols() returns a pointer to the array + *\li On error, NULL is returned. + */ + +void +isc_backtrace_symbols_fd(void *const *buffer, int size, int fd); +/* + * isc_backtrace_symbols_fd() performs the same operation as + * isc_backtrace_symbols(), but the resulting strings are immediately written to + * the file descriptor fd, and are not returned. isc_backtrace_symbols_fd() + * does not call malloc(3), and so can be employed in situations where the + * latter function might fail. + * + * Notes: + * + *\li See isc_backtrace_symbols() notes + *\li See platform NOTES for backtrace_symbols_fd for caveats + */ + +ISC_LANG_ENDDECLS diff --git a/lib/isc/include/isc/barrier.h b/lib/isc/include/isc/barrier.h new file mode 100644 index 0000000..a60472f --- /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 HAVE_PTHREAD_BARRIER_INIT + +#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..1faa629 --- /dev/null +++ b/lib/isc/include/isc/base32.h @@ -0,0 +1,143 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 */ + +/* + * 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 diff --git a/lib/isc/include/isc/base64.h b/lib/isc/include/isc/base64.h new file mode 100644 index 0000000..b1bc089 --- /dev/null +++ b/lib/isc/include/isc/base64.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. + */ + +#pragma once + +/*! \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 diff --git a/lib/isc/include/isc/buffer.h b/lib/isc/include/isc/buffer.h new file mode 100644 index 0000000..1be0081 --- /dev/null +++ b/lib/isc/include/isc/buffer.h @@ -0,0 +1,1023 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/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/list.h> +#include <isc/magic.h> +#include <isc/region.h> +#include <isc/string.h> +#include <isc/types.h> + +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_initnull(isc_buffer_t *b); + +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_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_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. + */ + +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. + */ + +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. + */ + +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_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. + */ + +/* + * Buffer functions implemented as inline. + */ + +/*! \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 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. + */ + +/*! + * \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. + */ +static inline void +isc_buffer_init(isc_buffer_t *b, void *base, unsigned int length) { + ISC_REQUIRE(b != NULL); + + *b = (isc_buffer_t){ .base = base, + .length = length, + .magic = ISC_BUFFER_MAGIC }; + ISC_LINK_INIT(b, link); +} + +/*! + *\brief Initialize a buffer 'b' with a null data field and zero length. + * This can later be grown as needed and swapped in place. + */ +static inline void +isc_buffer_initnull(isc_buffer_t *b) { + *b = (isc_buffer_t){ .magic = ISC_BUFFER_MAGIC }; + ISC_LINK_INIT(b, link); +} + +/*! + * \brief Make 'b' refer to the 'length'-byte constant region starting + * at 'base'. + * + * Requires: + * + *\li 'length' > 0 + *\li 'base' is a pointer to a sequence of 'length' bytes. + */ +#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) + +/*! + * \brief Make 'b' an invalid buffer. + * + * Requires: + *\li 'b' is a valid buffer. + * + * Ensures: + *\li Future attempts to use 'b' without calling isc_buffer_init() on + * it will cause an assertion failure. + */ +static inline void +isc_buffer_invalidate(isc_buffer_t *b) { + 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; +} + +/*! + * \brief Make 'r' refer to the region of 'b'. + * + * Requires: + * + *\li 'b' is a valid buffer. + * + *\li 'r' points to a region structure. + */ +static inline void +isc_buffer_region(isc_buffer_t *b, isc_region_t *r) { + ISC_REQUIRE(ISC_BUFFER_VALID(b)); + ISC_REQUIRE(r != NULL); + + r->base = b->base; + r->length = b->length; +} + +/*! + * \brief Make 'r' refer to the used region of 'b'. + * + * Requires: + * + *\li 'b' is a valid buffer. + * + *\li 'r' points to a region structure. + */ +static inline void +isc_buffer_usedregion(const isc_buffer_t *b, isc_region_t *r) { + ISC_REQUIRE(ISC_BUFFER_VALID(b)); + ISC_REQUIRE(r != NULL); + + r->base = b->base; + r->length = b->used; +} + +/*! + * \brief Make 'r' refer to the available region of 'b'. + * + * Requires: + * + *\li 'b' is a valid buffer. + * + *\li 'r' points to a region structure. + */ +static inline void +isc_buffer_availableregion(isc_buffer_t *b, isc_region_t *r) { + ISC_REQUIRE(ISC_BUFFER_VALID(b)); + ISC_REQUIRE(r != NULL); + + r->base = isc_buffer_used(b); + r->length = isc_buffer_availablelength(b); +} + +/*! + * \brief Increase the 'used' region of 'b' by 'n' bytes. + * + * Requires: + * + *\li 'b' is a valid buffer + * + *\li used + n <= length + */ +static inline void +isc_buffer_add(isc_buffer_t *b, unsigned int n) { + ISC_REQUIRE(ISC_BUFFER_VALID(b)); + ISC_REQUIRE(b->used + n <= b->length); + + b->used += n; +} + +/*! + * \brief Decrease the 'used' region of 'b' by 'n' bytes. + * + * Requires: + * + *\li 'b' is a valid buffer + * + *\li used >= n + */ +static inline void +isc_buffer_subtract(isc_buffer_t *b, unsigned int n) { + 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; + } +} + +/*!< + * \brief Make the used region empty. + * + * Requires: + * + *\li 'b' is a valid buffer + * + * Ensures: + * + *\li used = 0 + */ +static inline void +isc_buffer_clear(isc_buffer_t *b) { + ISC_REQUIRE(ISC_BUFFER_VALID(b)); + + b->used = 0; + b->current = 0; + b->active = 0; +} + +/*! + * \brief Make 'r' refer to the consumed region of 'b'. + * + * Requires: + * + *\li 'b' is a valid buffer. + * + *\li 'r' points to a region structure. + */ +static inline void +isc_buffer_consumedregion(isc_buffer_t *b, isc_region_t *r) { + ISC_REQUIRE(ISC_BUFFER_VALID(b)); + ISC_REQUIRE(r != NULL); + + r->base = b->base; + r->length = b->current; +} + +/*! + * \brief Make 'r' refer to the remaining region of 'b'. + * + * Requires: + * + *\li 'b' is a valid buffer. + * + *\li 'r' points to a region structure. + */ +static inline void +isc_buffer_remainingregion(isc_buffer_t *b, isc_region_t *r) { + ISC_REQUIRE(ISC_BUFFER_VALID(b)); + ISC_REQUIRE(r != NULL); + + r->base = isc_buffer_current(b); + r->length = isc_buffer_remaininglength(b); +} + +/*! + * \brief Make 'r' refer to the active region of 'b'. + * + * Requires: + * + *\li 'b' is a valid buffer. + * + *\li 'r' points to a region structure. + */ +static inline void +isc_buffer_activeregion(isc_buffer_t *b, isc_region_t *r) { + 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; + } +} + +/*! + * \brief Sets the end of the active region 'n' bytes after current. + * + * Requires: + * + *\li 'b' is a valid buffer. + * + *\li current + n <= used + */ +static inline void +isc_buffer_setactive(isc_buffer_t *b, unsigned int n) { + ISC_REQUIRE(ISC_BUFFER_VALID(b)); + ISC_REQUIRE(b->current + n <= b->used); + + b->active = b->current + n; +} + +/*!< + * \brief Make the consumed region empty. + * + * Requires: + * + *\li 'b' is a valid buffer + * + * Ensures: + * + *\li current == 0 + */ +static inline void +isc_buffer_first(isc_buffer_t *b) { + ISC_REQUIRE(ISC_BUFFER_VALID(b)); + + b->current = 0; +} + +/*! + * \brief Increase the 'consumed' region of 'b' by 'n' bytes. + * + * Requires: + * + *\li 'b' is a valid buffer + * + *\li current + n <= used + */ +static inline void +isc_buffer_forward(isc_buffer_t *b, unsigned int n) { + ISC_REQUIRE(ISC_BUFFER_VALID(b)); + ISC_REQUIRE(b->current + n <= b->used); + + b->current += n; +} + +/*! + * \brief Decrease the 'consumed' region of 'b' by 'n' bytes. + * + * Requires: + * + *\li 'b' is a valid buffer + * + *\li n <= current + */ +static inline void +isc_buffer_back(isc_buffer_t *b, unsigned int n) { + ISC_REQUIRE(ISC_BUFFER_VALID(b)); + ISC_REQUIRE(n <= b->current); + + b->current -= n; +} + +/*! + * \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. + */ +static inline void +isc_buffer_putuint8(isc_buffer_t *b, uint8_t val) { + unsigned char *cp; + + ISC_REQUIRE(ISC_BUFFER_VALID(b)); + + if (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] = 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. + */ +static inline void +isc_buffer_putuint16(isc_buffer_t *b, uint16_t val) { + unsigned char *cp; + + ISC_REQUIRE(ISC_BUFFER_VALID(b)); + + if (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)(val >> 8); + cp[1] = (unsigned char)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. + */ +static inline void +isc_buffer_putuint24(isc_buffer_t *b, uint32_t val) { + unsigned char *cp; + + ISC_REQUIRE(ISC_BUFFER_VALID(b)); + + if (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)(val >> 16); + cp[1] = (unsigned char)(val >> 8); + cp[2] = (unsigned char)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. + */ +static inline void +isc_buffer_putuint32(isc_buffer_t *b, uint32_t val) { + unsigned char *cp; + + ISC_REQUIRE(ISC_BUFFER_VALID(b)); + + if (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)(val >> 24); + cp[1] = (unsigned char)(val >> 16); + cp[2] = (unsigned char)(val >> 8); + cp[3] = (unsigned char)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. + */ +static inline void +isc_buffer_putuint48(isc_buffer_t *b, uint64_t val) { + unsigned char *cp = NULL; + + ISC_REQUIRE(ISC_BUFFER_VALID(b)); + + if (b->autore) { + isc_buffer_t *tmp = b; + ISC_REQUIRE(isc_buffer_reserve(&tmp, 6) == ISC_R_SUCCESS); + } + + ISC_REQUIRE(isc_buffer_availablelength(b) >= 6U); + + cp = isc_buffer_used(b); + b->used += 6; + cp[0] = (unsigned char)(val >> 40); + cp[1] = (unsigned char)(val >> 32); + cp[2] = (unsigned char)(val >> 24); + cp[3] = (unsigned char)(val >> 16); + cp[4] = (unsigned char)(val >> 8); + cp[5] = (unsigned char)val; +} + +/*! + * \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'. + */ +static inline void +isc_buffer_putmem(isc_buffer_t *b, const unsigned char *base, + unsigned int length) { + ISC_REQUIRE(ISC_BUFFER_VALID(b)); + + if (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; + } +} + +/*! + * \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'). + */ +static inline void +isc_buffer_putstr(isc_buffer_t *b, const char *source) { + unsigned int length; + unsigned char *cp; + + ISC_REQUIRE(ISC_BUFFER_VALID(b)); + ISC_REQUIRE(source != NULL); + + length = (unsigned int)strlen(source); + if (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; +} +ISC_LANG_ENDDECLS 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..ac72135 --- /dev/null +++ b/lib/isc/include/isc/commandline.h @@ -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. + */ + +#pragma once + +/*! \file isc/commandline.h */ + +#include <stdbool.h> + +#include <isc/lang.h> +#include <isc/result.h> +#include <isc/types.h> + +/*% Index into parent argv vector. */ +extern int isc_commandline_index; +/*% Character checked for validity. */ +extern int isc_commandline_option; +/*% Argument associated with option. */ +extern char *isc_commandline_argument; +/*% For printing error messages. */ +extern char *isc_commandline_progname; +/*% Print error message. */ +extern bool isc_commandline_errprint; +/*% Reset getopt. */ +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 diff --git a/lib/isc/include/isc/condition.h b/lib/isc/include/isc/condition.h new file mode 100644 index 0000000..0e1dcf1 --- /dev/null +++ b/lib/isc/include/isc/condition.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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) { \ + FATAL_SYSERROR(errno, "pthread_cond_init()"); \ + } + +#define isc_condition_wait(cp, mp) \ + ((pthread_cond_wait((cp), (mp)) == 0) ? ISC_R_SUCCESS \ + : ISC_R_UNEXPECTED) + +#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/include/isc/counter.h b/lib/isc/include/isc/counter.h new file mode 100644 index 0000000..820e4a2 --- /dev/null +++ b/lib/isc/include/isc/counter.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/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 diff --git a/lib/isc/include/isc/crc64.h b/lib/isc/include/isc/crc64.h new file mode 100644 index 0000000..4147672 --- /dev/null +++ b/lib/isc/include/isc/crc64.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. + */ + +#pragma once + +/*! \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 diff --git a/lib/isc/include/isc/deprecated.h b/lib/isc/include/isc/deprecated.h new file mode 100644 index 0000000..c83508d --- /dev/null +++ b/lib/isc/include/isc/deprecated.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 + +#if (__GNUC__ + 0) > 3 +#define ISC_DEPRECATED __attribute__((deprecated)) +#else /* if (__GNUC__ + 0) > 3 */ +#define ISC_DEPRECATED /* none */ +#endif /* __GNUC__ > 3*/ diff --git a/lib/isc/include/isc/dir.h b/lib/isc/include/isc/dir.h new file mode 100644 index 0000000..c1ff2c7 --- /dev/null +++ b/lib/isc/include/isc/dir.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. + */ + +#pragma once + +/*! \file */ + +#include <dirent.h> +#include <limits.h> + +#include <isc/lang.h> +#include <isc/result.h> + +#include <sys/types.h> /* Required on some systems. */ + +#ifndef NAME_MAX +#define NAME_MAX 256 +#endif + +#ifndef PATH_MAX +#define PATH_MAX 1024 +#endif + +/*% 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 diff --git a/lib/isc/include/isc/endian.h b/lib/isc/include/isc/endian.h new file mode 100644 index 0000000..be91b1d --- /dev/null +++ b/lib/isc/include/isc/endian.h @@ -0,0 +1,166 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 __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..0151df3 --- /dev/null +++ b/lib/isc/include/isc/errno.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. + */ + +#pragma once + +/*! \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 diff --git a/lib/isc/include/isc/error.h b/lib/isc/include/isc/error.h new file mode 100644 index 0000000..f4f6682 --- /dev/null +++ b/lib/isc/include/isc/error.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/error.h */ + +#include <stdarg.h> + +#include <isc/attributes.h> +#include <isc/formatcheck.h> +#include <isc/lang.h> + +ISC_LANG_BEGINDECLS + +typedef void (*isc_errorcallback_t)(const char *, int, const char *, + 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 *, const char *, ...) + ISC_FORMAT_PRINTF(4, 5); + +/*% fatal error */ +noreturn void +isc_error_fatal(const char *, int, const char *, const char *, ...) + ISC_FORMAT_PRINTF(4, 5); + +ISC_LANG_ENDDECLS diff --git a/lib/isc/include/isc/event.h b/lib/isc/include/isc/event.h new file mode 100644 index 0000000..811bcd8 --- /dev/null +++ b/lib/isc/include/isc/event.h @@ -0,0 +1,117 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/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 diff --git a/lib/isc/include/isc/eventclass.h b/lib/isc/include/isc/eventclass.h new file mode 100644 index 0000000..b8be2ff --- /dev/null +++ b/lib/isc/include/isc/eventclass.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/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) +/*@}*/ diff --git a/lib/isc/include/isc/file.h b/lib/isc/include/isc/file.h new file mode 100644 index 0000000..f78c5c5 --- /dev/null +++ b/lib/isc/include/isc/file.h @@ -0,0 +1,381 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/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 + */ + +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 diff --git a/lib/isc/include/isc/formatcheck.h b/lib/isc/include/isc/formatcheck.h new file mode 100644 index 0000000..e3a37b0 --- /dev/null +++ b/lib/isc/include/isc/formatcheck.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 + +/*! \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__ */ diff --git a/lib/isc/include/isc/fuzz.h b/lib/isc/include/isc/fuzz.h new file mode 100644 index 0000000..ce322ea --- /dev/null +++ b/lib/isc/include/isc/fuzz.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 + +typedef enum { + isc_fuzz_none, + isc_fuzz_client, + isc_fuzz_tcpclient, + isc_fuzz_resolver, + isc_fuzz_http, + isc_fuzz_rndc +} isc_fuzztype_t; diff --git a/lib/isc/include/isc/glob.h b/lib/isc/include/isc/glob.h new file mode 100644 index 0000000..c9e8d19 --- /dev/null +++ b/lib/isc/include/isc/glob.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 + +#include <isc/lang.h> +#include <isc/result.h> + +#if HAVE_GLOB_H +#include <glob.h> +#else +#include <stddef.h> + +#include <isc/mem.h> + +typedef struct { + size_t gl_pathc; + char **gl_pathv; + isc_mem_t *mctx; + void *reserved; +} glob_t; + +#endif + +ISC_LANG_BEGINDECLS + +isc_result_t +isc_glob(const char *pattern, glob_t *pglob); + +void +isc_globfree(glob_t *pglob); + +ISC_LANG_ENDDECLS diff --git a/lib/isc/include/isc/hash.h b/lib/isc/include/isc/hash.h new file mode 100644 index 0000000..ee9e74f --- /dev/null +++ b/lib/isc/include/isc/hash.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. + */ + +#pragma once + +#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 diff --git a/lib/isc/include/isc/heap.h b/lib/isc/include/isc/heap.h new file mode 100644 index 0000000..98ae74c --- /dev/null +++ b/lib/isc/include/isc/heap.h @@ -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. + */ + +#pragma once + +/*! \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 diff --git a/lib/isc/include/isc/hex.h b/lib/isc/include/isc/hex.h new file mode 100644 index 0000000..d5f714e --- /dev/null +++ b/lib/isc/include/isc/hex.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. + */ + +#pragma once + +/*! \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 diff --git a/lib/isc/include/isc/hmac.h b/lib/isc/include/isc/hmac.h new file mode 100644 index 0000000..68b63d4 --- /dev/null +++ b/lib/isc/include/isc/hmac.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. + */ + +/*! + * \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/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: in: the length of @digest + * out: 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). @digestlen + * is used to pass in the length of the digest buffer and returns the length + * of digest written to @digest. + */ +isc_result_t +isc_hmac(const isc_md_type_t *type, const void *key, const size_t 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, const 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: in: the length of @digest + * out: 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. @digestlen + * is used to pass in the length of the digest buffer and returns the length + * of digest written to @digest. 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..163fbef --- /dev/null +++ b/lib/isc/include/isc/ht.h @@ -0,0 +1,191 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 */ + +#pragma once + +#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; + +enum { ISC_HT_CASE_SENSITIVE = 0x00, ISC_HT_CASE_INSENSITIVE = 0x01 }; + +/*% + * Initialize hashtable at *htp, using memory context and size of (1<<bits) + * + * If 'options' contains ISC_HT_CASE_INSENSITIVE, then upper- and lower-case + * letters in key values will generate the same hash values; this can be used + * when the key for a hash table is a DNS name. + * + * 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, + unsigned int options); + +/*% + * 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 + *\li write-lock + * + * 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, const 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 + * \li read-lock + * + * 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, + const uint32_t keysize, void **valuep); + +/*% + * Delete node from hashtable + * + * Requires: + *\li ht is a valid hashtable + *\li write-lock + * + * 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, const 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 + */ +size_t +isc_ht_count(const isc_ht_t *ht); diff --git a/lib/isc/include/isc/httpd.h b/lib/isc/include/isc/httpd.h new file mode 100644 index 0000000..1f69a4c --- /dev/null +++ b/lib/isc/include/isc/httpd.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. + */ + +#pragma once + +/*! \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> +#include <isc/url.h> + +#define HTTPD_EVENTCLASS ISC_EVENTCLASS(4300) +#define HTTPD_SHUTDOWN (HTTPD_EVENTCLASS + 0x0001) + +#define ISC_HTTPDMGR_SHUTTINGDOWN 0x00000001 + +typedef isc_result_t(isc_httpdaction_t)( + const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, 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 *); + +isc_result_t +isc_httpdmgr_create(isc_nm_t *nm, isc_mem_t *mctx, isc_sockaddr_t *addr, + isc_httpdclientok_t *client_ok, + isc_httpdondestroy_t *ondestroy, void *cb_arg, + isc_httpdmgr_t **httpdmgrp); + +void +isc_httpdmgr_shutdown(isc_httpdmgr_t **httpdp); + +isc_result_t +isc_httpdmgr_addurl(isc_httpdmgr_t *httpdmgr, const char *url, bool isstatic, + isc_httpdaction_t *func, void *arg); + +void +isc_httpd_setfinishhook(void (*fn)(void)); + +bool +isc_httpdurl_isstatic(const isc_httpdurl_t *url); + +const isc_time_t * +isc_httpdurl_loadtime(const isc_httpdurl_t *url); + +const isc_time_t * +isc_httpd_if_modified_since(const isc_httpd_t *httpd); diff --git a/lib/isc/include/isc/interfaceiter.h b/lib/isc/include/isc/interfaceiter.h new file mode 100644 index 0000000..c696091 --- /dev/null +++ b/lib/isc/include/isc/interfaceiter.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. + */ + +#pragma once + +/***** +***** 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 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..b7e88a5 --- /dev/null +++ b/lib/isc/include/isc/lang.h @@ -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. + */ + +#pragma once + +/*! \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 */ diff --git a/lib/isc/include/isc/lex.h b/lib/isc/include/isc/lex.h new file mode 100644 index 0000000..14b29cf --- /dev/null +++ b/lib/isc/include/isc/lex.h @@ -0,0 +1,444 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/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 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..54b4022 --- /dev/null +++ b/lib/isc/include/isc/log.h @@ -0,0 +1,853 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/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/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. + */ +extern isc_logcategory_t isc_categories[]; +extern isc_log_t *isc_lctx; +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_LOGCATEGORY_SSLKEYLOG (&isc_categories[2]) +/*@}*/ + +#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. + */ + +void +isc_log_setforcelog(bool v); +/*%< + * Turn forced logging on/off for the current thread. This can be used to + * temporarily increase the debug level to maximum for the duration of + * a single task event. + */ + +ISC_LANG_ENDDECLS diff --git a/lib/isc/include/isc/magic.h b/lib/isc/include/isc/magic.h new file mode 100644 index 0000000..5e2f184 --- /dev/null +++ b/lib/isc/include/isc/magic.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/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) \ + ((a) != NULL && ((const isc__magic_t *)(a))->magic == (b)) + +#define ISC_MAGIC(a, b, c, d) ((a) << 24 | (b) << 16 | (c) << 8 | (d)) diff --git a/lib/isc/include/isc/managers.h b/lib/isc/include/isc/managers.h new file mode 100644 index 0000000..774ed30 --- /dev/null +++ b/lib/isc/include/isc/managers.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. + */ + +#pragma once + +#include <isc/netmgr.h> +#include <isc/result.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, + isc_timermgr_t **timermgrp); + +void +isc_managers_destroy(isc_nm_t **netmgrp, isc_taskmgr_t **taskmgrp, + isc_timermgr_t **timermgrp); diff --git a/lib/isc/include/isc/md.h b/lib/isc/include/isc/md.h new file mode 100644 index 0000000..f52424b --- /dev/null +++ b/lib/isc/include/isc/md.h @@ -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. + */ + +/*! + * \file isc/md.h + * \brief This is the header file for message digest algorithms. + */ + +#pragma once + +#include <isc/lang.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..bd59f73 --- /dev/null +++ b/lib/isc/include/isc/mem.h @@ -0,0 +1,572 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/mem.h */ + +#include <stdbool.h> +#include <stdio.h> + +#include <isc/attributes.h> +#include <isc/lang.h> +#include <isc/mutex.h> +#include <isc/types.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 0 +#endif /* ifndef ISC_MEM_TRACKLINES */ + +extern unsigned int isc_mem_debugging; +extern unsigned int isc_mem_defaultflags; + +/*@{*/ +#define ISC_MEM_DEBUGTRACE 0x00000001U +#define ISC_MEM_DEBUGRECORD 0x00000002U +#define ISC_MEM_DEBUGUSAGE 0x00000004U +#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. + */ +/*@}*/ + +#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 */ + +/* + * Flags for isc_mem_create() calls. + */ +#define ISC_MEMFLAG_RESERVED1 0x00000001 /* reserved, obsoleted, don't use */ +#define ISC_MEMFLAG_RESERVED2 0x00000002 /* reserved, obsoleted, don't use */ +#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_DEFAULTFILL +#define ISC_MEMFLAG_DEFAULT ISC_MEMFLAG_FILL +#else /* if !ISC_MEM_USE_INTERNAL_MALLOC */ +#define ISC_MEMFLAG_DEFAULT 0 +#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 + */ + +/*% + * 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), 0 _ISC_MEM_FILELINE) +#define isc_mem_get_aligned(c, s, a) \ + ISCMEMFUNC(get)((c), (s), (a)_ISC_MEM_FILELINE) +#define isc_mem_reget(c, p, o, n) \ + ISCMEMFUNC(reget)((c), (p), (o), (n), 0 _ISC_MEM_FILELINE) +#define isc_mem_reget_aligned(c, p, o, n, a) \ + ISCMEMFUNC(reget)((c), (p), (o), (n), (a)_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, l) \ + ISCMEMFUNC(strndup)((c), (p), (l)_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), 0 _ISC_MEM_FILELINE); \ + (p) = NULL; \ + } while (0) +#define isc_mem_put_aligned(c, p, s, a) \ + do { \ + ISCMEMFUNC(put) \ + ((c), (p), (s), (a)_ISC_MEM_FILELINE); \ + (p) = NULL; \ + } while (0) +#define isc_mem_putanddetach(c, p, s) \ + do { \ + ISCMEMFUNC(putanddetach)((c), (p), (s), 0 _ISC_MEM_FILELINE); \ + (p) = NULL; \ + } while (0) +#define isc_mem_putanddetach_aligned(c, p, s, a) \ + do { \ + ISCMEMFUNC(putanddetach) \ + ((c), (p), (s), (a)_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) + +/*@{*/ +#define isc_mem_create(cp) ISCMEMFUNC(create)((cp)_ISC_MEM_FILELINE) +void ISCMEMFUNC(create)(isc_mem_t **_ISC_MEM_FLARG); + +/*!< + * \brief Create a memory context. + * + * Requires: + * mctxp != NULL && *mctxp == NULL */ +/*@}*/ + +#define isc_mem_create_arena(cp) isc__mem_create_arena((cp)_ISC_MEM_FILELINE) +void +isc__mem_create_arena(isc_mem_t **_ISC_MEM_FLARG); +/*!< + * \brief Create a memory context that routs all its operations to a + * dedicated jemalloc arena (when available). When jemalloc is not + * available, the function is, effectively, an alias to + * isc_mem_create(). + * + * Requires: + * mctxp != NULL && *mctxp == NULL */ +/*@}*/ + +isc_result_t +isc_mem_arena_set_muzzy_decay_ms(isc_mem_t *mctx, const ssize_t decay_ms); + +isc_result_t +isc_mem_arena_set_dirty_decay_ms(isc_mem_t *mctx, const ssize_t decay_ms); +/*!< + * \brief These two functions set the given parameters on the + * jemalloc arena associated with the memory context (if there is + * one). When jemalloc is not available, these are no-op. + * + * NOTE: The "muzzy_decay_ms" and "dirty_decay_ms" are the most common + * parameters to adjust when the defaults do not work well (per the + * official jemalloc tuning guide: + * https://github.com/jemalloc/jemalloc/blob/dev/TUNING.md). + * + * Requires: + * mctx - a valid memory context. + */ +/*@}*/ + +void +isc_mem_attach(isc_mem_t *, isc_mem_t **); + +/*@{*/ +void +isc_mem_attach(isc_mem_t *, isc_mem_t **); +#define isc_mem_detach(cp) ISCMEMFUNC(detach)((cp)_ISC_MEM_FILELINE) +void ISCMEMFUNC(detach)(isc_mem_t **_ISC_MEM_FLARG); +/*!< + * \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. + */ +/*@}*/ + +#define isc_mem_destroy(cp) ISCMEMFUNC(destroy)((cp)_ISC_MEM_FILELINE) +void ISCMEMFUNC(destroy)(isc_mem_t **_ISC_MEM_FLARG); +/*%< + * 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. + */ + +size_t +isc_mem_malloced(isc_mem_t *ctx); +/*%< + * Get an estimate of the amount of memory allocated in 'mctx', in bytes. + */ + +size_t +isc_mem_maxmalloced(isc_mem_t *ctx); +/*%< + * Get an estimate of the largest amount of memory that has been + * allocated in 'mctx' at any time. + */ + +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_clearwater(isc_mem_t *mctx); +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 set to NULL, the 'hiwater' and 'lowater' must set to 0, and + * high- and low-water processing are disabled for this memory context. There's + * a convenient function isc_mem_clearwater(). + * + * Requires: + * + *\li If 'water' is NULL, 'hiwater' and 'lowater' must be set to 0. + *\li If 'water' and 'water_arg' have previously been set, they are + unchanged. + *\li 'hiwater' >= 'lowater' + */ + +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); +/*%< + * Name 'ctx'. + * + * Notes: + * + *\li Only the first 15 characters of 'name' will be copied. + * + * 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. + */ + +#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 + */ + +#define isc_mempool_create(c, s, mp) \ + isc__mempool_create((c), (s), (mp)_ISC_MEM_FILELINE) +void +isc__mempool_create(isc_mem_t *restrict mctx, const size_t element_size, + isc_mempool_t **mpctxp _ISC_MEM_FLARG); +/*%< + * Create a memory pool. + * + * Requires: + *\li mctx is a valid memory context. + *\li size > 0 + *\li mpctxp != NULL and *mpctxp == NULL + * + * Defaults: + *\li freemax = 1 + *\li fillcount = 1 + * + * Returns: + *\li #ISC_R_NOMEMORY -- not enough memory to create pool + *\li #ISC_R_SUCCESS -- all is well. + */ + +#define isc_mempool_destroy(mp) isc__mempool_destroy((mp)_ISC_MEM_FILELINE) +void +isc__mempool_destroy(isc_mempool_t **restrict mpctxp _ISC_MEM_FLARG); +/*%< + * 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 *restrict 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. + * + * All functions require (in addition to other requirements): + * mpctx is a valid memory pool + */ + +unsigned int +isc_mempool_getfreemax(isc_mempool_t *restrict mpctx); +/*%< + * Returns the maximum allowed size of the free list. + */ + +void +isc_mempool_setfreemax(isc_mempool_t *restrict mpctx, const unsigned int limit); +/*%< + * Sets the maximum allowed size of the free list. + */ + +unsigned int +isc_mempool_getfreecount(isc_mempool_t *restrict mpctx); +/*%< + * Returns current size of the free list. + */ + +unsigned int +isc_mempool_getallocated(isc_mempool_t *restrict mpctx); +/*%< + * Returns the number of items allocated from this pool. + */ + +unsigned int +isc_mempool_getfillcount(isc_mempool_t *restrict 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 *restrict mpctx, + const 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, + size_t _ISC_MEM_FLARG); +void ISCMEMFUNC(put)(isc_mem_t *, void *, size_t, size_t _ISC_MEM_FLARG); +void ISCMEMFUNC(free)(isc_mem_t *, void *_ISC_MEM_FLARG); + +ISC_ATTR_MALLOC_DEALLOCATOR_IDX(ISCMEMFUNC(put), 2) +void *ISCMEMFUNC(get)(isc_mem_t *, size_t, size_t _ISC_MEM_FLARG); + +ISC_ATTR_DEALLOCATOR_IDX(ISCMEMFUNC(put), 2) +void *ISCMEMFUNC(reget)(isc_mem_t *, void *, size_t, size_t, + size_t _ISC_MEM_FLARG); + +ISC_ATTR_MALLOC_DEALLOCATOR_IDX(ISCMEMFUNC(free), 2) +void *ISCMEMFUNC(allocate)(isc_mem_t *, size_t _ISC_MEM_FLARG); + +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); + +ISC_ATTR_RETURNS_NONNULL +ISC_ATTR_MALLOC_DEALLOCATOR_IDX(ISCMEMFUNC(free), 2) +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 diff --git a/lib/isc/include/isc/meminfo.h b/lib/isc/include/isc/meminfo.h new file mode 100644 index 0000000..fc39e26 --- /dev/null +++ b/lib/isc/include/isc/meminfo.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. + */ + +#pragma once + +#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 diff --git a/lib/isc/include/isc/mutex.h b/lib/isc/include/isc/mutex.h new file mode 100644 index 0000000..b794216 --- /dev/null +++ b/lib/isc/include/isc/mutex.h @@ -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. + */ + +#pragma once + +/*! \file */ + +#include <pthread.h> +#include <stdio.h> + +#include <isc/lang.h> +#include <isc/result.h> /* for ISC_R_ codes */ + +ISC_LANG_BEGINDECLS + +typedef pthread_mutex_t isc_mutex_t; + +int +isc__mutex_init(isc_mutex_t *mp); + +#define isc_mutex_init(mp) \ + do { \ + int _err = isc__mutex_init((mp)); \ + if (_err != 0) { \ + FATAL_SYSERROR(_err, "pthread_mutex_init()"); \ + } \ + } while (0) + +#define isc_mutex_lock(mp) \ + ((pthread_mutex_lock((mp)) == 0) ? ISC_R_SUCCESS : ISC_R_UNEXPECTED) + +#define isc_mutex_unlock(mp) \ + ((pthread_mutex_unlock((mp)) == 0) ? ISC_R_SUCCESS : ISC_R_UNEXPECTED) + +#define isc_mutex_trylock(mp) \ + ((pthread_mutex_trylock((mp)) == 0) ? ISC_R_SUCCESS : ISC_R_LOCKBUSY) + +#define isc_mutex_destroy(mp) RUNTIME_CHECK(pthread_mutex_destroy((mp)) == 0) + +ISC_LANG_ENDDECLS diff --git a/lib/isc/include/isc/mutexblock.h b/lib/isc/include/isc/mutexblock.h new file mode 100644 index 0000000..f5f2e32 --- /dev/null +++ b/lib/isc/include/isc/mutexblock.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/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 diff --git a/lib/isc/include/isc/net.h b/lib/isc/include/isc/net.h new file mode 100644 index 0000000..1a3ce63 --- /dev/null +++ b/lib/isc/include/isc/net.h @@ -0,0 +1,292 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 + * \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/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. + */ + +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 diff --git a/lib/isc/include/isc/netaddr.h b/lib/isc/include/isc/netaddr.h new file mode 100644 index 0000000..112051f --- /dev/null +++ b/lib/isc/include/isc/netaddr.h @@ -0,0 +1,197 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/netaddr.h */ + +#include <inttypes.h> +#include <stdbool.h> + +#include <isc/lang.h> +#include <isc/net.h> +#include <isc/types.h> + +#include <sys/types.h> +#include <sys/un.h> + +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; + char un[sizeof(((struct sockaddr_un *)0)->sun_path)]; + } type; + uint32_t zone; +}; + +struct isc_netprefix { + isc_netaddr_t addr; + unsigned int prefixlen; +}; + +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 diff --git a/lib/isc/include/isc/netdb.h b/lib/isc/include/isc/netdb.h new file mode 100644 index 0000000..93799d7 --- /dev/null +++ b/lib/isc/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. + */ + +#pragma once + +/***** +***** 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> diff --git a/lib/isc/include/isc/netmgr.h b/lib/isc/include/isc/netmgr.h new file mode 100644 index 0000000..eff33f6 --- /dev/null +++ b/lib/isc/include/isc/netmgr.h @@ -0,0 +1,811 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/tls.h> +#include <isc/types.h> + +#include <sys/socket.h> +#include <sys/types.h> + +#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. + */ + +void +isc_nmsocket_set_tlsctx(isc_nmsocket_t *listener, isc_tlsctx_t *tlsctx); +/*%< + * Asynchronously replace the TLS context within the listener socket object. + * The function is intended to be used during reconfiguration. + * + * Requires: + * \li 'listener' is a pointer to a valid network manager listener socket + object with TLS support; + * \li 'tlsctx' is a valid pointer to a TLS context object. + */ + +void +isc_nmsocket_set_max_streams(isc_nmsocket_t *listener, + const uint32_t max_streams); +/*%< + * Set the maximum allowed number of concurrent streams for accepted + * client connections. The implementation might be asynchronous + * depending on the listener socket type. + * + * The call is a no-op for any listener socket type that does not + * support concept of multiple sessions per a client + * connection. Currently, it works only for HTTP/2 listeners. + * + * Setting 'max_streams' to '0' instructs the listener that there is + * no limit for concurrent streams. + * + * Requires: + * \li 'listener' is a pointer to a valid network manager listener socket. + */ + +#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. + */ +bool +isc_nmhandle_timer_running(isc_nmhandle_t *handle); +/*%< + * Return true if the timer for the socket connected to 'handle' + * is running. + */ + +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 or + * TLSDNS). 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'. + */ + +isc_result_t +isc_nm_routeconnect(isc_nm_t *mgr, isc_nm_cb_t cb, void *cbarg, + size_t extrahandlesize); +/*%< + * Open a route/netlink socket and call 'cb', so the caller can be + * begin listening for interface changes. This behaves similarly to + * isc_nm_udpconnect(). + * + * Returns ISC_R_NOTIMPLEMENTED on systems where route/netlink sockets + * are not supported. + */ + +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. + */ + +isc_result_t +isc_nm_listentlsdns(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_tlsctx_t *sslctx, isc_nmsocket_t **sockp); +/*%< + * Same as isc_nm_listentcpdns but for an SSL (DoT) socket. + */ + +void +isc_nm_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_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. + */ + +void +isc_nm_setnetbuffers(isc_nm_t *mgr, int32_t recv_tcp, int32_t send_tcp, + int32_t recv_udp, int32_t send_udp); +/*%< + * If not 0, sets the SO_RCVBUF and SO_SNDBUF socket options for TCP and UDP + * respectively. + * + * 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. + */ + +isc_result_t +isc_nm_checkaddr(const isc_sockaddr_t *addr, isc_socktype_t type); +/*%< + * Check whether the specified address is available on the local system + * by opening a socket and immediately closing it. + * + * Requires: + *\li 'addr' is not NULL. + */ + +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); +void +isc_nm_tlsdnsconnect(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_tlsctx_t *sslctx, + isc_tlsctx_client_session_cache_t *client_sess_cache); +/*%< + * Establish a DNS client connection via a TCP or TLS 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'. + */ + +/*%< + * Returns 'true' iff 'handle' is associated with a socket of type + * 'isc_nm_tlsdnssocket'. + */ + +bool +isc_nm_is_http_handle(isc_nmhandle_t *handle); +/*%< + * Returns 'true' iff 'handle' is associated with a socket of type + * 'isc_nm_httpsocket'. + */ + +#if HAVE_LIBNGHTTP2 + +#define ISC_NM_HTTP_DEFAULT_PATH "/dns-query" + +isc_result_t +isc_nm_listentls(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_tlsctx_t *sslctx, isc_nmsocket_t **sockp); + +void +isc_nm_tlsconnect(isc_nm_t *mgr, isc_sockaddr_t *local, isc_sockaddr_t *peer, + isc_nm_cb_t cb, void *cbarg, isc_tlsctx_t *ctx, + isc_tlsctx_client_session_cache_t *client_sess_cache, + unsigned int timeout, size_t extrahandlesize); + +void +isc_nm_httpconnect(isc_nm_t *mgr, isc_sockaddr_t *local, isc_sockaddr_t *peer, + const char *uri, bool POST, isc_nm_cb_t cb, void *cbarg, + isc_tlsctx_t *ctx, + isc_tlsctx_client_session_cache_t *client_sess_cache, + unsigned int timeout, size_t extrahandlesize); + +isc_result_t +isc_nm_listenhttp(isc_nm_t *mgr, isc_sockaddr_t *iface, int backlog, + isc_quota_t *quota, isc_tlsctx_t *ctx, + isc_nm_http_endpoints_t *eps, uint32_t max_concurrent_streams, + isc_nmsocket_t **sockp); + +isc_nm_http_endpoints_t * +isc_nm_http_endpoints_new(isc_mem_t *mctx); +/*%< + * Create a new, empty HTTP endpoints set object. + * + * Requires: + * \li 'mctx' a valid memory context object. + */ + +isc_result_t +isc_nm_http_endpoints_add(isc_nm_http_endpoints_t *restrict eps, + const char *uri, const isc_nm_recv_cb_t cb, + void *cbarg, const size_t extrahandlesize); +/*%< Adds a new endpoint to the given HTTP endpoints set object. + * + * NOTE: adding an endpoint is allowed only if the endpoint object has + * not been passed to isc_nm_listenhttp() yet. + * + * Requires: + * \li 'eps' is a valid pointer to a valid isc_nm_http_endpoints_t + * object; + * \li 'uri' is a valid pointer to a string of length > 0; + * \li 'cb' is a valid pointer to a read callback function. + */ + +void +isc_nm_http_endpoints_attach(isc_nm_http_endpoints_t *source, + isc_nm_http_endpoints_t **targetp); +/*%< + * Attaches to an HTTP endpoints set object. + * + * Requires: + * \li 'source' is a non-NULL pointer to a valid + * isc_nm_http_endpoints_t object; + * \li 'target' is a pointer to a pointer, containing NULL. + */ + +void +isc_nm_http_endpoints_detach(isc_nm_http_endpoints_t **restrict epsp); +/*%< + * Detaches from an HTTP endpoints set object. When reference count + * reaches 0, the object get deleted. + * + * Requires: + * \li 'epsp' is a pointer to a pointer to a valid + * isc_nm_http_endpoints_t object. + */ + +bool +isc_nm_http_path_isvalid(const char *path); +/*%< + * Returns 'true' if 'path' matches the format requirements for + * the path component of a URI as defined in RFC 3986 section 3.3. + */ + +void +isc_nm_http_makeuri(const bool https, const isc_sockaddr_t *sa, + const char *hostname, const uint16_t http_port, + const char *abs_path, char *outbuf, + const size_t outbuf_len); +/*%< + * Makes a URI connection string out of na isc_sockaddr_t object 'sa' + * or the specified 'hostname' and 'http_port'. + * + * Requires: + * \li 'abs_path' is a valid absolute HTTP path string; + * \li 'outbuf' is a valid pointer to a buffer which will get the result; + * \li 'outbuf_len' is a size of the result buffer and is greater than zero. + */ + +void +isc_nm_http_set_endpoints(isc_nmsocket_t *listener, + isc_nm_http_endpoints_t *eps); +/*%< + * Asynchronously replace the set of HTTP endpoints (paths) within + * the listener socket object. The function is intended to be used + * during reconfiguration. + * + * Requires: + * \li 'listener' is a pointer to a valid network manager HTTP listener socket; + * \li 'eps' is a valid pointer to an HTTP endpoints set. + */ + +#endif /* HAVE_LIBNGHTTP2 */ + +void +isc_nm_bad_request(isc_nmhandle_t *handle); +/*%< + * Perform a transport protocol specific action on the handle in case of a + * bad/malformed incoming DNS message. + * + * NOTE: The function currently is no-op for any protocol except HTTP/2. + * + * Requires: + * \li 'handle' is a valid netmgr handle object. + */ + +isc_result_t +isc_nm_xfr_checkperm(isc_nmhandle_t *handle); +/*%< + * Check if it is permitted to do a zone transfer over the given handle. + * + * Returns: + * \li #ISC_R_SUCCESS Success, permission check passed successfully + * \li #ISC_R_DOTALPNERROR No permission because of ALPN tag mismatch + * \li #ISC_R_NOPERM No permission because of other restrictions + * \li any other result indicates failure (i.e. no permission) + * + * Requires: + * \li 'handle' is a valid connection handle. + */ + +void +isc_nm_set_maxage(isc_nmhandle_t *handle, const uint32_t ttl); +/*%< + * Set the minimal time to live from the server's response Answer + * section as a hint to the underlying transport. + * + * NOTE: The function currently is no-op for any protocol except HTTP/2. + * + * Requires: + * + * \li 'handle' is a valid netmgr handle object associated with an accepted + * connection. + */ + +isc_nmsocket_type +isc_nm_socket_type(const isc_nmhandle_t *handle); +/*%< + * Returns the handle's underlying socket type. + * + * Requires: + * \li 'handle' is a valid netmgr handle object. + */ + +bool +isc_nm_has_encryption(const isc_nmhandle_t *handle); +/*%< + * Returns 'true' iff the handle's underlying transport does encryption. + * + * Requires: + * \li 'handle' is a valid netmgr handle object. + */ + +const char * +isc_nm_verify_tls_peer_result_string(const isc_nmhandle_t *handle); +/*%< + * Returns user-readable message describing TLS peer's certificate + * validation result. Returns 'NULL' for the transport handles for + * which peer verification was not performed. + * + * Requires: + * \li 'handle' is a valid netmgr handle object. + */ + +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..307864a --- /dev/null +++ b/lib/isc/include/isc/netscope.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 + +/*! \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 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/offset.h b/lib/isc/include/isc/offset.h new file mode 100644 index 0000000..2b62e2a --- /dev/null +++ b/lib/isc/include/isc/offset.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. + */ + +#pragma once + +/*! \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; diff --git a/lib/isc/include/isc/once.h b/lib/isc/include/isc/once.h new file mode 100644 index 0000000..7d341ca --- /dev/null +++ b/lib/isc/include/isc/once.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 + +/*! \file */ + +#include <pthread.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) diff --git a/lib/isc/include/isc/os.h b/lib/isc/include/isc/os.h new file mode 100644 index 0000000..fd7e5cf --- /dev/null +++ b/lib/isc/include/isc/os.h @@ -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. + */ + +#pragma once + +/*! \file isc/os.h */ +#include <isc/lang.h> +#include <isc/types.h> + +#include <sys/stat.h> + +ISC_LANG_BEGINDECLS + +/*%< + * Hardcode the L1 cacheline size of the CPU to 64, this is checked in + * the os.c library constructor if operating system provide means to + * get the L1 cacheline size using sysconf(). + */ +#define ISC_OS_CACHELINE_SIZE 64 + +unsigned int +isc_os_ncpus(void); +/*%< + * Return the number of CPUs available on the system, or 1 if this cannot + * be determined. + */ + +unsigned long +isc_os_cacheline(void); +/*%< + * Return L1 cacheline size of the CPU. + * If L1 cache is greater than ISC_OS_CACHELINE_SIZE, ensure it is used + * instead of constant. Is common on ppc64le architecture. + */ + +mode_t +isc_os_umask(void); +/*%< + * Return umask of the current process as initialized at the program start + */ + +ISC_LANG_ENDDECLS diff --git a/lib/isc/include/isc/parseint.h b/lib/isc/include/isc/parseint.h new file mode 100644 index 0000000..aa54772 --- /dev/null +++ b/lib/isc/include/isc/parseint.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. + */ + +#pragma once + +#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 diff --git a/lib/isc/include/isc/pool.h b/lib/isc/include/isc/pool.h new file mode 100644 index 0000000..90b8934 --- /dev/null +++ b/lib/isc/include/isc/pool.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. + */ + +#pragma once + +/***** +***** 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 diff --git a/lib/isc/include/isc/portset.h b/lib/isc/include/isc/portset.h new file mode 100644 index 0000000..fa5d51e --- /dev/null +++ b/lib/isc/include/isc/portset.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. + */ + +/*! \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. + */ + +#pragma once + +/*** + *** 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 diff --git a/lib/isc/include/isc/print.h b/lib/isc/include/isc/print.h new file mode 100644 index 0000000..d48bf70 --- /dev/null +++ b/lib/isc/include/isc/print.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 + +/*! \file isc/print.h */ + +/*** + *** Imports + ***/ + +#include <isc/formatcheck.h> /* Required for ISC_FORMAT_PRINTF() macro. */ +#include <isc/lang.h> + +/*** + *** Functions + ***/ + +#include <stdio.h> diff --git a/lib/isc/include/isc/quota.h b/lib/isc/include/isc/quota.h new file mode 100644 index 0000000..2a25fa1 --- /dev/null +++ b/lib/isc/include/isc/quota.h @@ -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. + */ + +#pragma once + +/***** +***** 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; + ISC_LINK(isc_quota_t) link; +}; + +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 diff --git a/lib/isc/include/isc/radix.h b/lib/isc/include/isc/radix.h new file mode 100644 index 0000000..9a91118 --- /dev/null +++ b/lib/isc/include/isc/radix.h @@ -0,0 +1,221 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 <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) 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..45ec447 --- /dev/null +++ b/lib/isc/include/isc/ratelimiter.h @@ -0,0 +1,145 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/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 diff --git a/lib/isc/include/isc/refcount.h b/lib/isc/include/isc/refcount.h new file mode 100644 index 0000000..6b1f7cd --- /dev/null +++ b/lib/isc/include/isc/refcount.h @@ -0,0 +1,244 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/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) \ + ({ \ + 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) \ + ({ \ + 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) \ + ({ \ + 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) + +#define ISC_REFCOUNT_TRACE_DECL(name) \ + name##_t *name##__ref(name##_t *ptr, const char *func, \ + const char *file, unsigned int line); \ + void name##__unref(name##_t *ptr, const char *func, const char *file, \ + unsigned int line); \ + void name##__attach(name##_t *ptr, name##_t **ptrp, const char *func, \ + const char *file, unsigned int line); \ + void name##__detach(name##_t **ptrp, const char *func, \ + const char *file, unsigned int line) + +#define ISC_REFCOUNT_TRACE_IMPL(name, destroy) \ + name##_t *name##__ref(name##_t *ptr, const char *func, \ + const char *file, unsigned int line) { \ + REQUIRE(ptr != NULL); \ + uint_fast32_t refs = \ + isc_refcount_increment(&ptr->references) + 1; \ + fprintf(stderr, \ + "%s:%s:%s:%u:%p->references = %" PRIuFAST32 "\n", \ + __func__, func, file, line, ptr, refs); \ + return (ptr); \ + } \ + \ + void name##__unref(name##_t *ptr, const char *func, const char *file, \ + unsigned int line) { \ + REQUIRE(ptr != NULL); \ + uint_fast32_t refs = \ + isc_refcount_decrement(&ptr->references) - 1; \ + if (refs == 0) { \ + destroy(ptr); \ + } \ + fprintf(stderr, \ + "%s:%s:%s:%u:%p->references = %" PRIuFAST32 "\n", \ + __func__, func, file, line, ptr, refs); \ + } \ + void name##__attach(name##_t *ptr, name##_t **ptrp, const char *func, \ + const char *file, unsigned int line) { \ + REQUIRE(ptrp != NULL && *ptrp == NULL); \ + uint_fast32_t refs = \ + isc_refcount_increment(&ptr->references) + 1; \ + fprintf(stderr, \ + "%s:%s:%s:%u:%p->references = %" PRIuFAST32 "\n", \ + __func__, func, file, line, ptr, refs); \ + *ptrp = ptr; \ + } \ + \ + void name##__detach(name##_t **ptrp, const char *func, \ + const char *file, unsigned int line) { \ + REQUIRE(ptrp != NULL && *ptrp != NULL); \ + name##_t *ptr = *ptrp; \ + *ptrp = NULL; \ + uint_fast32_t refs = \ + isc_refcount_decrement(&ptr->references) - 1; \ + if (refs == 0) { \ + destroy(ptr); \ + } \ + fprintf(stderr, \ + "%s:%s:%s:%u:%p->references = %" PRIuFAST32 "\n", \ + __func__, func, file, line, ptr, refs); \ + } + +#define ISC_REFCOUNT_DECL(name) \ + name##_t *name##_ref(name##_t *ptr); \ + void name##_unref(name##_t *ptr); \ + void name##_attach(name##_t *ptr, name##_t **ptrp); \ + void name##_detach(name##_t **ptrp) + +#define ISC_REFCOUNT_IMPL(name, destroy) \ + name##_t *name##_ref(name##_t *ptr) { \ + REQUIRE(ptr != NULL); \ + isc_refcount_increment(&ptr->references); \ + return (ptr); \ + } \ + \ + void name##_unref(name##_t *ptr) { \ + REQUIRE(ptr != NULL); \ + if (isc_refcount_decrement(&ptr->references) == 1) { \ + destroy(ptr); \ + } \ + } \ + void name##_attach(name##_t *ptr, name##_t **ptrp) { \ + REQUIRE(ptrp != NULL && *ptrp == NULL); \ + name##_ref(ptr); \ + *ptrp = ptr; \ + } \ + \ + void name##_detach(name##_t **ptrp) { \ + REQUIRE(ptrp != NULL && *ptrp != NULL); \ + name##_t *ptr = *ptrp; \ + *ptrp = NULL; \ + name##_unref(ptr); \ + } + +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..989d7b1 --- /dev/null +++ b/lib/isc/include/isc/regex.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 + +/*! \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 diff --git a/lib/isc/include/isc/region.h b/lib/isc/include/isc/region.h new file mode 100644 index 0000000..6f775cf --- /dev/null +++ b/lib/isc/include/isc/region.h @@ -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. + */ + +#pragma once + +/*! \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 diff --git a/lib/isc/include/isc/resource.h b/lib/isc/include/isc/resource.h new file mode 100644 index 0000000..dc4b2b1 --- /dev/null +++ b/lib/isc/include/isc/resource.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. + */ + +#pragma once + +/*! \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 diff --git a/lib/isc/include/isc/result.h b/lib/isc/include/isc/result.h new file mode 100644 index 0000000..a43772e --- /dev/null +++ b/lib/isc/include/isc/result.h @@ -0,0 +1,293 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/result.h */ + +#include <inttypes.h> + +#include <isc/lang.h> + +typedef enum isc_result { + ISC_R_SUCCESS, /*%< success */ + ISC_R_NOMEMORY, /*%< out of memory */ + ISC_R_TIMEDOUT, /*%< timed out */ + ISC_R_NOTHREADS, /*%< no available threads */ + ISC_R_ADDRNOTAVAIL, /*%< address not available */ + ISC_R_ADDRINUSE, /*%< address in use */ + ISC_R_NOPERM, /*%< permission denied */ + ISC_R_NOCONN, /*%< no pending connections */ + ISC_R_NETUNREACH, /*%< network unreachable */ + ISC_R_HOSTUNREACH, /*%< host unreachable */ + ISC_R_NETDOWN, /*%< network down */ + ISC_R_HOSTDOWN, /*%< host down */ + ISC_R_CONNREFUSED, /*%< connection refused */ + ISC_R_NORESOURCES, /*%< not enough free resources */ + ISC_R_EOF, /*%< end of file */ + ISC_R_BOUND, /*%< socket already bound */ + ISC_R_RELOAD, /*%< reload */ + ISC_R_SUSPEND = ISC_R_RELOAD, /*%< alias of 'reload' */ + ISC_R_LOCKBUSY, /*%< lock busy */ + ISC_R_EXISTS, /*%< already exists */ + ISC_R_NOSPACE, /*%< ran out of space */ + ISC_R_CANCELED, /*%< operation canceled */ + ISC_R_NOTBOUND, /*%< socket is not bound */ + ISC_R_SHUTTINGDOWN, /*%< shutting down */ + ISC_R_NOTFOUND, /*%< not found */ + ISC_R_UNEXPECTEDEND, /*%< unexpected end of input */ + ISC_R_FAILURE, /*%< generic failure */ + ISC_R_IOERROR, /*%< I/O error */ + ISC_R_NOTIMPLEMENTED, /*%< not implemented */ + ISC_R_UNBALANCED, /*%< unbalanced parentheses */ + ISC_R_NOMORE, /*%< no more */ + ISC_R_INVALIDFILE, /*%< invalid file */ + ISC_R_BADBASE64, /*%< bad base64 encoding */ + ISC_R_UNEXPECTEDTOKEN, /*%< unexpected token */ + ISC_R_QUOTA, /*%< quota reached */ + ISC_R_UNEXPECTED, /*%< unexpected error */ + ISC_R_ALREADYRUNNING, /*%< already running */ + ISC_R_IGNORE, /*%< ignore */ + ISC_R_MASKNONCONTIG, /*%< addr mask not contiguous */ + ISC_R_FILENOTFOUND, /*%< file not found */ + ISC_R_FILEEXISTS, /*%< file already exists */ + ISC_R_NOTCONNECTED, /*%< socket is not connected */ + ISC_R_RANGE, /*%< out of range */ + ISC_R_NOENTROPY, /*%< out of entropy */ + ISC_R_MULTICAST, /*%< invalid use of multicast */ + ISC_R_NOTFILE, /*%< not a file */ + ISC_R_NOTDIRECTORY, /*%< not a directory */ + ISC_R_EMPTY, /*%< queue is empty */ + ISC_R_FAMILYMISMATCH, /*%< address family mismatch */ + ISC_R_FAMILYNOSUPPORT, /*%< AF not supported */ + ISC_R_BADHEX, /*%< bad hex encoding */ + ISC_R_TOOMANYOPENFILES, /*%< too many open files */ + ISC_R_NOTBLOCKING, /*%< not blocking */ + ISC_R_UNBALANCEDQUOTES, /*%< unbalanced quotes */ + ISC_R_INPROGRESS, /*%< operation in progress */ + ISC_R_CONNECTIONRESET, /*%< connection reset */ + ISC_R_SOFTQUOTA, /*%< soft quota reached */ + ISC_R_BADNUMBER, /*%< not a valid number */ + ISC_R_DISABLED, /*%< disabled */ + ISC_R_MAXSIZE, /*%< max size */ + ISC_R_BADADDRESSFORM, /*%< invalid address format */ + ISC_R_BADBASE32, /*%< bad base32 encoding */ + ISC_R_UNSET, /*%< unset */ + ISC_R_MULTIPLE, /*%< multiple */ + ISC_R_WOULDBLOCK, /*%< would block */ + ISC_R_COMPLETE, /*%< complete */ + ISC_R_CRYPTOFAILURE, /*%< cryptography library failure */ + ISC_R_DISCQUOTA, /*%< disc quota */ + ISC_R_DISCFULL, /*%< disc full */ + ISC_R_DEFAULT, /*%< default */ + ISC_R_IPV4PREFIX, /*%< IPv4 prefix */ + ISC_R_TLSERROR, /*%< TLS error */ + ISC_R_TLSBADPEERCERT, /*%< TLS peer certificate verification failed */ + ISC_R_HTTP2ALPNERROR, /*%< ALPN for HTTP/2 failed */ + ISC_R_DOTALPNERROR, /*%< ALPN for DoT failed */ + ISC_R_INVALIDPROTO, /*%< invalid protocol */ + + DNS_R_LABELTOOLONG, + DNS_R_BADESCAPE, + DNS_R_EMPTYLABEL, + DNS_R_BADDOTTEDQUAD, + DNS_R_INVALIDNS, + DNS_R_UNKNOWN, + DNS_R_BADLABELTYPE, + DNS_R_BADPOINTER, + DNS_R_TOOMANYHOPS, + DNS_R_DISALLOWED, + DNS_R_EXTRATOKEN, + DNS_R_EXTRADATA, + DNS_R_TEXTTOOLONG, + DNS_R_NOTZONETOP, + DNS_R_SYNTAX, + DNS_R_BADCKSUM, + DNS_R_BADAAAA, + DNS_R_NOOWNER, + DNS_R_NOTTL, + DNS_R_BADCLASS, + DNS_R_NAMETOOLONG, + DNS_R_PARTIALMATCH, + DNS_R_NEWORIGIN, + DNS_R_UNCHANGED, + DNS_R_BADTTL, + DNS_R_NOREDATA, + DNS_R_CONTINUE, + DNS_R_DELEGATION, + DNS_R_GLUE, + DNS_R_DNAME, + DNS_R_CNAME, + DNS_R_BADDB, + DNS_R_ZONECUT, + DNS_R_BADZONE, + DNS_R_MOREDATA, + DNS_R_UPTODATE, + DNS_R_TSIGVERIFYFAILURE, + DNS_R_TSIGERRORSET, + DNS_R_SIGINVALID, + DNS_R_SIGEXPIRED, + DNS_R_SIGFUTURE, + DNS_R_KEYUNAUTHORIZED, + DNS_R_INVALIDTIME, + DNS_R_EXPECTEDTSIG, + DNS_R_UNEXPECTEDTSIG, + DNS_R_INVALIDTKEY, + DNS_R_HINT, + DNS_R_DROP, + DNS_R_NOTLOADED, + DNS_R_NCACHENXDOMAIN, + DNS_R_NCACHENXRRSET, + DNS_R_WAIT, + DNS_R_NOTVERIFIEDYET, + DNS_R_NOIDENTITY, + DNS_R_NOJOURNAL, + DNS_R_ALIAS, + DNS_R_USETCP, + DNS_R_NOVALIDSIG, + DNS_R_NOVALIDNSEC, + DNS_R_NOTINSECURE, + DNS_R_UNKNOWNSERVICE, + DNS_R_RECOVERABLE, + DNS_R_UNKNOWNOPT, + DNS_R_UNEXPECTEDID, + DNS_R_SEENINCLUDE, + DNS_R_NOTEXACT, + DNS_R_BLACKHOLED, + DNS_R_BADALG, + DNS_R_METATYPE, + DNS_R_CNAMEANDOTHER, + DNS_R_SINGLETON, + DNS_R_HINTNXRRSET, + DNS_R_NOMASTERFILE, + DNS_R_UNKNOWNPROTO, + DNS_R_CLOCKSKEW, + DNS_R_BADIXFR, + DNS_R_NOTAUTHORITATIVE, + DNS_R_NOVALIDKEY, + DNS_R_OBSOLETE, + DNS_R_FROZEN, + DNS_R_UNKNOWNFLAG, + DNS_R_EXPECTEDRESPONSE, + DNS_R_NOVALIDDS, + DNS_R_NSISADDRESS, + DNS_R_REMOTEFORMERR, + DNS_R_TRUNCATEDTCP, + DNS_R_LAME, + DNS_R_UNEXPECTEDRCODE, + DNS_R_UNEXPECTEDOPCODE, + DNS_R_CHASEDSSERVERS, + DNS_R_EMPTYNAME, + DNS_R_EMPTYWILD, + DNS_R_BADBITMAP, + DNS_R_FROMWILDCARD, + DNS_R_BADOWNERNAME, + DNS_R_BADNAME, + DNS_R_DYNAMIC, + DNS_R_UNKNOWNCOMMAND, + DNS_R_MUSTBESECURE, + DNS_R_COVERINGNSEC, + DNS_R_MXISADDRESS, + DNS_R_DUPLICATE, + DNS_R_INVALIDNSEC3, + DNS_R_NOTPRIMARY, + DNS_R_BROKENCHAIN, + DNS_R_EXPIRED, + DNS_R_NOTDYNAMIC, + DNS_R_BADEUI, + DNS_R_NTACOVERED, + DNS_R_BADCDS, + DNS_R_BADCDNSKEY, + DNS_R_OPTERR, + DNS_R_BADDNSTAP, + DNS_R_BADTSIG, + DNS_R_BADSIG0, + DNS_R_TOOMANYRECORDS, + DNS_R_VERIFYFAILURE, + DNS_R_ATZONETOP, + DNS_R_NOKEYMATCH, + DNS_R_TOOMANYKEYS, + DNS_R_KEYNOTACTIVE, + DNS_R_NSEC3ITERRANGE, + DNS_R_NSEC3SALTRANGE, + DNS_R_NSEC3BADALG, + DNS_R_NSEC3RESALT, + DNS_R_INCONSISTENTRR, + DNS_R_NOALPN, + + DST_R_UNSUPPORTEDALG, + DST_R_CRYPTOFAILURE, + /* compat */ + DST_R_OPENSSLFAILURE = DST_R_CRYPTOFAILURE, + DST_R_NOCRYPTO, + DST_R_NULLKEY, + DST_R_INVALIDPUBLICKEY, + DST_R_INVALIDPRIVATEKEY, + DST_R_WRITEERROR, + DST_R_INVALIDPARAM, + DST_R_SIGNFAILURE, + DST_R_VERIFYFAILURE, + DST_R_NOTPUBLICKEY, + DST_R_NOTPRIVATEKEY, + DST_R_KEYCANNOTCOMPUTESECRET, + DST_R_COMPUTESECRETFAILURE, + DST_R_NORANDOMNESS, + DST_R_BADKEYTYPE, + DST_R_NOENGINE, + DST_R_EXTERNALKEY, + + DNS_R_NOERROR, + DNS_R_FORMERR, + DNS_R_SERVFAIL, + DNS_R_NXDOMAIN, + DNS_R_NOTIMP, + DNS_R_REFUSED, + DNS_R_YXDOMAIN, + DNS_R_YXRRSET, + DNS_R_NXRRSET, + DNS_R_NOTAUTH, + DNS_R_NOTZONE, + DNS_R_RCODE11, + DNS_R_RCODE12, + DNS_R_RCODE13, + DNS_R_RCODE14, + DNS_R_RCODE15, + DNS_R_BADVERS, + DNS_R_BADCOOKIE = DNS_R_NOERROR + 23, + + ISCCC_R_UNKNOWNVERSION, + ISCCC_R_SYNTAX, + ISCCC_R_BADAUTH, + ISCCC_R_EXPIRED, + ISCCC_R_CLOCKSKEW, + ISCCC_R_DUPLICATE, + ISCCC_R_MAXDEPTH, + + ISC_R_NRESULTS, /*% The number of results. */ + ISC_R_MAKE_ENUM_32BIT = INT32_MAX, +} isc_result_t; + +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_LANG_ENDDECLS diff --git a/lib/isc/include/isc/rwlock.h b/lib/isc/include/isc/rwlock.h new file mode 100644 index 0000000..a0d7083 --- /dev/null +++ b/lib/isc/include/isc/rwlock.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. + */ + +#pragma once + +#include <inttypes.h> + +/*! \file isc/rwlock.h */ + +#include <isc/atomic.h> +#include <isc/condition.h> +#include <isc/lang.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 diff --git a/lib/isc/include/isc/safe.h b/lib/isc/include/isc/safe.h new file mode 100644 index 0000000..35e7759 --- /dev/null +++ b/lib/isc/include/isc/safe.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. + */ + +#pragma once + +/*! \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 diff --git a/lib/isc/include/isc/serial.h b/lib/isc/include/isc/serial.h new file mode 100644 index 0000000..b7bfa5f --- /dev/null +++ b/lib/isc/include/isc/serial.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 <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 diff --git a/lib/isc/include/isc/siphash.h b/lib/isc/include/isc/siphash.h new file mode 100644 index 0000000..69d4742 --- /dev/null +++ b/lib/isc/include/isc/siphash.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. + */ + +/*! \file isc/siphash.h */ + +#pragma once + +#include <isc/lang.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..9f3986b --- /dev/null +++ b/lib/isc/include/isc/sockaddr.h @@ -0,0 +1,248 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/sockaddr.h */ + +#include <stdbool.h> + +#include <isc/lang.h> +#include <isc/net.h> +#include <isc/types.h> + +#include <sys/un.h> + +/* + * 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; + struct sockaddr_un sunix; + } 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 diff --git a/lib/isc/include/isc/stat.h b/lib/isc/include/isc/stat.h new file mode 100644 index 0000000..fad37c9 --- /dev/null +++ b/lib/isc/include/isc/stat.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. + */ + +#pragma once + +/***** +***** 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> diff --git a/lib/isc/include/isc/stats.h b/lib/isc/include/isc/stats.h new file mode 100644 index 0000000..5bed7d5 --- /dev/null +++ b/lib/isc/include/isc/stats.h @@ -0,0 +1,253 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/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 diff --git a/lib/isc/include/isc/stdatomic.h b/lib/isc/include/isc/stdatomic.h new file mode 100644 index 0000000..7c2e21c --- /dev/null +++ b/lib/isc/include/isc/stdatomic.h @@ -0,0 +1,125 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 */ + +/* GCC 4.7.0 introduced __atomic builtins, but not the __GNUC_ATOMICS define */ +#if !defined(__GNUC_ATOMICS) && __GNUC__ == 4 && __GNUC_MINOR__ >= 7 +#define __GNUC_ATOMICS +#endif + +#if !defined(__GNUC_ATOMICS) +#error "isc/stdatomic.h does not support your compiler" +#endif /* if !defined(__GNUC_ATOMICS) */ + +typedef 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 +} 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; + +#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) + +#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/include/isc/stdio.h b/lib/isc/include/isc/stdio.h new file mode 100644 index 0000000..c8bac4d --- /dev/null +++ b/lib/isc/include/isc/stdio.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/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 diff --git a/lib/isc/include/isc/stdtime.h b/lib/isc/include/isc/stdtime.h new file mode 100644 index 0000000..45ec50f --- /dev/null +++ b/lib/isc/include/isc/stdtime.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 <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 diff --git a/lib/isc/include/isc/strerr.h b/lib/isc/include/isc/strerr.h new file mode 100644 index 0000000..563ff48 --- /dev/null +++ b/lib/isc/include/isc/strerr.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 + +/*! \file isc/strerr.h */ + +#include <isc/string.h> + +/*** + *** Default strerror_r buffer size + ***/ + +#define ISC_STRERRORSIZE 128 + +#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..fbf6129 --- /dev/null +++ b/lib/isc/include/isc/string.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. + */ + +#pragma once + +/*! \file isc/string.h */ + +#include <string.h> + +#include <isc/lang.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..2be3a8b --- /dev/null +++ b/lib/isc/include/isc/symtab.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 + +/***** +***** 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 diff --git a/lib/isc/include/isc/syslog.h b/lib/isc/include/isc/syslog.h new file mode 100644 index 0000000..021f7fa --- /dev/null +++ b/lib/isc/include/isc/syslog.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 + +/*! \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 diff --git a/lib/isc/include/isc/task.h b/lib/isc/include/isc/task.h new file mode 100644 index 0000000..75b7cdb --- /dev/null +++ b/lib/isc/include/isc/task.h @@ -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. + */ + +#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. + * + */ + +/*** + *** 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_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 save 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(). + */ + +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..cde2287 --- /dev/null +++ b/lib/isc/include/isc/taskpool.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 + +/***** +***** 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 diff --git a/lib/isc/include/isc/thread.h b/lib/isc/include/isc/thread.h new file mode 100644 index 0000000..a70e2c7 --- /dev/null +++ b/lib/isc/include/isc/thread.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 <pthread.h> +#if HAVE_THREADS_H +#include <threads.h> +#endif + +#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> + +extern thread_local size_t isc_tid_v; + +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 + +ISC_LANG_ENDDECLS diff --git a/lib/isc/include/isc/time.h b/lib/isc/include/isc/time.h new file mode 100644 index 0000000..9c9da37 --- /dev/null +++ b/lib/isc/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. + */ + +#pragma once + +/*! \file */ + +#include <inttypes.h> +#include <stdbool.h> + +#include <isc/lang.h> +#include <isc/types.h> + +enum { + MS_PER_SEC = 1000, /*%< Milliseonds per second. */ + US_PER_MS = 1000, /*%< Microseconds per millisecond. */ + US_PER_SEC = 1000 * 1000, /*%< Microseconds per second. */ + NS_PER_US = 1000, /*%< Nanoseconds per millisecond. */ + NS_PER_MS = 1000 * 1000, /*%< Nanoseconds per microsecond. */ + NS_PER_SEC = 1000 * 1000 * 1000, /*%< Nanoseconds per second. */ +}; + +/*** + *** 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. + */ + +unsigned int +isc_interval_ms(const isc_interval_t *i); +/*%< + * Returns interval 'i' expressed as a number of milliseconds. + * + * 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 diff --git a/lib/isc/include/isc/timer.h b/lib/isc/include/isc/timer.h new file mode 100644 index 0000000..815cf42 --- /dev/null +++ b/lib/isc/include/isc/timer.h @@ -0,0 +1,271 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/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. + */ + +void +isc_timermgr_poke(isc_timermgr_t *m); + +ISC_LANG_ENDDECLS diff --git a/lib/isc/include/isc/tls.h b/lib/isc/include/isc/tls.h new file mode 100644 index 0000000..efffffb --- /dev/null +++ b/lib/isc/include/isc/tls.h @@ -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. + */ + +#pragma once + +#include <isc/mem.h> +#include <isc/region.h> +#include <isc/result.h> +#include <isc/types.h> + +typedef struct ssl_ctx_st isc_tlsctx_t; +typedef struct ssl_st isc_tls_t; + +typedef struct x509_store_st isc_tls_cert_store_t; + +void +isc_tlsctx_free(isc_tlsctx_t **ctpx); +/*%< + * Free a TLS client or server context. + * + * Requires: + *\li 'ctxp' != NULL and '*ctxp' != NULL. + */ + +void +isc_tlsctx_attach(isc_tlsctx_t *src, isc_tlsctx_t **ptarget); +/*%< + * Attach to the TLS context. + * + * Requires: + *\li 'src' != NULL; + *\li 'ptarget' != NULL; + *\li '*ptarget' == NULL. + */ + +isc_result_t +isc_tlsctx_createserver(const char *keyfile, const char *certfile, + isc_tlsctx_t **ctxp); +/*%< + * Set up a TLS server context, using the key and certificate specified in + * 'keyfile' and 'certfile', or a self-generated ephemeral key and + * certificdate if both 'keyfile' and 'certfile' are NULL. + * + * Requires: + *\li 'ctxp' != NULL and '*ctxp' == NULL. + *\li 'keyfile' and 'certfile' are either both NULL or both non-NULL. + */ + +isc_result_t +isc_tlsctx_createclient(isc_tlsctx_t **ctxp); +/*%< + * Set up a TLS client context. + * + * Requires: + *\li 'ctxp' != NULL and '*ctxp' == NULL. + */ + +isc_result_t +isc_tlsctx_load_certificate(isc_tlsctx_t *ctx, const char *keyfile, + const char *certfile); +/*%< + * Load a TLS certificate into a TLS context. + * + * Requires: + *\li 'ctx' != NULL; + *\li 'keyfile' and 'certfile' are both non-NULL. + */ + +typedef enum isc_tls_protocol_version { + /* these must be the powers of two */ + ISC_TLS_PROTO_VER_1_2 = 1 << 0, + ISC_TLS_PROTO_VER_1_3 = 1 << 1, + ISC_TLS_PROTO_VER_UNDEFINED, +} isc_tls_protocol_version_t; + +void +isc_tlsctx_set_protocols(isc_tlsctx_t *ctx, const uint32_t tls_versions); +/*%< + * Sets the supported TLS protocol versions via the 'tls_versions' bit + * set argument (see `isc_tls_protocol_version_t` enum for the + * expected values). + * + * Requires: + *\li 'ctx' != NULL; + *\li 'tls_versions' != 0. + */ + +bool +isc_tls_protocol_supported(const isc_tls_protocol_version_t tls_ver); +/*%< + * Check in runtime that the specified TLS protocol versions is supported. + */ + +isc_tls_protocol_version_t +isc_tls_protocol_name_to_version(const char *name); +/*%< + * Convert the protocol version string into the version of + * 'isc_tls_protocol_version_t' type. + * Requires: + *\li 'name' != NULL. + */ + +bool +isc_tlsctx_load_dhparams(isc_tlsctx_t *ctx, const char *dhparams_file); +/*%< + * Load Diffie-Hellman parameters file and apply it to the given TLS context + * 'ctx'. + * + * Requires: + * \li 'ctx' != NULL; + * \li 'dhaprams_file' a valid pointer to a non empty string. + */ + +bool +isc_tls_cipherlist_valid(const char *cipherlist); +/*%< + * Check if cipher list string is valid. + * + * Requires: + * \li 'cipherlist' a valid pointer to a non empty string. + */ + +void +isc_tlsctx_set_cipherlist(isc_tlsctx_t *ctx, const char *cipherlist); +/*%< + * Set cipher list string for on the given TLS context 'ctx'. + * + * Requires: + * \li 'ctx' != NULL; + * \li 'cipherlist' a valid pointer to a non empty string. + */ + +void +isc_tlsctx_prefer_server_ciphers(isc_tlsctx_t *ctx, const bool prefer); +/*%< + * Make the given TLS context 'ctx' to prefer or to not prefer + * server side ciphers during the ciphers negotiation. + * + * Requires: + * \li 'ctx' != NULL. + */ + +void +isc_tlsctx_session_tickets(isc_tlsctx_t *ctx, const bool use); +/*%< + * Enable/Disable stateless session resumptions tickets on the given + * TLS context 'ctx' (see RFC5077). + * + * Requires: + * \li 'ctx' != NULL. + */ + +isc_tls_t * +isc_tls_create(isc_tlsctx_t *ctx); +/*%< + * Set up the structure to hold data for a new TLS connection. + * + * Requires: + *\li 'ctx' != NULL. + */ + +void +isc_tls_free(isc_tls_t **tlsp); +/*%< + * Free a TLS structure. + * + * Requires: + *\li 'tlsp' != NULL and '*tlsp' != NULL. + */ + +const char * +isc_tls_verify_peer_result_string(isc_tls_t *tls); +/*%< + * Return a user readable description of a remote peer's certificate + * validation. + * + * Requires: + *\li 'tls' != NULL. + */ + +#if HAVE_LIBNGHTTP2 +void +isc_tlsctx_enable_http2client_alpn(isc_tlsctx_t *ctx); +void +isc_tlsctx_enable_http2server_alpn(isc_tlsctx_t *ctx); +/*%< + * Enable HTTP/2 Application Layer Protocol Negotation for 'ctx'. + * + * Requires: + *\li 'ctx' is not NULL. + */ +#endif /* HAVE_LIBNGHTTP2 */ + +void +isc_tls_get_selected_alpn(isc_tls_t *tls, const unsigned char **alpn, + unsigned int *alpnlen); + +#define ISC_TLS_DOT_PROTO_ALPN_ID "dot" +#define ISC_TLS_DOT_PROTO_ALPN_ID_LEN 3 + +void +isc_tlsctx_enable_dot_client_alpn(isc_tlsctx_t *ctx); +void +isc_tlsctx_enable_dot_server_alpn(isc_tlsctx_t *ctx); +/*%< + * Enable DoT Application Layer Protocol Negotation for 'ctx'. + * + * Requires: + *\li 'ctx' is not NULL. + */ + +isc_result_t +isc_tlsctx_enable_peer_verification(isc_tlsctx_t *ctx, const bool is_server, + isc_tls_cert_store_t *store, + const char *hostname, + bool hostname_ignore_subject); +/*%< + * Enable peer certificate and, optionally, hostname (for client contexts) + * verification. + * + * Requires: + *\li 'ctx' is not NULL; + *\li 'store' is not NULL. + */ + +isc_result_t +isc_tlsctx_load_client_ca_names(isc_tlsctx_t *ctx, const char *ca_bundle_file); +/*%< + * Load the list of CA-certificate names from a CA-bundle file to + * send by the server to a client when requesting a peer certificate. + * Usually used in conjunction with + * isc_tlsctx_enable_peer_validation(). + * + * Requires: + *\li 'ctx' is not NULL; + *\li 'ca_bundle_file' is not NULL. + */ + +isc_result_t +isc_tls_cert_store_create(const char *ca_bundle_filename, + isc_tls_cert_store_t **pstore); +/*%< + * Create X509 certificate store. The 'ca_bundle_filename' might be + * 'NULL' or an empty string, which means use the default system wide + * bundle/directory. + * + * Requires: + *\li 'pstore' is a valid pointer to a pointer containing 'NULL'. + */ + +void +isc_tls_cert_store_free(isc_tls_cert_store_t **pstore); +/*%< + * Free X509 certificate store. + * + * Requires: + *\li 'pstore' is a valid pointer to a pointer containing a non-'NULL' value. + */ + +typedef struct isc_tlsctx_client_session_cache isc_tlsctx_client_session_cache_t; +/*%< + * TLS client session cache is an object which allows efficient + * storing and retrieval of previously saved TLS sessions so that they + * can be resumed. This object is supposed to be a foundation for + * implementing TLS session resumption - a standard technique to + * reduce the cost of re-establishing a connection to the remote + * server endpoint. + * + * OpenSSL does server-side TLS session caching transparently by + * default. However, on the client-side, a TLS session to resume must + * be manually specified when establishing the TLS connection. The TLS + * client session cache is precisely the foundation for that. + * + * The cache has been designed to have the following characteristics: + * + * - Fixed maximal number of entries to not keep too many obsolete + * sessions within the cache; + * + * - The cache is indexed by character string keys. Each string is a + * key representing a remote endpoint (unique remote endpoint name, + * e.g. combination of the remote IP address and port); + * + * - Least Recently Used (LRU) cache replacement policy while allowing + * easy removal of obsolete entries; + * + * - Ability to store multiple TLS sessions associated with the given + * key (remote endpoint name). This characteristic is important if + * multiple connections to the same remote server can be established; + * + * - Ability to efficiently retrieve the most recent TLS sessions + * associated with the key (remote endpoint name). + * + * Because of these characteristics, the cache will end up having the + * necessary amount of resumable TLS session parameters to the most + * used remote endpoints ("hot entries") after a short period of + * initial use ("warmup"). + * + * Attempting to resume TLS sessions is an optimisation, which is not + * guaranteed to succeed because it requires the same session to be + * present in the server session caches. If it is not the case, the + * usual handshake procedure takes place. However, when session + * resumption is successful, it reduces the amount of the + * computational resources required as well as the amount of data to + * be transmitted when (re)establishing the connection. Also, it + * reduces round trip time (by reducing the number of packets to + * transmit). + * + * This optimisation is important in the context of DNS because the + * amount of data within the full handshake messages might be + * comparable to or surpass the size of a typical DNS message. + */ + +void +isc_tlsctx_client_session_cache_create( + isc_mem_t *mctx, isc_tlsctx_t *ctx, const size_t max_entries, + isc_tlsctx_client_session_cache_t **cachep); +/*%< + * Create a new TLS client session cache object. + * + * Requires: + *\li 'mctx' is a valid memory context object; + *\li 'ctx' is a valid TLS context object; + *\li 'max_entries' is a positive number; + *\li 'cachep' is a valid pointer to a pointer which must be equal to NULL. + */ + +void +isc_tlsctx_client_session_cache_attach( + isc_tlsctx_client_session_cache_t *source, + isc_tlsctx_client_session_cache_t **targetp); +/*%< + * Create a reference to the TLS client session cache object. + * + * Requires: + *\li 'source' is a valid TLS client session cache object; + *\li 'targetp' is a valid pointer to a pointer which must equal NULL. + */ + +void +isc_tlsctx_client_session_cache_detach( + isc_tlsctx_client_session_cache_t **cachep); +/*%< + * Remove a reference to the TLS client session cache object. + * + * Requires: + *\li 'cachep' is a pointer to a pointer to a valid TLS client session cache + *object. + */ + +void +isc_tlsctx_client_session_cache_keep(isc_tlsctx_client_session_cache_t *cache, + char *remote_peer_name, isc_tls_t *tls); +/*%< + * Add a resumable TLS client session within 'tls' to the TLS client + * session cache object 'cache' and associate it with + * 'remote_server_name' string. Also, the oldest entry from the cache + * might get removed if the cache is full. + * + * Requires: + *\li 'cache' is a pointer to a valid TLS client session cache object; + *\li 'remote_peer_name' is a pointer to a non empty character string; + *\li 'tls' is a valid, non-'NULL' pointer to a TLS connection state object. + */ + +void +isc_tlsctx_client_session_cache_keep_sockaddr( + isc_tlsctx_client_session_cache_t *cache, isc_sockaddr_t *remote_peer, + isc_tls_t *tls); +/*%< + * The same as 'isc_tlsctx_client_session_cache_keep()', but uses a + * 'isc_sockaddr_t' as a key, instead of a character string. + * + * Requires: + *\li 'remote_peer' is a valid, non-'NULL', pointer to an 'isc_sockaddr_t' + *object. + */ + +void +isc_tlsctx_client_session_cache_reuse(isc_tlsctx_client_session_cache_t *cache, + char *remote_server_name, isc_tls_t *tls); +/*% + * Try to restore a previously stored TLS session denoted by a remote + * server name as a key ('remote_server_name') into the given TLS + * connection state object ('tls'). The successfully restored session + * gets removed from the cache. + * + * Requires: + *\li 'cache' is a pointer to a valid TLS client session cache object; + *\li 'remote_peer_name' is a pointer to a non empty character string; + *\li 'tls' is a valid, non-'NULL' pointer to a TLS connection state object. + */ + +void +isc_tlsctx_client_session_cache_reuse_sockaddr( + isc_tlsctx_client_session_cache_t *cache, isc_sockaddr_t *remote_peer, + isc_tls_t *tls); +/*%< + * The same as 'isc_tlsctx_client_session_cache_reuse()', but uses a + * 'isc_sockaddr_t' as a key, instead of a character string. + * + * Requires: + *\li 'remote_peer' is a valid, non-'NULL' pointer to an 'isc_sockaddr_t' + *object. + */ + +const isc_tlsctx_t * +isc_tlsctx_client_session_cache_getctx(isc_tlsctx_client_session_cache_t *cache); +/*%< + * Returns a TLS context associated with the given TLS client + * session cache object. The function is intended to be used to + * implement the sanity checks ('INSIST()'s and 'REQUIRE()'s). + * + * Requires: + *\li 'cache' is a pointer to a valid TLS client session cache object. + */ + +#define ISC_TLSCTX_CLIENT_SESSION_CACHE_DEFAULT_SIZE (150) +/*%< + * The default maximum size of a TLS client session cache. The value + * should be large enough to hold enough sessions to successfully + * re-establish connections to the most remote TLS servers, but not + * too big to avoid keeping too much obsolete sessions. + */ + +typedef struct isc_tlsctx_cache isc_tlsctx_cache_t; +/*%< + * The TLS context cache is an object which allows retrieving a + * previously created TLS context based on the following tuple: + * + * 1. The name of a TLS entry, as defined in the configuration file; + * 2. A transport type. Currently, only TLS (DoT) and HTTPS (DoH) are + * supported; + * 3. An IP address family (AF_INET or AF_INET6). + * + * There are multiple uses for this object: + * + * First, it allows reuse of client-side contexts during zone transfers. + * That, in turn, allows use of session caches associated with these + * contexts, which enables TLS session resumption, making establishment + * of XoT connections faster and computationally cheaper. + * + * Second, it can be extended to be used as storage for TLS context related + * data, as defined in 'tls' statements in the configuration file (for + * example, CA-bundle intermediate certificate storage, client-side contexts + * with pre-loaded certificates in a case of Mutual TLS, etc). This will + * be used to implement Strict/Mutual TLS. + * + * Third, it avoids creating an excessive number of server-side TLS + * contexts, which might help to reduce the number of contexts + * created during server initialisation and reconfiguration. + */ + +typedef enum { + isc_tlsctx_cache_none = 0, + isc_tlsctx_cache_tls, + isc_tlsctx_cache_https, + isc_tlsctx_cache_count +} isc_tlsctx_cache_transport_t; +/*%< TLS context cache transport type values. */ + +void +isc_tlsctx_cache_create(isc_mem_t *mctx, isc_tlsctx_cache_t **cachep); +/*%< + * Create a new TLS context cache object. + * + * Requires: + *\li 'mctx' is a valid memory context; + *\li 'cachep' is a valid pointer to a pointer which must be equal to NULL. + */ + +void +isc_tlsctx_cache_attach(isc_tlsctx_cache_t *source, + isc_tlsctx_cache_t **targetp); +/*%< + * Create a reference to the TLS context cache object. + * + * Requires: + *\li 'source' is a valid TLS context cache object; + *\li 'targetp' is a valid pointer to a pointer which must equal NULL. + */ + +void +isc_tlsctx_cache_detach(isc_tlsctx_cache_t **cachep); +/*%< + * Remove a reference to the TLS context cache object. + * + * Requires: + *\li 'cachep' is a pointer to a pointer to a valid TLS + * context cache object. + */ + +isc_result_t +isc_tlsctx_cache_add( + isc_tlsctx_cache_t *cache, const char *name, + const isc_tlsctx_cache_transport_t transport, const uint16_t family, + isc_tlsctx_t *ctx, isc_tls_cert_store_t *store, + isc_tlsctx_client_session_cache_t *client_sess_cache, + isc_tlsctx_t **pfound, isc_tls_cert_store_t **pfound_store, + isc_tlsctx_client_session_cache_t **pfound_client_sess_cache); +/*%< + * + * Add a new TLS context and its associated data to the TLS context + * cache. 'pfound' is an optional pointer, which can be used to + * retrieve an already existing TLS context object in a case it + * exists. + * + * The passed certificates store object ('store') possession is + * transferred to the cache object in a case of success. In some cases + * it might be destroyed immediately upon the call completion. + * + * The possession of the passed TLS client session cache + * ('client_sess_cache') is also transferred to the cache object in a + * case of success. + * + * Requires: + *\li 'cache' is a valid pointer to a TLS context cache object; + *\li 'name' is a valid pointer to a non-empty string; + *\li 'transport' is a valid transport identifier (currently only + * TLS/DoT and HTTPS/DoH are supported); + *\li 'family' - either 'AF_INET' or 'AF_INET6'; + *\li 'ctx' - a valid pointer to a valid TLS context object; + *\li 'store' - a valid pointer to a valid TLS certificates store object or + * 'NULL'; + *\li 'client_sess_cache' - a valid pointer to a valid TLS client sessions + *cache object or 'NULL. + * + * Returns: + *\li #ISC_R_EXISTS - node of the same key already exists; + *\li #ISC_R_SUCCESS - the new entry has been added successfully. + */ + +isc_result_t +isc_tlsctx_cache_find( + isc_tlsctx_cache_t *cache, const char *name, + const isc_tlsctx_cache_transport_t transport, const uint16_t family, + isc_tlsctx_t **pctx, isc_tls_cert_store_t **pstore, + isc_tlsctx_client_session_cache_t **pfound_client_sess_cache); +/*%< + * Look up a TLS context and its associated data in the TLS context cache. + * + * Requires: + *\li 'cache' is a valid pointer to a TLS context cache object; + *\li 'name' is a valid pointer to a non empty string; + *\li 'transport' - a valid transport identifier (currently only + * TLS/DoT and HTTPS/DoH are supported; + *\li 'family' - either 'AF_INET' or 'AF_INET6'; + *\li 'pctx' - a valid pointer to a non-NULL pointer; + *\li 'pstore' - a valid pointer to a non-NULL pointer or 'NULL'. + *\li 'pfound_client_sess_cache' - a valid pointer to a non-NULL pointer or + *'NULL'. + * + * Returns: + *\li #ISC_R_SUCCESS - the context has been found; + *\li #ISC_R_NOTFOUND - the context has not been found. In such a case, + * 'pstore' still might get initialised as there is one to many + * relation between stores and contexts. + */ + +void +isc_tlsctx_set_random_session_id_context(isc_tlsctx_t *ctx); +/*%< + * Set context within which session can be reused to a randomly + * generated value. This one is used for TLS session resumption using + * session IDs. See OpenSSL documentation for + * 'SSL_CTX_set_session_id_context()'. + * + * It might be worth noting that usually session ID contexts are kept + * static for an application and particular certificate + * combination. However, for the cases when exporting server side TLS + * session cache to/loading from external memory is not required, we + * might use random IDs just fine. See, + * e.g. 'ngx_ssl_session_id_context()' in NGINX for an example of how + * a session ID might be obtained. + * + * Requires: + *\li 'ctx' - a valid non-NULL pointer; + */ diff --git a/lib/isc/include/isc/tm.h b/lib/isc/include/isc/tm.h new file mode 100644 index 0000000..58feec4 --- /dev/null +++ b/lib/isc/include/isc/tm.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 + +/*! \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 diff --git a/lib/isc/include/isc/types.h b/lib/isc/include/isc/types.h new file mode 100644 index 0000000..7e0fc02 --- /dev/null +++ b/lib/isc/include/isc/types.h @@ -0,0 +1,133 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/result.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_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 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 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_netprefix isc_netprefix_t; /*%< Net Prefix */ +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 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_stats isc_stats_t; /*%< Statistics */ +typedef int_fast64_t isc_statscounter_t; +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 */ + +#if HAVE_LIBNGHTTP2 +typedef struct isc_nm_http_endpoints isc_nm_http_endpoints_t; +/*%< HTTP endpoints set */ +#endif /* HAVE_LIBNGHTTP2 */ + +typedef void (*isc_taskaction_t)(isc_task_t *, isc_event_t *); + +/*% 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; + +typedef enum isc_nmsocket_type { + isc_nm_nonesocket = 0, + isc_nm_udpsocket = 1 << 1, + isc_nm_tcpsocket = 1 << 2, + isc_nm_tcpdnssocket = 1 << 3, + isc_nm_tlssocket = 1 << 4, + isc_nm_tlsdnssocket = 1 << 5, + isc_nm_httpsocket = 1 << 6, + isc_nm_maxsocket, + + isc_nm_udplistener, /* Aggregate of nm_udpsocks */ + isc_nm_tcplistener, + isc_nm_tlslistener, + isc_nm_tcpdnslistener, + isc_nm_tlsdnslistener, + isc_nm_httplistener +} isc_nmsocket_type; + +typedef isc_nmsocket_type isc_nmsocket_type_t; diff --git a/lib/isc/include/isc/url.h b/lib/isc/include/isc/url.h new file mode 100644 index 0000000..d3b935b --- /dev/null +++ b/lib/isc/include/isc/url.h @@ -0,0 +1,85 @@ +/* + * 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. + */ + +#pragma once + +#include <stdbool.h> +#include <stddef.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..922661b --- /dev/null +++ b/lib/isc/include/isc/util.h @@ -0,0 +1,377 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 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)) +#else +#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 + */ + +/* 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) */ + +/* Courtesy of Joseph Quinsey: https://godbolt.org/z/K9RvWS */ +#define TOKENPASTE(a, b) a##b /* "##" is the "Token Pasting Operator" */ +#define EXPAND_THEN_PASTE(a, b) TOKENPASTE(a, b) /* expand then paste */ +#define STATIC_ASSERT(x, msg) \ + enum { EXPAND_THEN_PASTE(ASSERT_line_, __LINE__) = 1 / ((msg) && (x)) } +#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 */ + +/* + * 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() + +#endif /* UNIT_TESTING */ + +/* + * Errors + */ +#include <isc/error.h> /* Contractual promise. */ +#include <isc/strerr.h> /* for ISC_STRERRORSIZE */ + +#define UNEXPECTED_ERROR(...) \ + isc_error_unexpected(__FILE__, __LINE__, __func__, __VA_ARGS__) + +#define FATAL_ERROR(...) \ + isc_error_fatal(__FILE__, __LINE__, __func__, __VA_ARGS__) + +#define REPORT_SYSERROR(report, err, fmt, ...) \ + { \ + char strerr[ISC_STRERRORSIZE]; \ + strerror_r(err, strerr, sizeof(strerr)); \ + report(__FILE__, __LINE__, __func__, fmt ": %s (%d)", \ + ##__VA_ARGS__, strerr, err); \ + } + +#define UNEXPECTED_SYSERROR(err, ...) \ + REPORT_SYSERROR(isc_error_unexpected, err, __VA_ARGS__) + +#define FATAL_SYSERROR(err, ...) \ + REPORT_SYSERROR(isc_error_fatal, err, __VA_ARGS__) + +#ifdef UNIT_TESTING + +#define RUNTIME_CHECK(cond) \ + ((cond) ? (void)0 \ + : (mock_assert(0, #cond, __FILE__, __LINE__), abort())) + +#else /* UNIT_TESTING */ + +#define RUNTIME_CHECK(cond) \ + ((cond) ? (void)0 : FATAL_ERROR("RUNTIME_CHECK(%s) failed", #cond)) + +#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__ */ + +/*% + * Misc + */ +#include <isc/deprecated.h> + +/*% + * Swap + */ +#define ISC_SWAP(a, b) \ + { \ + typeof(a) __tmp_swap = a; \ + a = b; \ + b = __tmp_swap; \ + } diff --git a/lib/isc/interfaceiter.c b/lib/isc/interfaceiter.c new file mode 100644 index 0000000..566c5bd --- /dev/null +++ b/lib/isc/interfaceiter.c @@ -0,0 +1,518 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 <ifaddrs.h> +#include <inttypes.h> +#include <stdbool.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 */ + +/*% 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("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; +} + +#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/iterated_hash.c b/lib/isc/iterated_hash.c new file mode 100644 index 0000000..ad62381 --- /dev/null +++ b/lib/isc/iterated_hash.c @@ -0,0 +1,139 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/opensslv.h> + +#include <isc/iterated_hash.h> +#include <isc/util.h> + +#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000 + +#include <openssl/sha.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) { + ERR_clear_error(); + return (0); + } + + if (SHA1_Update(&ctx, buf, len) != 1) { + ERR_clear_error(); + return (0); + } + + if (SHA1_Update(&ctx, salt, saltlength) != 1) { + ERR_clear_error(); + return (0); + } + + if (SHA1_Final(out, &ctx) != 1) { + ERR_clear_error(); + return (0); + } + + buf = out; + len = SHA_DIGEST_LENGTH; + } while (n++ < iterations); + + return (SHA_DIGEST_LENGTH); +} + +#else + +#include <openssl/evp.h> + +#include <isc/md.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; + unsigned int outlength = 0; + const unsigned char *buf; + EVP_MD_CTX *ctx; + ; + EVP_MD *md; + + if (hashalg != 1) { + return (0); + } + + ctx = EVP_MD_CTX_new(); + RUNTIME_CHECK(ctx != NULL); + md = EVP_MD_fetch(NULL, "SHA1", NULL); + RUNTIME_CHECK(md != NULL); + + buf = in; + len = inlength; + + do { + if (EVP_DigestInit_ex(ctx, md, NULL) != 1) { + goto fail; + } + + if (EVP_DigestUpdate(ctx, buf, len) != 1) { + goto fail; + } + + if (EVP_DigestUpdate(ctx, salt, saltlength) != 1) { + goto fail; + } + + if (EVP_DigestFinal_ex(ctx, out, &outlength) != 1) { + goto fail; + } + + buf = out; + len = outlength; + } while (n++ < iterations); + + EVP_MD_CTX_free(ctx); + EVP_MD_free(md); + + return (outlength); + +fail: + EVP_MD_CTX_free(ctx); + EVP_MD_free(md); + ERR_clear_error(); + return (0); +} + +#endif diff --git a/lib/isc/jemalloc_shim.h b/lib/isc/jemalloc_shim.h new file mode 100644 index 0000000..493bf5f --- /dev/null +++ b/lib/isc/jemalloc_shim.h @@ -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. + */ + +#pragma once + +#if !defined(HAVE_JEMALLOC) + +#include <stddef.h> + +#include <isc/util.h> + +const char *malloc_conf = NULL; + +/* Without jemalloc, isc_mem_get_align() is equal to isc_mem_get() */ +#define MALLOCX_ALIGN(a) (a & 0) /* Clear the flag */ +#define MALLOCX_ZERO ((int)0x40) +#define MALLOCX_TCACHE_NONE (0) +#define MALLOCX_ARENA(a) (0) + +#if defined(HAVE_MALLOC_SIZE) || defined(HAVE_MALLOC_USABLE_SIZE) + +#include <stdlib.h> + +static inline void * +mallocx(size_t size, int flags) { + UNUSED(flags); + + return (malloc(size)); +} + +static inline void +sdallocx(void *ptr, size_t size, int flags) { + UNUSED(size); + UNUSED(flags); + + free(ptr); +} + +static inline void * +rallocx(void *ptr, size_t size, int flags) { + UNUSED(flags); + REQUIRE(size != 0); + + return (realloc(ptr, size)); +} + +#ifdef HAVE_MALLOC_SIZE + +#include <malloc/malloc.h> + +static inline size_t +sallocx(void *ptr, int flags) { + UNUSED(flags); + + return (malloc_size(ptr)); +} + +#elif HAVE_MALLOC_USABLE_SIZE + +#ifdef __DragonFly__ +/* + * On DragonFly BSD 'man 3 malloc' advises us to include the following + * header to have access to malloc_usable_size(). + */ +#include <malloc_np.h> +#else +#include <malloc.h> +#endif + +static inline size_t +sallocx(void *ptr, int flags) { + UNUSED(flags); + + return (malloc_usable_size(ptr)); +} + +#endif /* HAVE_MALLOC_SIZE */ + +#else /* defined(HAVE_MALLOC_SIZE) || defined (HAVE_MALLOC_USABLE_SIZE) */ + +#include <stdlib.h> + +typedef union { + size_t size; + max_align_t __alignment; +} size_info; + +static inline void * +mallocx(size_t size, int flags) { + void *ptr = NULL; + + UNUSED(flags); + + size_info *si = malloc(size + sizeof(*si)); + INSIST(si != NULL); + + si->size = size; + ptr = &si[1]; + + return (ptr); +} + +static inline void +sdallocx(void *ptr, size_t size, int flags) { + size_info *si = &(((size_info *)ptr)[-1]); + + UNUSED(size); + UNUSED(flags); + + free(si); +} + +static inline size_t +sallocx(void *ptr, int flags) { + size_info *si = &(((size_info *)ptr)[-1]); + + UNUSED(flags); + + return (si[0].size); +} + +static inline void * +rallocx(void *ptr, size_t size, int flags) { + size_info *si = &(((size_info *)ptr)[-1]); + + UNUSED(flags); + + si = realloc(si, size + sizeof(*si)); + INSIST(si != NULL); + + si->size = size; + ptr = &si[1]; + + return (ptr); +} + +#endif /* defined(HAVE_MALLOC_SIZE) || defined (HAVE_MALLOC_USABLE_SIZE) */ + +#endif /* !defined(HAVE_JEMALLOC) */ diff --git a/lib/isc/lex.c b/lib/isc/lex.c new file mode 100644 index 0000000..19ed4a7 --- /dev/null +++ b/lib/isc/lex.c @@ -0,0 +1,1136 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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> + +#include "errno2result.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__errno2result( + errno); + 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("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/lib.c b/lib/isc/lib.c new file mode 100644 index 0000000..343fedf --- /dev/null +++ b/lib/isc/lib.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 <isc/mem.h> +#include <isc/os.h> +#include <isc/tls.h> +#include <isc/util.h> + +#include "config.h" +#include "mem_p.h" +#include "os_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__initialize(void) ISC_CONSTRUCTOR; +void +isc__shutdown(void) ISC_DESTRUCTOR; + +void +isc__initialize(void) { + isc__os_initialize(); + isc__mem_initialize(); + isc__tls_initialize(); + isc__trampoline_initialize(); + (void)isc_os_ncpus(); +} + +void +isc__shutdown(void) { + isc__trampoline_shutdown(); + isc__tls_shutdown(); + isc__mem_shutdown(); + isc__os_shutdown(); +} diff --git a/lib/isc/log.c b/lib/isc/log.c new file mode 100644 index 0000000..523fd2d --- /dev/null +++ b/lib/isc/log.c @@ -0,0 +1,1909 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 <unistd.h> + +#include <isc/atomic.h> +#include <isc/dir.h> +#include <isc/errno.h> +#include <isc/file.h> +#include <isc/log.h> +#include <isc/magic.h> +#include <isc/mem.h> +#include <isc/print.h> +#include <isc/rwlock.h> +#include <isc/stat.h> +#include <isc/stdio.h> +#include <isc/string.h> +#include <isc/thread.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); + +static thread_local bool forcelog = false; + +/* + * 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[]. + */ +isc_logcategory_t isc_categories[] = { { "default", 0 }, /* "default + must come + first. */ + { "general", 0 }, + { "sslkeylog", 0 }, + { NULL, 0 } }; + +/*! + * See above comment for categories, and apply it to modules. + */ +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. + */ +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 *digit_end; + char dirbuf[PATH_MAX + 1]; + const char *bname; + const char *dirname = "."; + int version, greatest = -1; + isc_dir_t dir; + isc_result_t result; + size_t bnamelen; + + bname = strrchr(file->name, '/'); + if (bname != NULL) { + /* + * Copy the complete file name to dirbuf. + */ + size_t len = strlcpy(dirbuf, file->name, sizeof(dirbuf)); + if (len >= sizeof(dirbuf)) { + result = ISC_R_NOSPACE; + syslog(LOG_ERR, "unable to remove log files: %s", + isc_result_totext(result)); + return (result); + } + + /* + * Truncate after trailing '/' so the code works for + * files in the root directory. + */ + bname++; + dirbuf[bname - file->name] = '\0'; + dirname = dirbuf; + } else { + bname = file->name; + } + bnamelen = strlen(bname); + + isc_dir_init(&dir); + result = isc_dir_open(&dir, dirname); + + /* + * 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) { + int n = unlinkat(dirfd(dir.handle), + dir.entry.name, 0); + if (n < 0) { + result = isc_errno_toresult(errno); + if (result != ISC_R_SUCCESS && + result != ISC_R_FILENOTFOUND) + { + syslog(LOG_ERR, + "unable to remove log " + "file '%s%s': %s", + bname == file->name + ? "" + : dirname, + 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, const 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) { + char *digit_end; + char dirbuf[PATH_MAX + 1]; + const char *bname; + const char *dirname = "."; + int64_t version, last = INT64_MAX; + isc_dir_t dir; + isc_result_t result; + size_t bnamelen; + + bname = strrchr(file->name, '/'); + if (bname != NULL) { + /* + * Copy the complete file name to dirbuf. + */ + size_t len = strlcpy(dirbuf, file->name, sizeof(dirbuf)); + if (len >= sizeof(dirbuf)) { + result = ISC_R_NOSPACE; + syslog(LOG_ERR, "unable to remove log files: %s", + isc_result_totext(result)); + return (result); + } + + /* + * Truncate after trailing '/' so the code works for + * files in the root directory. + */ + bname++; + dirbuf[bname - file->name] = '\0'; + dirname = dirbuf; + } else { + bname = file->name; + } + bnamelen = strlen(bname); + + isc_dir_init(&dir); + result = isc_dir_open(&dir, dirname); + + /* + * Return if the directory open failed. + */ + if (result != ISC_R_SUCCESS) { + return (result); + } + + last = last_to_keep(versions, &dir, bname, bnamelen); + + 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 = strtoull(&dir.entry.name[bnamelen + 1], + &digit_end, 10); + /* + * Remove any backup files that exceed versions. + */ + if (*digit_end == '\0' && version < last) { + int n = unlinkat(dirfd(dir.handle), + dir.entry.name, 0); + if (n < 0) { + result = isc_errno_toresult(errno); + if (result != ISC_R_SUCCESS && + result != ISC_R_FILENOTFOUND) + { + syslog(LOG_ERR, + "unable to remove log " + "file '%s%s': %s", + bname == file->name + ? "" + : dirname, + 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); + } + if (forcelog) { + return (true); + } + + 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; + int_fast32_t dlevel; + 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); + + if (!forcelog) { + 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); +} + +void +isc_log_setforcelog(bool v) { + forcelog = v; +} diff --git a/lib/isc/managers.c b/lib/isc/managers.c new file mode 100644 index 0000000..5e9a6c3 --- /dev/null +++ b/lib/isc/managers.c @@ -0,0 +1,117 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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" +#include "timer_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_timermgr_t **timermgrp) { + isc_result_t result; + isc_nm_t *netmgr = NULL; + isc_taskmgr_t *taskmgr = NULL; + isc_timermgr_t *timermgr = 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("isc_taskmgr_create() failed: %s", + isc_result_totext(result)); + goto fail; + } + *taskmgrp = taskmgr; + } + + REQUIRE(timermgrp == NULL || *timermgrp == NULL); + if (timermgrp != NULL) { + result = isc__timermgr_create(mctx, &timermgr); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR("isc_timermgr_create() failed: %s", + isc_result_totext(result)); + goto fail; + } + *timermgrp = timermgr; + } + + return (ISC_R_SUCCESS); +fail: + isc_managers_destroy(netmgrp, taskmgrp, timermgrp); + + return (result); +} + +void +isc_managers_destroy(isc_nm_t **netmgrp, isc_taskmgr_t **taskmgrp, + isc_timermgr_t **timermgrp) { + /* + * 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); + } + + /* + * 5. Clean up the remaining managers. + */ + if (timermgrp != NULL) { + INSIST(*timermgrp != NULL); + isc__timermgr_destroy(timermgrp); + } +} diff --git a/lib/isc/md.c b/lib/isc/md.c new file mode 100644 index 0000000..a8c6312 --- /dev/null +++ b/lib/isc/md.c @@ -0,0 +1,185 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 (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) { + ERR_clear_error(); + 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) { + ERR_clear_error(); + 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 (buf == NULL || len == 0) { + return (ISC_R_SUCCESS); + } + + if (EVP_DigestUpdate(md, buf, len) != 1) { + ERR_clear_error(); + 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) { + ERR_clear_error(); + 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_get0_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) { \ + const isc_md_type_t *value = EVP_##alg(); \ + if (value == NULL) { \ + ERR_clear_error(); \ + } \ + return (value); \ + } + +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..61a66f6 --- /dev/null +++ b/lib/isc/mem.c @@ -0,0 +1,1908 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/align.h> +#include <isc/hash.h> +#include <isc/magic.h> +#include <isc/mem.h> +#include <isc/mutex.h> +#include <isc/once.h> +#include <isc/os.h> +#include <isc/print.h> +#include <isc/refcount.h> +#include <isc/string.h> +#include <isc/types.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 */ + +/* On DragonFly BSD the header does not provide jemalloc API */ +#if defined(HAVE_MALLOC_NP_H) && !defined(__DragonFly__) +#include <malloc_np.h> +#define JEMALLOC_API_SUPPORTED 1 +#elif defined(HAVE_JEMALLOC) +#include <jemalloc/jemalloc.h> +#define JEMALLOC_API_SUPPORTED 1 + +#if JEMALLOC_VERSION_MAJOR < 4 +#define sdallocx(ptr, size, flags) dallocx(ptr, flags) +#define MALLOCX_TCACHE_NONE (0) +#endif /* JEMALLOC_VERSION_MAJOR < 4 */ + +#else +#include "jemalloc_shim.h" +#endif + +#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 */ +unsigned int isc_mem_debugging = ISC_MEM_DEBUGGING; +unsigned int isc_mem_defaultflags = ISC_MEMFLAG_DEFAULT; + +#define ISC_MEM_ILLEGAL_ARENA (UINT_MAX) + +/* + * Constants. + */ + +#define ZERO_ALLOCATION_SIZE sizeof(void *) +#define ALIGNMENT 8U /*%< must be a power of 2 */ +#define ALIGNMENT_SIZE sizeof(size_info) +#define DEBUG_TABLE_COUNT 512U +#define STATS_BUCKETS 512U +#define STATS_BUCKET_SIZE 32U + +/* + * Types. + */ +#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; +}; + +struct stats { + atomic_size_t gets; + atomic_size_t totalgets; +}; + +#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; + +struct isc_mem { + unsigned int magic; + unsigned int flags; + unsigned int jemalloc_flags; + unsigned int jemalloc_arena; + isc_mutex_t lock; + bool checkfree; + struct stats stats[STATS_BUCKETS + 1]; + isc_refcount_t references; + char name[16]; + atomic_size_t total; + atomic_size_t inuse; + atomic_size_t maxinuse; + atomic_size_t malloced; + atomic_size_t maxmalloced; + atomic_bool hi_called; + atomic_bool is_overmem; + isc_mem_water_t water; + void *water_arg; + atomic_size_t hi_water; + atomic_size_t lo_water; + ISC_LIST(isc_mempool_t) pools; + unsigned int poolcnt; + +#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 */ + unsigned int magic; + 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 */ + size_t allocated; /*%< # of items currently given out */ + size_t freecount; /*%< # of items on reserved list */ + size_t freemax; /*%< # of items allowed on free list */ + size_t fillcount; /*%< # of items to fetch on each fill */ + /*%< Stats only. */ + size_t gets; /*%< # of requests to this pool */ + /*%< Debugging only. */ + char name[16]; /*%< printed name in stats reports */ +}; + +/* + * 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 SHOULD_TRACE_OR_RECORD(ptr) \ + ((isc_mem_debugging & TRACE_OR_RECORD) != 0 && ptr != NULL) + +#define ADD_TRACE(a, b, c, d, e) \ + if (SHOULD_TRACE_OR_RECORD(b)) { \ + add_trace_entry(a, b, c, d, e); \ + } + +#define DELETE_TRACE(a, b, c, d, e) \ + if (SHOULD_TRACE_OR_RECORD(b)) { \ + delete_trace_entry(a, b, c, d, e); \ + } + +static void +print_active(isc_mem_t *ctx, FILE *out); +#endif /* ISC_MEM_TRACKLINES */ + +static size_t +increment_malloced(isc_mem_t *ctx, size_t size) { + size_t malloced = atomic_fetch_add_relaxed(&ctx->malloced, size) + size; + size_t maxmalloced = atomic_load_relaxed(&ctx->maxmalloced); + + if (malloced > maxmalloced) { + atomic_compare_exchange_strong(&ctx->maxmalloced, &maxmalloced, + malloced); + } + + return (malloced); +} + +static size_t +decrement_malloced(isc_mem_t *ctx, size_t size) { + size_t malloced = atomic_fetch_sub_relaxed(&ctx->malloced, size) - size; + + return (malloced); +} + +#if ISC_MEM_TRACKLINES +/*! + * mctx must not be locked. + */ +static void +add_trace_entry(isc_mem_t *mctx, const void *ptr, size_t size FLARG) { + debuglink_t *dl = NULL; + uint32_t hash; + uint32_t idx; + + MCTXLOCK(mctx); + + 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) { + goto unlock; + } + +#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 = mallocx(sizeof(*dl), mctx->jemalloc_flags); + INSIST(dl != NULL); + increment_malloced(mctx, sizeof(*dl)); + + 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++; +unlock: + MCTXUNLOCK(mctx); +} + +static void +delete_trace_entry(isc_mem_t *mctx, const void *ptr, size_t size, + const char *file, unsigned int line) { + debuglink_t *dl = NULL; + uint32_t hash; + uint32_t idx; + + MCTXLOCK(mctx); + + 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) { + goto unlock; + } + +#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 (dl != NULL) { + if (dl->ptr == ptr) { + ISC_LIST_UNLINK(mctx->debuglist[idx], dl, link); + decrement_malloced(mctx, sizeof(*dl)); + sdallocx(dl, sizeof(*dl), mctx->jemalloc_flags); + goto unlock; + } + dl = ISC_LIST_NEXT(dl, link); + } + + /* + * If we get here, we didn't find the item on the list. We're + * screwed. + */ + UNREACHABLE(); +unlock: + MCTXUNLOCK(mctx); +} +#endif /* ISC_MEM_TRACKLINES */ + +#define ADJUST_ZERO_ALLOCATION_SIZE(s) \ + if (s == 0) { \ + s = ZERO_ALLOCATION_SIZE; \ + } + +#define MEM_ALIGN(a) ((a) ? MALLOCX_ALIGN(a) : 0) + +/*! + * Perform a malloc, doing memory filling and overrun detection as necessary. + */ +static void * +mem_get(isc_mem_t *ctx, size_t size, int flags) { + char *ret = NULL; + + ADJUST_ZERO_ALLOCATION_SIZE(size); + + ret = mallocx(size, flags | ctx->jemalloc_flags); + INSIST(ret != NULL); + + if ((ctx->flags & ISC_MEMFLAG_FILL) != 0) { + memset(ret, 0xbe, size); /* Mnemonic for "beef". */ + } + + 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, int flags) { + ADJUST_ZERO_ALLOCATION_SIZE(size); + + if ((ctx->flags & ISC_MEMFLAG_FILL) != 0) { + memset(mem, 0xde, size); /* Mnemonic for "dead". */ + } + sdallocx(mem, size, flags | ctx->jemalloc_flags); +} + +static void * +mem_realloc(isc_mem_t *ctx, void *old_ptr, size_t old_size, size_t new_size, + int flags) { + void *new_ptr = NULL; + + ADJUST_ZERO_ALLOCATION_SIZE(new_size); + + new_ptr = rallocx(old_ptr, new_size, flags | ctx->jemalloc_flags); + INSIST(new_ptr != NULL); + + if ((ctx->flags & ISC_MEMFLAG_FILL) != 0) { + ssize_t diff_size = new_size - old_size; + void *diff_ptr = (uint8_t *)new_ptr + old_size; + if (diff_size > 0) { + /* Mnemonic for "beef". */ + memset(diff_ptr, 0xbe, diff_size); + } + } + + return (new_ptr); +} + +#define stats_bucket(ctx, size) \ + ((size / STATS_BUCKET_SIZE) >= STATS_BUCKETS \ + ? &ctx->stats[STATS_BUCKETS] \ + : &ctx->stats[size / STATS_BUCKET_SIZE]) + +/*! + * Update internal counters after a memory get. + */ +static void +mem_getstats(isc_mem_t *ctx, size_t size) { + struct stats *stats = stats_bucket(ctx, size); + + atomic_fetch_add_relaxed(&ctx->total, size); + atomic_fetch_add_release(&ctx->inuse, size); + + atomic_fetch_add_relaxed(&stats->gets, 1); + atomic_fetch_add_relaxed(&stats->totalgets, 1); + + increment_malloced(ctx, size); +} + +/*! + * Update internal counters after a memory put. + */ +static void +mem_putstats(isc_mem_t *ctx, void *ptr, size_t size) { + struct stats *stats = stats_bucket(ctx, size); + atomic_size_t s, g; + + UNUSED(ptr); + + s = atomic_fetch_sub_release(&ctx->inuse, size); + INSIST(s >= size); + + g = atomic_fetch_sub_release(&stats->gets, 1); + INSIST(g >= 1); + + decrement_malloced(ctx, size); +} + +/* + * Private. + */ + +static bool +mem_jemalloc_arena_create(unsigned int *pnew_arenano) { + REQUIRE(pnew_arenano != NULL); + +#if defined(JEMALLOC_API_SUPPORTED) && JEMALLOC_VERSION_MAJOR >= 4 + unsigned int arenano = 0; + size_t len = sizeof(arenano); + int res = 0; + + res = mallctl("arenas.create", &arenano, &len, NULL, 0); + if (res != 0) { + return (false); + } + + *pnew_arenano = arenano; + + return (true); +#else + *pnew_arenano = ISC_MEM_ILLEGAL_ARENA; + return (true); +#endif /* defined(JEMALLOC_API_SUPPORTED) && JEMALLOC_VERSION_MAJOR >= 4 */ +} + +static bool +mem_jemalloc_arena_destroy(unsigned int arenano) { +#if defined(JEMALLOC_API_SUPPORTED) && JEMALLOC_VERSION_MAJOR >= 4 + int res = 0; + char buf[256] = { 0 }; + + (void)snprintf(buf, sizeof(buf), "arena.%u.destroy", arenano); + res = mallctl(buf, NULL, NULL, NULL, 0); + if (res != 0) { + return (false); + } + + return (true); +#else + UNUSED(arenano); + return (true); +#endif /* defined(JEMALLOC_API_SUPPORTED) && JEMALLOC_VERSION_MAJOR >= 4 */ +} + +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, unsigned int jemalloc_flags) { + isc_mem_t *ctx = NULL; + + REQUIRE(ctxp != NULL && *ctxp == NULL); + + ctx = mallocx(sizeof(*ctx), + MALLOCX_ALIGN(isc_os_cacheline()) | jemalloc_flags); + INSIST(ctx != NULL); + + *ctx = (isc_mem_t){ + .magic = MEM_MAGIC, + .flags = flags, + .jemalloc_flags = jemalloc_flags, + .jemalloc_arena = ISC_MEM_ILLEGAL_ARENA, + .checkfree = true, + }; + + isc_mutex_init(&ctx->lock); + isc_refcount_init(&ctx->references, 1); + + atomic_init(&ctx->total, 0); + atomic_init(&ctx->inuse, 0); + atomic_init(&ctx->maxinuse, 0); + atomic_init(&ctx->malloced, sizeof(*ctx)); + atomic_init(&ctx->maxmalloced, sizeof(*ctx)); + atomic_init(&ctx->hi_water, 0); + atomic_init(&ctx->lo_water, 0); + atomic_init(&ctx->hi_called, false); + atomic_init(&ctx->is_overmem, false); + + for (size_t i = 0; i < STATS_BUCKETS + 1; i++) { + atomic_init(&ctx->stats[i].gets, 0); + atomic_init(&ctx->stats[i].totalgets, 0); + } + ISC_LIST_INIT(ctx->pools); + +#if ISC_MEM_TRACKLINES + if ((isc_mem_debugging & ISC_MEM_DEBUGRECORD) != 0) { + unsigned int i; + + ctx->debuglist = + mallocx((DEBUG_TABLE_COUNT * sizeof(debuglist_t)), + ctx->jemalloc_flags); + INSIST(ctx->debuglist != NULL); + + for (i = 0; i < DEBUG_TABLE_COUNT; i++) { + ISC_LIST_INIT(ctx->debuglist[i]); + } + increment_malloced(ctx, + DEBUG_TABLE_COUNT * sizeof(debuglist_t)); + } +#endif /* if ISC_MEM_TRACKLINES */ + + LOCK(&contextslock); + ISC_LIST_INITANDAPPEND(contexts, ctx, link); + UNLOCK(&contextslock); + + *ctxp = ctx; +} + +/* + * Public. + */ + +static void +destroy(isc_mem_t *ctx) { + unsigned int i; + size_t malloced; + unsigned int arena_no; + + LOCK(&contextslock); + ISC_LIST_UNLINK(contexts, ctx, link); + totallost += isc_mem_inuse(ctx); + UNLOCK(&contextslock); + + ctx->magic = 0; + + arena_no = ctx->jemalloc_arena; + + INSIST(ISC_LIST_EMPTY(ctx->pools)); + +#if ISC_MEM_TRACKLINES + if (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); + sdallocx(dl, sizeof(*dl), ctx->jemalloc_flags); + decrement_malloced(ctx, sizeof(*dl)); + } + } + + sdallocx(ctx->debuglist, + (DEBUG_TABLE_COUNT * sizeof(debuglist_t)), + ctx->jemalloc_flags); + decrement_malloced(ctx, + DEBUG_TABLE_COUNT * sizeof(debuglist_t)); + } +#endif /* if ISC_MEM_TRACKLINES */ + + if (ctx->checkfree) { + for (i = 0; i <= STATS_BUCKETS; i++) { + struct stats *stats = &ctx->stats[i]; + size_t gets = atomic_load_acquire(&stats->gets); + if (gets != 0U) { + fprintf(stderr, + "Failing assertion due to probable " + "leaked memory in context %p (\"%s\") " + "(stats[%u].gets == %zu).\n", + ctx, ctx->name, i, gets); +#if ISC_MEM_TRACKLINES + print_active(ctx, stderr); +#endif /* if ISC_MEM_TRACKLINES */ + INSIST(gets == 0U); + } + } + } + + isc_mutex_destroy(&ctx->lock); + + malloced = decrement_malloced(ctx, sizeof(*ctx)); + + if (ctx->checkfree) { + INSIST(malloced == 0); + } + sdallocx(ctx, sizeof(*ctx), + MALLOCX_ALIGN(isc_os_cacheline()) | ctx->jemalloc_flags); + + if (arena_no != ISC_MEM_ILLEGAL_ARENA) { + RUNTIME_CHECK(mem_jemalloc_arena_destroy(arena_no) == true); + } +} + +void +isc_mem_attach(isc_mem_t *source, isc_mem_t **targetp) { + REQUIRE(VALID_CONTEXT(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + isc_refcount_increment(&source->references); + + *targetp = source; +} + +void +isc__mem_detach(isc_mem_t **ctxp FLARG) { + isc_mem_t *ctx = NULL; + + REQUIRE(ctxp != NULL && VALID_CONTEXT(*ctxp)); + + ctx = *ctxp; + *ctxp = NULL; + + if (isc_refcount_decrement(&ctx->references) == 1) { + isc_refcount_destroy(&ctx->references); +#if ISC_MEM_TRACKLINES + if ((isc_mem_debugging & ISC_MEM_DEBUGTRACE) != 0) { + fprintf(stderr, "destroy mctx %p file %s line %u\n", + ctx, file, line); + } +#endif + 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, + size_t alignment FLARG) { + isc_mem_t *ctx = NULL; + + REQUIRE(ctxp != NULL && VALID_CONTEXT(*ctxp)); + REQUIRE(ptr != NULL); + REQUIRE(size != 0); + + ctx = *ctxp; + *ctxp = NULL; + + DELETE_TRACE(ctx, ptr, size, file, line); + + mem_putstats(ctx, ptr, size); + mem_put(ctx, ptr, size, MEM_ALIGN(alignment)); + + if (isc_refcount_decrement(&ctx->references) == 1) { + isc_refcount_destroy(&ctx->references); + destroy(ctx); + } +} + +void +isc__mem_destroy(isc_mem_t **ctxp FLARG) { + isc_mem_t *ctx = NULL; + + /* + * This routine provides legacy support for callers who use mctxs + * without attaching/detaching. + */ + + REQUIRE(ctxp != NULL && VALID_CONTEXT(*ctxp)); + + ctx = *ctxp; + *ctxp = NULL; + +#if ISC_MEM_TRACKLINES + if ((isc_mem_debugging & ISC_MEM_DEBUGTRACE) != 0) { + fprintf(stderr, "destroy mctx %p file %s line %u\n", ctx, file, + line); + } + + 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; +} + +#define CALL_HI_WATER(ctx) \ + { \ + if (ctx->water != NULL && hi_water(ctx)) { \ + (ctx->water)(ctx->water_arg, ISC_MEM_HIWATER); \ + } \ + } + +#define CALL_LO_WATER(ctx) \ + { \ + if ((ctx->water != NULL) && lo_water(ctx)) { \ + (ctx->water)(ctx->water_arg, ISC_MEM_LOWATER); \ + } \ + } + +static bool +hi_water(isc_mem_t *ctx) { + size_t inuse; + size_t maxinuse; + size_t hiwater = atomic_load_relaxed(&ctx->hi_water); + + if (hiwater == 0) { + return (false); + } + + inuse = atomic_load_acquire(&ctx->inuse); + if (inuse <= hiwater) { + return (false); + } + + maxinuse = atomic_load_acquire(&ctx->maxinuse); + if (inuse > maxinuse) { + (void)atomic_compare_exchange_strong(&ctx->maxinuse, &maxinuse, + inuse); + + if ((isc_mem_debugging & ISC_MEM_DEBUGUSAGE) != 0) { + fprintf(stderr, "maxinuse = %lu\n", + (unsigned long)inuse); + } + } + + if (atomic_load_acquire(&ctx->hi_called)) { + return (false); + } + + /* We are over water (for the first time) */ + atomic_store_release(&ctx->is_overmem, true); + + return (true); +} + +static bool +lo_water(isc_mem_t *ctx) { + size_t inuse; + size_t lowater = atomic_load_relaxed(&ctx->lo_water); + + if (lowater == 0) { + return (false); + } + + inuse = atomic_load_acquire(&ctx->inuse); + if (inuse >= lowater) { + return (false); + } + + if (!atomic_load_acquire(&ctx->hi_called)) { + return (false); + } + + /* We are no longer overmem */ + atomic_store_release(&ctx->is_overmem, false); + + return (true); +} + +void * +isc__mem_get(isc_mem_t *ctx, size_t size, size_t alignment FLARG) { + void *ptr = NULL; + + REQUIRE(VALID_CONTEXT(ctx)); + + ptr = mem_get(ctx, size, MEM_ALIGN(alignment)); + + mem_getstats(ctx, size); + ADD_TRACE(ctx, ptr, size, file, line); + + CALL_HI_WATER(ctx); + + return (ptr); +} + +void +isc__mem_put(isc_mem_t *ctx, void *ptr, size_t size, size_t alignment FLARG) { + REQUIRE(VALID_CONTEXT(ctx)); + + DELETE_TRACE(ctx, ptr, size, file, line); + + mem_putstats(ctx, ptr, size); + mem_put(ctx, ptr, size, MEM_ALIGN(alignment)); + + CALL_LO_WATER(ctx); +} + +void +isc_mem_waterack(isc_mem_t *ctx, int flag) { + REQUIRE(VALID_CONTEXT(ctx)); + + if (flag == ISC_MEM_LOWATER) { + atomic_store(&ctx->hi_called, false); + } else if (flag == ISC_MEM_HIWATER) { + atomic_store(&ctx->hi_called, true); + } +} + +#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; + + fprintf(out, "Dump of all outstanding memory " + "allocations:\n"); + 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) { + fprintf(out, "\tNone.\n"); + } + } +} +#endif /* if ISC_MEM_TRACKLINES */ + +/* + * Print the stats[] on the stream "out" with suitable formatting. + */ +void +isc_mem_stats(isc_mem_t *ctx, FILE *out) { + isc_mempool_t *pool = NULL; + + REQUIRE(VALID_CONTEXT(ctx)); + + MCTXLOCK(ctx); + + for (size_t i = 0; i <= STATS_BUCKETS; i++) { + size_t totalgets; + size_t gets; + struct stats *stats = &ctx->stats[i]; + + totalgets = atomic_load_acquire(&stats->totalgets); + gets = atomic_load_acquire(&stats->gets); + + if (totalgets != 0U && gets != 0U) { + fprintf(out, "%s%5zu: %11zu gets, %11zu rem", + (i == STATS_BUCKETS) ? ">=" : " ", i, + totalgets, gets); + 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) { + fprintf(out, "[Pool statistics]\n"); + fprintf(out, "%15s %10s %10s %10s %10s %10s %10s %1s\n", "name", + "size", "allocated", "freecount", "freemax", + "fillcount", "gets", "L"); + } + while (pool != NULL) { + fprintf(out, + "%15s %10zu %10zu %10zu %10zu %10zu %10zu %10zu %s\n", + pool->name, pool->size, (size_t)0, 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); +} + +void * +isc__mem_allocate(isc_mem_t *ctx, size_t size FLARG) { + void *ptr = NULL; + + REQUIRE(VALID_CONTEXT(ctx)); + + ptr = mem_get(ctx, size, 0); + + /* Recalculate the real allocated size */ + size = sallocx(ptr, ctx->jemalloc_flags); + + mem_getstats(ctx, size); + ADD_TRACE(ctx, ptr, size, file, line); + + CALL_HI_WATER(ctx); + + return (ptr); +} + +void * +isc__mem_reget(isc_mem_t *ctx, void *old_ptr, size_t old_size, size_t new_size, + size_t alignment FLARG) { + void *new_ptr = NULL; + + if (old_ptr == NULL) { + REQUIRE(old_size == 0); + new_ptr = isc__mem_get(ctx, new_size, alignment FLARG_PASS); + } else if (new_size == 0) { + isc__mem_put(ctx, old_ptr, old_size, alignment FLARG_PASS); + } else { + DELETE_TRACE(ctx, old_ptr, old_size, file, line); + mem_putstats(ctx, old_ptr, old_size); + + new_ptr = mem_realloc(ctx, old_ptr, old_size, new_size, + MEM_ALIGN(alignment)); + + mem_getstats(ctx, new_size); + ADD_TRACE(ctx, new_ptr, new_size, file, line); + + /* + * We want to postpone the call to water in edge case + * where the realloc will exactly hit on the boundary of + * the water and we would call water twice. + */ + CALL_LO_WATER(ctx); + CALL_HI_WATER(ctx); + } + + return (new_ptr); +} + +void * +isc__mem_reallocate(isc_mem_t *ctx, void *old_ptr, size_t new_size FLARG) { + void *new_ptr = NULL; + + REQUIRE(VALID_CONTEXT(ctx)); + + if (old_ptr == NULL) { + new_ptr = isc__mem_allocate(ctx, new_size FLARG_PASS); + } else if (new_size == 0) { + isc__mem_free(ctx, old_ptr FLARG_PASS); + } else { + size_t old_size = sallocx(old_ptr, ctx->jemalloc_flags); + + DELETE_TRACE(ctx, old_ptr, old_size, file, line); + mem_putstats(ctx, old_ptr, old_size); + + new_ptr = mem_realloc(ctx, old_ptr, old_size, new_size, 0); + + /* Recalculate the real allocated size */ + new_size = sallocx(new_ptr, ctx->jemalloc_flags); + + mem_getstats(ctx, new_size); + ADD_TRACE(ctx, new_ptr, new_size, file, line); + + /* + * We want to postpone the call to water in edge case + * where the realloc will exactly hit on the boundary of + * the water and we would call water twice. + */ + CALL_LO_WATER(ctx); + CALL_HI_WATER(ctx); + } + + return (new_ptr); +} + +void +isc__mem_free(isc_mem_t *ctx, void *ptr FLARG) { + size_t size = 0; + + REQUIRE(VALID_CONTEXT(ctx)); + + size = sallocx(ptr, ctx->jemalloc_flags); + + DELETE_TRACE(ctx, ptr, size, file, line); + + mem_putstats(ctx, ptr, size); + mem_put(ctx, ptr, size, 0); + + CALL_LO_WATER(ctx); +} + +/* + * Other useful things. + */ + +char * +isc__mem_strdup(isc_mem_t *mctx, const char *s FLARG) { + size_t len; + char *ns = NULL; + + REQUIRE(VALID_CONTEXT(mctx)); + REQUIRE(s != NULL); + + len = strlen(s) + 1; + + ns = isc__mem_allocate(mctx, len FLARG_PASS); + + strlcpy(ns, s, len); + + return (ns); +} + +char * +isc__mem_strndup(isc_mem_t *mctx, const char *s, size_t size FLARG) { + size_t len; + char *ns = NULL; + + REQUIRE(VALID_CONTEXT(mctx)); + REQUIRE(s != NULL); + REQUIRE(size != 0); + + len = strlen(s) + 1; + if (len > size) { + len = size; + } + + ns = isc__mem_allocate(mctx, len FLARG_PASS); + + strlcpy(ns, s, len); + + return (ns); +} + +void +isc_mem_setdestroycheck(isc_mem_t *ctx, bool flag) { + REQUIRE(VALID_CONTEXT(ctx)); + + MCTXLOCK(ctx); + + ctx->checkfree = flag; + + MCTXUNLOCK(ctx); +} + +size_t +isc_mem_inuse(isc_mem_t *ctx) { + REQUIRE(VALID_CONTEXT(ctx)); + + return (atomic_load_acquire(&ctx->inuse)); +} + +size_t +isc_mem_maxinuse(isc_mem_t *ctx) { + REQUIRE(VALID_CONTEXT(ctx)); + + return (atomic_load_acquire(&ctx->maxinuse)); +} + +size_t +isc_mem_total(isc_mem_t *ctx) { + REQUIRE(VALID_CONTEXT(ctx)); + + return (atomic_load_acquire(&ctx->total)); +} + +size_t +isc_mem_malloced(isc_mem_t *ctx) { + REQUIRE(VALID_CONTEXT(ctx)); + + return (atomic_load_acquire(&ctx->malloced)); +} + +size_t +isc_mem_maxmalloced(isc_mem_t *ctx) { + REQUIRE(VALID_CONTEXT(ctx)); + + return (atomic_load_acquire(&ctx->maxmalloced)); +} + +void +isc_mem_clearwater(isc_mem_t *mctx) { + isc_mem_setwater(mctx, NULL, NULL, 0, 0); +} + +void +isc_mem_setwater(isc_mem_t *ctx, isc_mem_water_t water, void *water_arg, + size_t hiwater, size_t lowater) { + isc_mem_water_t oldwater; + void *oldwater_arg; + + REQUIRE(VALID_CONTEXT(ctx)); + REQUIRE(hiwater >= lowater); + + oldwater = ctx->water; + oldwater_arg = ctx->water_arg; + + /* No water was set and new water is also NULL */ + if (oldwater == NULL && water == NULL) { + return; + } + + /* The water function is being set for the first time */ + if (oldwater == NULL) { + REQUIRE(water != NULL && lowater > 0); + + INSIST(atomic_load(&ctx->hi_water) == 0); + INSIST(atomic_load(&ctx->lo_water) == 0); + + ctx->water = water; + ctx->water_arg = water_arg; + atomic_store(&ctx->hi_water, hiwater); + atomic_store(&ctx->lo_water, lowater); + + return; + } + + REQUIRE((water == oldwater && water_arg == oldwater_arg) || + (water == NULL && water_arg == NULL && hiwater == 0)); + + atomic_store(&ctx->hi_water, hiwater); + atomic_store(&ctx->lo_water, lowater); + + if (atomic_load_acquire(&ctx->hi_called) && + (atomic_load_acquire(&ctx->inuse) < lowater || lowater == 0U)) + { + (oldwater)(oldwater_arg, ISC_MEM_LOWATER); + } +} + +bool +isc_mem_isovermem(isc_mem_t *ctx) { + REQUIRE(VALID_CONTEXT(ctx)); + + return (atomic_load_relaxed(&ctx->is_overmem)); +} + +void +isc_mem_setname(isc_mem_t *ctx, const char *name) { + REQUIRE(VALID_CONTEXT(ctx)); + + LOCK(&ctx->lock); + strlcpy(ctx->name, name, sizeof(ctx->name)); + UNLOCK(&ctx->lock); +} + +const char * +isc_mem_getname(isc_mem_t *ctx) { + REQUIRE(VALID_CONTEXT(ctx)); + + if (ctx->name[0] == 0) { + return (""); + } + + return (ctx->name); +} + +/* + * Memory pool stuff + */ + +void +isc__mempool_create(isc_mem_t *restrict mctx, const size_t element_size, + isc_mempool_t **restrict mpctxp FLARG) { + isc_mempool_t *restrict mpctx = NULL; + size_t size = element_size; + + REQUIRE(VALID_CONTEXT(mctx)); + REQUIRE(size > 0U); + REQUIRE(mpctxp != NULL && *mpctxp == NULL); + + /* + * Mempools are stored as a linked list of element. + */ + if (size < sizeof(element)) { + size = sizeof(element); + } + + /* + * Allocate space for this pool, initialize values, and if all + * works well, attach to the memory context. + */ + mpctx = isc_mem_get(mctx, sizeof(isc_mempool_t)); + + *mpctx = (isc_mempool_t){ + .size = size, + .freemax = 1, + .fillcount = 1, + }; + +#if ISC_MEM_TRACKLINES + if ((isc_mem_debugging & ISC_MEM_DEBUGTRACE) != 0) { + fprintf(stderr, "create pool %p file %s line %u mctx %p\n", + mpctx, file, line, mctx); + } +#endif /* ISC_MEM_TRACKLINES */ + + isc_mem_attach(mctx, &mpctx->mctx); + mpctx->magic = MEMPOOL_MAGIC; + + *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 *restrict mpctx, const char *name) { + REQUIRE(VALID_MEMPOOL(mpctx)); + REQUIRE(name != NULL); + + strlcpy(mpctx->name, name, sizeof(mpctx->name)); +} + +void +isc__mempool_destroy(isc_mempool_t **restrict mpctxp FLARG) { + isc_mempool_t *restrict mpctx = NULL; + isc_mem_t *mctx = NULL; + element *restrict item = NULL; + + REQUIRE(mpctxp != NULL); + REQUIRE(VALID_MEMPOOL(*mpctxp)); + + mpctx = *mpctxp; + *mpctxp = NULL; + + mctx = mpctx->mctx; + +#if ISC_MEM_TRACKLINES + if ((isc_mem_debugging & ISC_MEM_DEBUGTRACE) != 0) { + fprintf(stderr, "destroy pool %p file %s line %u mctx %p\n", + mpctx, file, line, mctx); + } +#endif + + if (mpctx->allocated > 0) { + UNEXPECTED_ERROR("mempool %s leaked memory", mpctx->name); + } + REQUIRE(mpctx->allocated == 0); + + /* + * Return any items on the free list + */ + 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, 0); + } + + /* + * Remove our linked list entry from the memory context. + */ + MCTXLOCK(mctx); + ISC_LIST_UNLINK(mctx->pools, mpctx, link); + mctx->poolcnt--; + MCTXUNLOCK(mctx); + + mpctx->magic = 0; + + isc_mem_putanddetach(&mpctx->mctx, mpctx, sizeof(isc_mempool_t)); +} + +void * +isc__mempool_get(isc_mempool_t *restrict mpctx FLARG) { + element *restrict item = NULL; + + REQUIRE(VALID_MEMPOOL(mpctx)); + + mpctx->allocated++; + + if (mpctx->items == NULL) { + isc_mem_t *mctx = mpctx->mctx; +#if !__SANITIZE_ADDRESS__ + const size_t fillcount = mpctx->fillcount; +#else + const size_t fillcount = 1; +#endif + /* + * We need to dip into the well. Fill up our free list. + */ + for (size_t i = 0; i < fillcount; i++) { + item = mem_get(mctx, mpctx->size, 0); + mem_getstats(mctx, mpctx->size); + item->next = mpctx->items; + mpctx->items = item; + mpctx->freecount++; + } + } + + item = mpctx->items; + INSIST(item != NULL); + + mpctx->items = item->next; + + INSIST(mpctx->freecount > 0); + mpctx->freecount--; + mpctx->gets++; + + ADD_TRACE(mpctx->mctx, item, mpctx->size, file, line); + + return (item); +} + +/* coverity[+free : arg-1] */ +void +isc__mempool_put(isc_mempool_t *restrict mpctx, void *mem FLARG) { + element *restrict item = NULL; + + REQUIRE(VALID_MEMPOOL(mpctx)); + REQUIRE(mem != NULL); + + isc_mem_t *mctx = mpctx->mctx; + const size_t freecount = mpctx->freecount; +#if !__SANITIZE_ADDRESS__ + const size_t freemax = mpctx->freemax; +#else + const size_t freemax = 0; +#endif + + INSIST(mpctx->allocated > 0); + mpctx->allocated--; + + DELETE_TRACE(mctx, mem, mpctx->size, file, line); + + /* + * If our free list is full, return this to the mctx directly. + */ + if (freecount >= freemax) { + mem_putstats(mctx, mem, mpctx->size); + mem_put(mctx, mem, mpctx->size, 0); + return; + } + + /* + * Otherwise, attach it to our free list and bump the counter. + */ + item = (element *)mem; + item->next = mpctx->items; + mpctx->items = item; + mpctx->freecount++; +} + +/* + * Quotas + */ + +void +isc_mempool_setfreemax(isc_mempool_t *restrict mpctx, + const unsigned int limit) { + REQUIRE(VALID_MEMPOOL(mpctx)); + mpctx->freemax = limit; +} + +unsigned int +isc_mempool_getfreemax(isc_mempool_t *restrict mpctx) { + REQUIRE(VALID_MEMPOOL(mpctx)); + + return (mpctx->freemax); +} + +unsigned int +isc_mempool_getfreecount(isc_mempool_t *restrict mpctx) { + REQUIRE(VALID_MEMPOOL(mpctx)); + + return (mpctx->freecount); +} + +unsigned int +isc_mempool_getallocated(isc_mempool_t *restrict mpctx) { + REQUIRE(VALID_MEMPOOL(mpctx)); + + return (mpctx->allocated); +} + +void +isc_mempool_setfillcount(isc_mempool_t *restrict mpctx, + unsigned int const limit) { + REQUIRE(VALID_MEMPOOL(mpctx)); + REQUIRE(limit > 0); + + mpctx->fillcount = limit; +} + +unsigned int +isc_mempool_getfillcount(isc_mempool_t *restrict mpctx) { + REQUIRE(VALID_MEMPOOL(mpctx)); + + return (mpctx->fillcount); +} + +/* + * Requires contextslock to be held by caller. + */ +#if ISC_MEM_TRACKLINES +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); +} +#endif + +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_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 *ctx) { + return (isc_refcount_current(&ctx->references)); +} + +typedef struct summarystat { + uint64_t total; + uint64_t inuse; + uint64_t malloced; + 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); +#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 += isc_mem_total(ctx); + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "total")); + TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", + (uint64_t)isc_mem_total(ctx))); + TRY0(xmlTextWriterEndElement(writer)); /* total */ + + summary->inuse += isc_mem_inuse(ctx); + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "inuse")); + TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", + (uint64_t)isc_mem_inuse(ctx))); + TRY0(xmlTextWriterEndElement(writer)); /* inuse */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "maxinuse")); + TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", + (uint64_t)isc_mem_maxinuse(ctx))); + TRY0(xmlTextWriterEndElement(writer)); /* maxinuse */ + + summary->malloced += isc_mem_malloced(ctx); + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "malloced")); + TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", + (uint64_t)isc_mem_malloced(ctx))); + TRY0(xmlTextWriterEndElement(writer)); /* malloced */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "maxmalloced")); + TRY0(xmlTextWriterWriteFormatString( + writer, "%" PRIu64 "", (uint64_t)isc_mem_maxmalloced(ctx))); + TRY0(xmlTextWriterEndElement(writer)); /* maxmalloced */ + + 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)atomic_load_relaxed(&ctx->hi_water))); + TRY0(xmlTextWriterEndElement(writer)); /* hiwater */ + + TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "lowater")); + TRY0(xmlTextWriterWriteFormatString( + writer, "%" PRIu64 "", + (uint64_t)atomic_load_relaxed(&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 = { 0 }; + uint64_t lost; + int xmlrc; + xmlTextWriterPtr writer = (xmlTextWriterPtr)writer0; + + 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 "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); + summary->total += isc_mem_total(ctx); + summary->inuse += isc_mem_inuse(ctx); + summary->malloced += isc_mem_malloced(ctx); +#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(isc_mem_total(ctx)); + CHECKMEM(obj); + json_object_object_add(ctxobj, "total", obj); + + obj = json_object_new_int64(isc_mem_inuse(ctx)); + CHECKMEM(obj); + json_object_object_add(ctxobj, "inuse", obj); + + obj = json_object_new_int64(isc_mem_maxinuse(ctx)); + CHECKMEM(obj); + json_object_object_add(ctxobj, "maxinuse", obj); + + obj = json_object_new_int64(isc_mem_malloced(ctx)); + CHECKMEM(obj); + json_object_object_add(ctxobj, "malloced", obj); + + obj = json_object_new_int64(isc_mem_maxmalloced(ctx)); + CHECKMEM(obj); + json_object_object_add(ctxobj, "maxmalloced", 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(atomic_load_relaxed(&ctx->hi_water)); + CHECKMEM(obj); + json_object_object_add(ctxobj, "hiwater", obj); + + obj = json_object_new_int64(atomic_load_relaxed(&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 = { 0 }; + uint64_t lost; + json_object *ctxarray, *obj; + json_object *memobj = (json_object *)memobj0; + + 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.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 FLARG) { + mem_create(mctxp, isc_mem_defaultflags, 0); +#if ISC_MEM_TRACKLINES + if ((isc_mem_debugging & ISC_MEM_DEBUGTRACE) != 0) { + fprintf(stderr, "create mctx %p file %s line %u\n", *mctxp, + file, line); + } +#endif /* ISC_MEM_TRACKLINES */ +} + +void +isc__mem_create_arena(isc_mem_t **mctxp FLARG) { + unsigned int arena_no = ISC_MEM_ILLEGAL_ARENA; + + RUNTIME_CHECK(mem_jemalloc_arena_create(&arena_no)); + + /* + * We use MALLOCX_TCACHE_NONE to bypass the tcache and route + * allocations directly to the arena. That is a recommendation + * from jemalloc developers: + * + * https://github.com/jemalloc/jemalloc/issues/2483#issuecomment-1698173849 + */ + mem_create(mctxp, isc_mem_defaultflags, + arena_no == ISC_MEM_ILLEGAL_ARENA + ? 0 + : MALLOCX_ARENA(arena_no) | MALLOCX_TCACHE_NONE); + (*mctxp)->jemalloc_arena = arena_no; +#if ISC_MEM_TRACKLINES + if ((isc_mem_debugging & ISC_MEM_DEBUGTRACE) != 0) { + fprintf(stderr, + "create mctx %p file %s line %u for jemalloc arena " + "%u\n", + *mctxp, file, line, arena_no); + } +#endif /* ISC_MEM_TRACKLINES */ +} + +#if defined(JEMALLOC_API_SUPPORTED) && JEMALLOC_VERSION_MAJOR >= 4 +static bool +jemalloc_set_ssize_value(const char *valname, ssize_t newval) { + int ret; + + ret = mallctl(valname, NULL, NULL, &newval, sizeof(newval)); + return (ret == 0); +} +#endif /* defined(JEMALLOC_API_SUPPORTED) && JEMALLOC_VERSION_MAJOR >= 4 */ + +static isc_result_t +mem_set_arena_ssize_value(isc_mem_t *mctx, const char *arena_valname, + const ssize_t newval) { + REQUIRE(VALID_CONTEXT(mctx)); +#if defined(JEMALLOC_API_SUPPORTED) && JEMALLOC_VERSION_MAJOR >= 4 + bool ret; + char buf[256] = { 0 }; + + if (mctx->jemalloc_arena == ISC_MEM_ILLEGAL_ARENA) { + return (ISC_R_UNEXPECTED); + } + + (void)snprintf(buf, sizeof(buf), "arena.%u.%s", mctx->jemalloc_arena, + arena_valname); + + ret = jemalloc_set_ssize_value(buf, newval); + + if (!ret) { + return (ISC_R_FAILURE); + } + + return (ISC_R_SUCCESS); +#else + UNUSED(arena_valname); + UNUSED(newval); + return (ISC_R_NOTIMPLEMENTED); +#endif /* defined(JEMALLOC_API_SUPPORTED) && JEMALLOC_VERSION_MAJOR >= 4 */ +} + +isc_result_t +isc_mem_arena_set_muzzy_decay_ms(isc_mem_t *mctx, const ssize_t decay_ms) { + return (mem_set_arena_ssize_value(mctx, "muzzy_decay_ms", decay_ms)); +} + +isc_result_t +isc_mem_arena_set_dirty_decay_ms(isc_mem_t *mctx, const ssize_t decay_ms) { + return (mem_set_arena_ssize_value(mctx, "dirty_decay_ms", decay_ms)); +} + +void +isc__mem_printactive(isc_mem_t *ctx, FILE *file) { +#if ISC_MEM_TRACKLINES + REQUIRE(VALID_CONTEXT(ctx)); + REQUIRE(file != NULL); + + print_active(ctx, file); +#else /* if ISC_MEM_TRACKLINES */ + UNUSED(ctx); + 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/meminfo.c b/lib/isc/meminfo.c new file mode 100644 index 0000000..612ccea --- /dev/null +++ b/lib/isc/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/mutex.c b/lib/isc/mutex.c new file mode 100644 index 0000000..65f1663 --- /dev/null +++ b/lib/isc/mutex.c @@ -0,0 +1,53 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/string.h> +#include <isc/util.h> + +#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; + +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 */ + +int +isc__mutex_init(isc_mutex_t *mp) { +#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); + + return (pthread_mutex_init(mp, &attr)); +#else /* HAVE_PTHREAD_MUTEX_ADAPTIVE_NP */ + return (pthread_mutex_init(mp, NULL)); +#endif /* HAVE_PTHREAD_MUTEX_ADAPTIVE_NP */ +} 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/net.c b/lib/isc/net.c new file mode 100644 index 0000000..e32a543 --- /dev/null +++ b/lib/isc/net.c @@ -0,0 +1,497 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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_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 isc_result_t +try_proto(int domain) { + int s; + isc_result_t result = ISC_R_SUCCESS; + + 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: + UNEXPECTED_SYSERROR(errno, "socket()"); + 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); + unix_result = try_proto(PF_UNIX); +} + +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; +#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) { + UNEXPECTED_SYSERROR(errno, "socket()"); + 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) { + UNEXPECTED_SYSERROR(errno, "socket()"); + 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; + 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) { + UNEXPECTED_SYSERROR(errno, "socket()"); + 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 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/netaddr.c b/lib/isc/netaddr.c new file mode 100644 index 0000000..c674d83 --- /dev/null +++ b/lib/isc/netaddr.c @@ -0,0 +1,467 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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; + case AF_UNIX: + if (strcmp(a->type.un, b->type.un) != 0) { + return (false); + } + break; + 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; + 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); + 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) { + 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); +} + +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; + case AF_UNIX: + memmove(t->type.un, s->type.sunix.sun_path, sizeof(t->type.un)); + t->zone = 0; + break; + 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/http.c b/lib/isc/netmgr/http.c new file mode 100644 index 0000000..f2d3e2d --- /dev/null +++ b/lib/isc/netmgr/http.c @@ -0,0 +1,3755 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 <ctype.h> +#include <inttypes.h> +#include <limits.h> +#include <nghttp2/nghttp2.h> +#include <signal.h> +#include <string.h> + +#include <isc/base64.h> +#include <isc/log.h> +#include <isc/netmgr.h> +#include <isc/print.h> +#include <isc/sockaddr.h> +#include <isc/tls.h> +#include <isc/url.h> +#include <isc/util.h> + +#include "netmgr-int.h" + +#define AUTHEXTRA 7 + +#define MAX_DNS_MESSAGE_SIZE (UINT16_MAX) + +#define DNS_MEDIA_TYPE "application/dns-message" + +/* + * See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control + * for additional details. Basically it means "avoid caching by any + * means." + */ +#define DEFAULT_CACHE_CONTROL "no-cache, no-store, must-revalidate" + +/* + * If server during request processing surpasses any of the limits + * below, it will just reset the stream without returning any error + * codes in a response. Ideally, these parameters should be + * configurable both globally and per every HTTP endpoint description + * in the configuration file, but for now it should be enough. + */ + +/* + * 128K should be enough to encode 64K of data into base64url inside GET + * request and have extra space for other headers + */ +#define MAX_ALLOWED_DATA_IN_HEADERS (MAX_DNS_MESSAGE_SIZE * 2) + +#define MAX_ALLOWED_DATA_IN_POST \ + (MAX_DNS_MESSAGE_SIZE + MAX_DNS_MESSAGE_SIZE / 2) + +#define HEADER_MATCH(header, name, namelen) \ + (((namelen) == sizeof(header) - 1) && \ + (strncasecmp((header), (const char *)(name), (namelen)) == 0)) + +#define MIN_SUCCESSFUL_HTTP_STATUS (200) +#define MAX_SUCCESSFUL_HTTP_STATUS (299) + +/* This definition sets the upper limit of pending write buffer to an + * adequate enough value. That is done mostly to fight a limitation + * for a max TLS record size in flamethrower (2K). In a perfect world + * this constant should not be required, if we ever move closer to + * that state, the constant, and corresponding code, should be + * removed. For now the limit seems adequate enough to fight + * "tinygrams" problem. */ +#define FLUSH_HTTP_WRITE_BUFFER_AFTER (1536) + +/* This switch is here mostly to test the code interoperability with + * buggy implementations */ +#define ENABLE_HTTP_WRITE_BUFFERING 1 + +#define SUCCESSFUL_HTTP_STATUS(code) \ + ((code) >= MIN_SUCCESSFUL_HTTP_STATUS && \ + (code) <= MAX_SUCCESSFUL_HTTP_STATUS) + +#define INITIAL_DNS_MESSAGE_BUFFER_SIZE (512) + +typedef struct isc_nm_http_response_status { + size_t code; + size_t content_length; + bool content_type_valid; +} isc_nm_http_response_status_t; + +typedef struct http_cstream { + isc_nm_recv_cb_t read_cb; + void *read_cbarg; + isc_nm_cb_t connect_cb; + void *connect_cbarg; + + bool sending; + bool reading; + + char *uri; + isc_url_parser_t up; + + char *authority; + size_t authoritylen; + char *path; + + isc_buffer_t *rbuf; + + size_t pathlen; + int32_t stream_id; + + bool post; /* POST or GET */ + isc_buffer_t *postdata; + char *GET_path; + size_t GET_path_len; + + isc_nm_http_response_status_t response_status; + isc_nmsocket_t *httpsock; + LINK(struct http_cstream) link; +} http_cstream_t; + +#define HTTP2_SESSION_MAGIC ISC_MAGIC('H', '2', 'S', 'S') +#define VALID_HTTP2_SESSION(t) ISC_MAGIC_VALID(t, HTTP2_SESSION_MAGIC) + +typedef ISC_LIST(isc__nm_uvreq_t) isc__nm_http_pending_callbacks_t; + +struct isc_nm_http_session { + unsigned int magic; + isc_refcount_t references; + isc_mem_t *mctx; + + size_t sending; + bool reading; + bool closed; + bool closing; + + nghttp2_session *ngsession; + bool client; + + ISC_LIST(http_cstream_t) cstreams; + ISC_LIST(isc_nmsocket_h2_t) sstreams; + size_t nsstreams; + + isc_nmhandle_t *handle; + isc_nmhandle_t *client_httphandle; + isc_nmsocket_t *serversocket; + + isc_buffer_t *buf; + + isc_tlsctx_t *tlsctx; + uint32_t max_concurrent_streams; + + isc__nm_http_pending_callbacks_t pending_write_callbacks; + isc_buffer_t *pending_write_data; +}; + +typedef enum isc_http_error_responses { + ISC_HTTP_ERROR_SUCCESS, /* 200 */ + ISC_HTTP_ERROR_NOT_FOUND, /* 404 */ + ISC_HTTP_ERROR_PAYLOAD_TOO_LARGE, /* 413 */ + ISC_HTTP_ERROR_URI_TOO_LONG, /* 414 */ + ISC_HTTP_ERROR_UNSUPPORTED_MEDIA_TYPE, /* 415 */ + ISC_HTTP_ERROR_BAD_REQUEST, /* 400 */ + ISC_HTTP_ERROR_NOT_IMPLEMENTED, /* 501 */ + ISC_HTTP_ERROR_GENERIC, /* 500 Internal Server Error */ + ISC_HTTP_ERROR_MAX +} isc_http_error_responses_t; + +typedef struct isc_http_send_req { + isc_nm_http_session_t *session; + isc_nmhandle_t *transphandle; + isc_nmhandle_t *httphandle; + isc_nm_cb_t cb; + void *cbarg; + isc_buffer_t *pending_write_data; + isc__nm_http_pending_callbacks_t pending_write_callbacks; +} isc_http_send_req_t; + +#define HTTP_ENDPOINTS_MAGIC ISC_MAGIC('H', 'T', 'E', 'P') +#define VALID_HTTP_ENDPOINTS(t) ISC_MAGIC_VALID(t, HTTP_ENDPOINTS_MAGIC) + +static bool +http_send_outgoing(isc_nm_http_session_t *session, isc_nmhandle_t *httphandle, + isc_nm_cb_t cb, void *cbarg); + +static void +http_do_bio(isc_nm_http_session_t *session, isc_nmhandle_t *send_httphandle, + isc_nm_cb_t send_cb, void *send_cbarg); + +static void +failed_httpstream_read_cb(isc_nmsocket_t *sock, isc_result_t result, + isc_nm_http_session_t *session); + +static void +client_call_failed_read_cb(isc_result_t result, isc_nm_http_session_t *session); + +static void +server_call_failed_read_cb(isc_result_t result, isc_nm_http_session_t *session); + +static void +failed_read_cb(isc_result_t result, isc_nm_http_session_t *session); + +static isc_result_t +server_send_error_response(const isc_http_error_responses_t error, + nghttp2_session *ngsession, isc_nmsocket_t *socket); + +static isc_result_t +client_send(isc_nmhandle_t *handle, const isc_region_t *region); + +static void +finish_http_session(isc_nm_http_session_t *session); + +static void +http_transpost_tcp_nodelay(isc_nmhandle_t *transphandle); + +static void +call_pending_callbacks(isc__nm_http_pending_callbacks_t pending_callbacks, + isc_result_t result); + +static void +server_call_cb(isc_nmsocket_t *socket, isc_nm_http_session_t *session, + const isc_result_t result, isc_region_t *data); + +static isc_nm_httphandler_t * +http_endpoints_find(const char *request_path, + const isc_nm_http_endpoints_t *restrict eps); + +static void +http_init_listener_endpoints(isc_nmsocket_t *listener, + isc_nm_http_endpoints_t *epset); + +static void +http_cleanup_listener_endpoints(isc_nmsocket_t *listener); + +static isc_nm_http_endpoints_t * +http_get_listener_endpoints(isc_nmsocket_t *listener, const int tid); + +static bool +http_session_active(isc_nm_http_session_t *session) { + REQUIRE(VALID_HTTP2_SESSION(session)); + return (!session->closed && !session->closing); +} + +static void * +http_malloc(size_t sz, isc_mem_t *mctx) { + return (isc_mem_allocate(mctx, sz)); +} + +static void * +http_calloc(size_t n, size_t sz, isc_mem_t *mctx) { + const size_t msize = n * sz; + void *data = isc_mem_allocate(mctx, msize); + + memset(data, 0, msize); + return (data); +} + +static void * +http_realloc(void *p, size_t newsz, isc_mem_t *mctx) { + return (isc_mem_reallocate(mctx, p, newsz)); +} + +static void +http_free(void *p, isc_mem_t *mctx) { + if (p == NULL) { /* as standard free() behaves */ + return; + } + isc_mem_free(mctx, p); +} + +static void +init_nghttp2_mem(isc_mem_t *mctx, nghttp2_mem *mem) { + *mem = (nghttp2_mem){ .malloc = (nghttp2_malloc)http_malloc, + .calloc = (nghttp2_calloc)http_calloc, + .realloc = (nghttp2_realloc)http_realloc, + .free = (nghttp2_free)http_free, + .mem_user_data = mctx }; +} + +static void +new_session(isc_mem_t *mctx, isc_tlsctx_t *tctx, + isc_nm_http_session_t **sessionp) { + isc_nm_http_session_t *session = NULL; + + REQUIRE(sessionp != NULL && *sessionp == NULL); + REQUIRE(mctx != NULL); + + session = isc_mem_get(mctx, sizeof(isc_nm_http_session_t)); + *session = (isc_nm_http_session_t){ .magic = HTTP2_SESSION_MAGIC, + .tlsctx = tctx }; + isc_refcount_init(&session->references, 1); + isc_mem_attach(mctx, &session->mctx); + ISC_LIST_INIT(session->cstreams); + ISC_LIST_INIT(session->sstreams); + ISC_LIST_INIT(session->pending_write_callbacks); + + *sessionp = session; +} + +void +isc__nm_httpsession_attach(isc_nm_http_session_t *source, + isc_nm_http_session_t **targetp) { + REQUIRE(VALID_HTTP2_SESSION(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + isc_refcount_increment(&source->references); + + *targetp = source; +} + +void +isc__nm_httpsession_detach(isc_nm_http_session_t **sessionp) { + isc_nm_http_session_t *session = NULL; + + REQUIRE(sessionp != NULL); + + session = *sessionp; + *sessionp = NULL; + + REQUIRE(VALID_HTTP2_SESSION(session)); + + if (isc_refcount_decrement(&session->references) > 1) { + return; + } + + finish_http_session(session); + + INSIST(ISC_LIST_EMPTY(session->sstreams)); + INSIST(ISC_LIST_EMPTY(session->cstreams)); + + if (session->ngsession != NULL) { + nghttp2_session_del(session->ngsession); + session->ngsession = NULL; + } + + if (session->buf != NULL) { + isc_buffer_free(&session->buf); + } + + /* We need an acquire memory barrier here */ + (void)isc_refcount_current(&session->references); + + session->magic = 0; + isc_mem_putanddetach(&session->mctx, session, + sizeof(isc_nm_http_session_t)); +} + +static http_cstream_t * +find_http_cstream(int32_t stream_id, isc_nm_http_session_t *session) { + http_cstream_t *cstream = NULL; + REQUIRE(VALID_HTTP2_SESSION(session)); + + if (ISC_LIST_EMPTY(session->cstreams)) { + return (NULL); + } + + for (cstream = ISC_LIST_HEAD(session->cstreams); cstream != NULL; + cstream = ISC_LIST_NEXT(cstream, link)) + { + if (cstream->stream_id == stream_id) { + break; + } + } + + /* LRU-like behaviour */ + if (cstream && ISC_LIST_HEAD(session->cstreams) != cstream) { + ISC_LIST_UNLINK(session->cstreams, cstream, link); + ISC_LIST_PREPEND(session->cstreams, cstream, link); + } + + return (cstream); +} + +static isc_result_t +new_http_cstream(isc_nmsocket_t *sock, http_cstream_t **streamp) { + isc_mem_t *mctx = sock->mgr->mctx; + const char *uri = NULL; + bool post; + http_cstream_t *stream = NULL; + isc_result_t result; + + uri = sock->h2.session->handle->sock->h2.connect.uri; + post = sock->h2.session->handle->sock->h2.connect.post; + + stream = isc_mem_get(mctx, sizeof(http_cstream_t)); + *stream = (http_cstream_t){ .stream_id = -1, + .post = post, + .uri = isc_mem_strdup(mctx, uri) }; + ISC_LINK_INIT(stream, link); + + result = isc_url_parse(stream->uri, strlen(stream->uri), 0, + &stream->up); + if (result != ISC_R_SUCCESS) { + isc_mem_free(mctx, stream->uri); + isc_mem_put(mctx, stream, sizeof(http_cstream_t)); + return (result); + } + + isc__nmsocket_attach(sock, &stream->httpsock); + stream->authoritylen = stream->up.field_data[ISC_UF_HOST].len; + stream->authority = isc_mem_get(mctx, stream->authoritylen + AUTHEXTRA); + memmove(stream->authority, &uri[stream->up.field_data[ISC_UF_HOST].off], + stream->up.field_data[ISC_UF_HOST].len); + + if (stream->up.field_set & (1 << ISC_UF_PORT)) { + stream->authoritylen += (size_t)snprintf( + stream->authority + + stream->up.field_data[ISC_UF_HOST].len, + AUTHEXTRA, ":%u", stream->up.port); + } + + /* If we don't have path in URI, we use "/" as path. */ + stream->pathlen = 1; + if (stream->up.field_set & (1 << ISC_UF_PATH)) { + stream->pathlen = stream->up.field_data[ISC_UF_PATH].len; + } + if (stream->up.field_set & (1 << ISC_UF_QUERY)) { + /* +1 for '?' character */ + stream->pathlen += + (size_t)(stream->up.field_data[ISC_UF_QUERY].len + 1); + } + + stream->path = isc_mem_get(mctx, stream->pathlen); + if (stream->up.field_set & (1 << ISC_UF_PATH)) { + memmove(stream->path, + &uri[stream->up.field_data[ISC_UF_PATH].off], + stream->up.field_data[ISC_UF_PATH].len); + } else { + stream->path[0] = '/'; + } + + if (stream->up.field_set & (1 << ISC_UF_QUERY)) { + stream->path[stream->pathlen - + stream->up.field_data[ISC_UF_QUERY].len - 1] = '?'; + memmove(stream->path + stream->pathlen - + stream->up.field_data[ISC_UF_QUERY].len, + &uri[stream->up.field_data[ISC_UF_QUERY].off], + stream->up.field_data[ISC_UF_QUERY].len); + } + + isc_buffer_allocate(mctx, &stream->rbuf, + INITIAL_DNS_MESSAGE_BUFFER_SIZE); + isc_buffer_setautorealloc(stream->rbuf, true); + + ISC_LIST_PREPEND(sock->h2.session->cstreams, stream, link); + *streamp = stream; + + return (ISC_R_SUCCESS); +} + +static void +put_http_cstream(isc_mem_t *mctx, http_cstream_t *stream) { + isc_mem_put(mctx, stream->path, stream->pathlen); + isc_mem_put(mctx, stream->authority, + stream->up.field_data[ISC_UF_HOST].len + AUTHEXTRA); + isc_mem_free(mctx, stream->uri); + if (stream->GET_path != NULL) { + isc_mem_free(mctx, stream->GET_path); + stream->GET_path = NULL; + stream->GET_path_len = 0; + } + + if (stream->postdata != NULL) { + INSIST(stream->post); + isc_buffer_free(&stream->postdata); + } + + if (stream == stream->httpsock->h2.connect.cstream) { + stream->httpsock->h2.connect.cstream = NULL; + } + if (ISC_LINK_LINKED(stream, link)) { + ISC_LIST_UNLINK(stream->httpsock->h2.session->cstreams, stream, + link); + } + isc__nmsocket_detach(&stream->httpsock); + + isc_buffer_free(&stream->rbuf); + isc_mem_put(mctx, stream, sizeof(http_cstream_t)); +} + +static void +finish_http_session(isc_nm_http_session_t *session) { + if (session->closed) { + return; + } + + if (session->handle != NULL) { + if (!session->closed) { + session->closed = true; + isc_nm_cancelread(session->handle); + } + + if (session->client) { + client_call_failed_read_cb(ISC_R_UNEXPECTED, session); + } else { + server_call_failed_read_cb(ISC_R_UNEXPECTED, session); + } + + call_pending_callbacks(session->pending_write_callbacks, + ISC_R_UNEXPECTED); + ISC_LIST_INIT(session->pending_write_callbacks); + + if (session->pending_write_data != NULL) { + isc_buffer_free(&session->pending_write_data); + } + + isc_nmhandle_detach(&session->handle); + } + + if (session->client_httphandle != NULL) { + isc_nmhandle_detach(&session->client_httphandle); + } + + INSIST(ISC_LIST_EMPTY(session->cstreams)); + + /* detach from server socket */ + if (session->serversocket != NULL) { + isc__nmsocket_detach(&session->serversocket); + } + session->closed = true; +} + +static int +on_client_data_chunk_recv_callback(int32_t stream_id, const uint8_t *data, + size_t len, isc_nm_http_session_t *session) { + http_cstream_t *cstream = find_http_cstream(stream_id, session); + + if (cstream != NULL) { + size_t new_rbufsize = len; + INSIST(cstream->rbuf != NULL); + new_rbufsize += isc_buffer_usedlength(cstream->rbuf); + if (new_rbufsize <= MAX_DNS_MESSAGE_SIZE && + new_rbufsize <= cstream->response_status.content_length) + { + isc_buffer_putmem(cstream->rbuf, data, len); + } else { + return (NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE); + } + } else { + return (NGHTTP2_ERR_CALLBACK_FAILURE); + } + + return (0); +} + +static int +on_server_data_chunk_recv_callback(int32_t stream_id, const uint8_t *data, + size_t len, isc_nm_http_session_t *session) { + isc_nmsocket_h2_t *h2 = ISC_LIST_HEAD(session->sstreams); + while (h2 != NULL) { + if (stream_id == h2->stream_id) { + if (isc_buffer_base(&h2->rbuf) == NULL) { + isc_buffer_init( + &h2->rbuf, + isc_mem_allocate(session->mctx, + h2->content_length), + MAX_DNS_MESSAGE_SIZE); + } + size_t new_bufsize = isc_buffer_usedlength(&h2->rbuf) + + len; + if (new_bufsize <= MAX_DNS_MESSAGE_SIZE && + new_bufsize <= h2->content_length) + { + isc_buffer_putmem(&h2->rbuf, data, len); + break; + } + + return (NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE); + } + h2 = ISC_LIST_NEXT(h2, link); + } + if (h2 == NULL) { + return (NGHTTP2_ERR_CALLBACK_FAILURE); + } + + return (0); +} + +static int +on_data_chunk_recv_callback(nghttp2_session *ngsession, uint8_t flags, + int32_t stream_id, const uint8_t *data, size_t len, + void *user_data) { + isc_nm_http_session_t *session = (isc_nm_http_session_t *)user_data; + int rv; + + UNUSED(ngsession); + UNUSED(flags); + + if (session->client) { + rv = on_client_data_chunk_recv_callback(stream_id, data, len, + session); + } else { + rv = on_server_data_chunk_recv_callback(stream_id, data, len, + session); + } + + return (rv); +} + +static void +call_unlink_cstream_readcb(http_cstream_t *cstream, + isc_nm_http_session_t *session, + isc_result_t result) { + isc_region_t read_data; + REQUIRE(VALID_HTTP2_SESSION(session)); + REQUIRE(cstream != NULL); + ISC_LIST_UNLINK(session->cstreams, cstream, link); + INSIST(VALID_NMHANDLE(session->client_httphandle)); + isc_buffer_usedregion(cstream->rbuf, &read_data); + cstream->read_cb(session->client_httphandle, result, &read_data, + cstream->read_cbarg); + put_http_cstream(session->mctx, cstream); +} + +static int +on_client_stream_close_callback(int32_t stream_id, + isc_nm_http_session_t *session) { + http_cstream_t *cstream = find_http_cstream(stream_id, session); + + if (cstream != NULL) { + isc_result_t result = + SUCCESSFUL_HTTP_STATUS(cstream->response_status.code) + ? ISC_R_SUCCESS + : ISC_R_FAILURE; + call_unlink_cstream_readcb(cstream, session, result); + if (ISC_LIST_EMPTY(session->cstreams)) { + int rv = 0; + rv = nghttp2_session_terminate_session( + session->ngsession, NGHTTP2_NO_ERROR); + if (rv != 0) { + return (rv); + } + /* Mark the session as closing one to finish it on a + * subsequent call to http_do_bio() */ + session->closing = true; + } + } else { + return (NGHTTP2_ERR_CALLBACK_FAILURE); + } + + return (0); +} + +static int +on_server_stream_close_callback(int32_t stream_id, + isc_nm_http_session_t *session) { + isc_nmsocket_t *sock = nghttp2_session_get_stream_user_data( + session->ngsession, stream_id); + int rv = 0; + + ISC_LIST_UNLINK(session->sstreams, &sock->h2, link); + session->nsstreams--; + + /* + * By making a call to isc__nmsocket_prep_destroy(), we ensure that + * the socket gets marked as inactive, allowing the HTTP/2 data + * associated with it to be properly disposed of eventually. + * + * An HTTP/2 stream socket will normally be marked as inactive in + * the normal course of operation. However, when browsers terminate + * HTTP/2 streams prematurely (e.g. by sending RST_STREAM), + * corresponding sockets can remain marked as active, retaining + * references to the HTTP/2 data (most notably the session objects), + * preventing them from being correctly freed and leading to BIND + * hanging on shutdown. Calling isc__nmsocket_prep_destroy() + * ensures that this will not happen. + */ + isc__nmsocket_prep_destroy(sock); + isc__nmsocket_detach(&sock); + return (rv); +} + +static int +on_stream_close_callback(nghttp2_session *ngsession, int32_t stream_id, + uint32_t error_code, void *user_data) { + isc_nm_http_session_t *session = (isc_nm_http_session_t *)user_data; + int rv = 0; + + REQUIRE(VALID_HTTP2_SESSION(session)); + REQUIRE(session->ngsession == ngsession); + + UNUSED(error_code); + + if (session->client) { + rv = on_client_stream_close_callback(stream_id, session); + } else { + rv = on_server_stream_close_callback(stream_id, session); + } + + return (rv); +} + +static bool +client_handle_status_header(http_cstream_t *cstream, const uint8_t *value, + const size_t valuelen) { + char tmp[32] = { 0 }; + const size_t tmplen = sizeof(tmp) - 1; + + strncpy(tmp, (const char *)value, ISC_MIN(tmplen, valuelen)); + cstream->response_status.code = strtoul(tmp, NULL, 10); + + if (SUCCESSFUL_HTTP_STATUS(cstream->response_status.code)) { + return (true); + } + + return (false); +} + +static bool +client_handle_content_length_header(http_cstream_t *cstream, + const uint8_t *value, + const size_t valuelen) { + char tmp[32] = { 0 }; + const size_t tmplen = sizeof(tmp) - 1; + + strncpy(tmp, (const char *)value, ISC_MIN(tmplen, valuelen)); + cstream->response_status.content_length = strtoul(tmp, NULL, 10); + + if (cstream->response_status.content_length == 0 || + cstream->response_status.content_length > MAX_DNS_MESSAGE_SIZE) + { + return (false); + } + + return (true); +} + +static bool +client_handle_content_type_header(http_cstream_t *cstream, const uint8_t *value, + const size_t valuelen) { + const char type_dns_message[] = DNS_MEDIA_TYPE; + const size_t len = sizeof(type_dns_message) - 1; + + UNUSED(valuelen); + + if (strncasecmp((const char *)value, type_dns_message, len) == 0) { + cstream->response_status.content_type_valid = true; + return (true); + } + + return (false); +} + +static int +client_on_header_callback(nghttp2_session *ngsession, + const nghttp2_frame *frame, const uint8_t *name, + size_t namelen, const uint8_t *value, size_t valuelen, + uint8_t flags, void *user_data) { + isc_nm_http_session_t *session = (isc_nm_http_session_t *)user_data; + http_cstream_t *cstream = NULL; + const char status[] = ":status"; + const char content_length[] = "Content-Length"; + const char content_type[] = "Content-Type"; + bool header_ok = true; + + REQUIRE(VALID_HTTP2_SESSION(session)); + REQUIRE(session->client); + + UNUSED(flags); + UNUSED(ngsession); + + cstream = find_http_cstream(frame->hd.stream_id, session); + if (cstream == NULL) { + /* + * This could happen in two cases: + * - the server sent us bad data, or + * - we closed the session prematurely before receiving all + * responses (i.e., because of a belated or partial response). + */ + return (NGHTTP2_ERR_CALLBACK_FAILURE); + } + + INSIST(!ISC_LIST_EMPTY(session->cstreams)); + + switch (frame->hd.type) { + case NGHTTP2_HEADERS: + if (frame->headers.cat != NGHTTP2_HCAT_RESPONSE) { + break; + } + + if (HEADER_MATCH(status, name, namelen)) { + header_ok = client_handle_status_header(cstream, value, + valuelen); + } else if (HEADER_MATCH(content_length, name, namelen)) { + header_ok = client_handle_content_length_header( + cstream, value, valuelen); + } else if (HEADER_MATCH(content_type, name, namelen)) { + header_ok = client_handle_content_type_header( + cstream, value, valuelen); + } + break; + } + + if (!header_ok) { + return (NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE); + } + + return (0); +} + +static void +initialize_nghttp2_client_session(isc_nm_http_session_t *session) { + nghttp2_session_callbacks *callbacks = NULL; + nghttp2_option *option = NULL; + nghttp2_mem mem; + + init_nghttp2_mem(session->mctx, &mem); + RUNTIME_CHECK(nghttp2_session_callbacks_new(&callbacks) == 0); + RUNTIME_CHECK(nghttp2_option_new(&option) == 0); + +#if NGHTTP2_VERSION_NUM >= (0x010c00) + nghttp2_option_set_max_send_header_block_length( + option, MAX_ALLOWED_DATA_IN_HEADERS); +#endif + + nghttp2_session_callbacks_set_on_data_chunk_recv_callback( + callbacks, on_data_chunk_recv_callback); + + nghttp2_session_callbacks_set_on_stream_close_callback( + callbacks, on_stream_close_callback); + + nghttp2_session_callbacks_set_on_header_callback( + callbacks, client_on_header_callback); + + RUNTIME_CHECK(nghttp2_session_client_new3(&session->ngsession, + callbacks, session, option, + &mem) == 0); + + nghttp2_option_del(option); + nghttp2_session_callbacks_del(callbacks); +} + +static bool +send_client_connection_header(isc_nm_http_session_t *session) { + nghttp2_settings_entry iv[] = { { NGHTTP2_SETTINGS_ENABLE_PUSH, 0 } }; + int rv; + + rv = nghttp2_submit_settings(session->ngsession, NGHTTP2_FLAG_NONE, iv, + sizeof(iv) / sizeof(iv[0])); + if (rv != 0) { + return (false); + } + + return (true); +} + +#define MAKE_NV(NAME, VALUE, VALUELEN) \ + { \ + (uint8_t *)(uintptr_t)(NAME), (uint8_t *)(uintptr_t)(VALUE), \ + sizeof(NAME) - 1, VALUELEN, NGHTTP2_NV_FLAG_NONE \ + } + +#define MAKE_NV2(NAME, VALUE) \ + { \ + (uint8_t *)(uintptr_t)(NAME), (uint8_t *)(uintptr_t)(VALUE), \ + sizeof(NAME) - 1, sizeof(VALUE) - 1, \ + NGHTTP2_NV_FLAG_NONE \ + } + +static ssize_t +client_read_callback(nghttp2_session *ngsession, int32_t stream_id, + uint8_t *buf, size_t length, uint32_t *data_flags, + nghttp2_data_source *source, void *user_data) { + isc_nm_http_session_t *session = (isc_nm_http_session_t *)user_data; + http_cstream_t *cstream = NULL; + + REQUIRE(session->client); + REQUIRE(!ISC_LIST_EMPTY(session->cstreams)); + + UNUSED(ngsession); + UNUSED(source); + + cstream = find_http_cstream(stream_id, session); + if (!cstream || cstream->stream_id != stream_id) { + /* We haven't found the stream, so we are not reading */ + return (NGHTTP2_ERR_CALLBACK_FAILURE); + } + + if (cstream->post) { + size_t len = isc_buffer_remaininglength(cstream->postdata); + + if (len > length) { + len = length; + } + + if (len > 0) { + memmove(buf, isc_buffer_current(cstream->postdata), + len); + isc_buffer_forward(cstream->postdata, len); + } + + if (isc_buffer_remaininglength(cstream->postdata) == 0) { + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + } + + return (len); + } else { + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + return (0); + } + + return (0); +} + +/* + * Send HTTP request to the remote peer. + */ +static isc_result_t +client_submit_request(isc_nm_http_session_t *session, http_cstream_t *stream) { + int32_t stream_id; + char *uri = stream->uri; + isc_url_parser_t *up = &stream->up; + nghttp2_data_provider dp; + + if (stream->post) { + char p[64]; + snprintf(p, sizeof(p), "%u", + isc_buffer_usedlength(stream->postdata)); + nghttp2_nv hdrs[] = { + MAKE_NV2(":method", "POST"), + MAKE_NV(":scheme", + &uri[up->field_data[ISC_UF_SCHEMA].off], + up->field_data[ISC_UF_SCHEMA].len), + MAKE_NV(":authority", stream->authority, + stream->authoritylen), + MAKE_NV(":path", stream->path, stream->pathlen), + MAKE_NV2("content-type", DNS_MEDIA_TYPE), + MAKE_NV2("accept", DNS_MEDIA_TYPE), + MAKE_NV("content-length", p, strlen(p)), + MAKE_NV2("cache-control", DEFAULT_CACHE_CONTROL) + }; + + dp = (nghttp2_data_provider){ .read_callback = + client_read_callback }; + stream_id = nghttp2_submit_request( + session->ngsession, NULL, hdrs, + sizeof(hdrs) / sizeof(hdrs[0]), &dp, stream); + } else { + INSIST(stream->GET_path != NULL); + INSIST(stream->GET_path_len != 0); + nghttp2_nv hdrs[] = { + MAKE_NV2(":method", "GET"), + MAKE_NV(":scheme", + &uri[up->field_data[ISC_UF_SCHEMA].off], + up->field_data[ISC_UF_SCHEMA].len), + MAKE_NV(":authority", stream->authority, + stream->authoritylen), + MAKE_NV(":path", stream->GET_path, + stream->GET_path_len), + MAKE_NV2("accept", DNS_MEDIA_TYPE), + MAKE_NV2("cache-control", DEFAULT_CACHE_CONTROL) + }; + + dp = (nghttp2_data_provider){ .read_callback = + client_read_callback }; + stream_id = nghttp2_submit_request( + session->ngsession, NULL, hdrs, + sizeof(hdrs) / sizeof(hdrs[0]), &dp, stream); + } + if (stream_id < 0) { + return (ISC_R_FAILURE); + } + + stream->stream_id = stream_id; + + return (ISC_R_SUCCESS); +} + +/* + * Read callback from TLS socket. + */ +static void +http_readcb(isc_nmhandle_t *handle, isc_result_t result, isc_region_t *region, + void *data) { + isc_nm_http_session_t *session = (isc_nm_http_session_t *)data; + ssize_t readlen; + + REQUIRE(VALID_HTTP2_SESSION(session)); + + UNUSED(handle); + + if (result != ISC_R_SUCCESS) { + if (result != ISC_R_TIMEDOUT) { + session->reading = false; + } + failed_read_cb(result, session); + return; + } + + readlen = nghttp2_session_mem_recv(session->ngsession, region->base, + region->length); + if (readlen < 0) { + failed_read_cb(ISC_R_UNEXPECTED, session); + return; + } + + if ((size_t)readlen < region->length) { + size_t unread_size = region->length - readlen; + if (session->buf == NULL) { + isc_buffer_allocate(session->mctx, &session->buf, + unread_size); + isc_buffer_setautorealloc(session->buf, true); + } + isc_buffer_putmem(session->buf, region->base + readlen, + unread_size); + isc_nm_pauseread(session->handle); + } + + /* We might have something to receive or send, do IO */ + http_do_bio(session, NULL, NULL, NULL); +} + +static void +call_pending_callbacks(isc__nm_http_pending_callbacks_t pending_callbacks, + isc_result_t result) { + isc__nm_uvreq_t *cbreq = ISC_LIST_HEAD(pending_callbacks); + while (cbreq != NULL) { + isc__nm_uvreq_t *next = ISC_LIST_NEXT(cbreq, link); + ISC_LIST_UNLINK(pending_callbacks, cbreq, link); + isc__nm_sendcb(cbreq->handle->sock, cbreq, result, false); + cbreq = next; + } +} + +static void +http_writecb(isc_nmhandle_t *handle, isc_result_t result, void *arg) { + isc_http_send_req_t *req = (isc_http_send_req_t *)arg; + isc_nm_http_session_t *session = req->session; + isc_nmhandle_t *transphandle = req->transphandle; + + REQUIRE(VALID_HTTP2_SESSION(session)); + REQUIRE(VALID_NMHANDLE(handle)); + + if (http_session_active(session)) { + INSIST(session->handle == handle); + } + + call_pending_callbacks(req->pending_write_callbacks, result); + + if (req->cb != NULL) { + req->cb(req->httphandle, result, req->cbarg); + isc_nmhandle_detach(&req->httphandle); + } + + isc_buffer_free(&req->pending_write_data); + isc_mem_put(session->mctx, req, sizeof(*req)); + + session->sending--; + http_do_bio(session, NULL, NULL, NULL); + isc_nmhandle_detach(&transphandle); + if (result != ISC_R_SUCCESS && session->sending == 0) { + finish_http_session(session); + } + isc__nm_httpsession_detach(&session); +} + +static void +move_pending_send_callbacks(isc_nm_http_session_t *session, + isc_http_send_req_t *send) { + STATIC_ASSERT( + sizeof(session->pending_write_callbacks) == + sizeof(send->pending_write_callbacks), + "size of pending writes requests callbacks lists differs"); + memmove(&send->pending_write_callbacks, + &session->pending_write_callbacks, + sizeof(session->pending_write_callbacks)); + ISC_LIST_INIT(session->pending_write_callbacks); +} + +static bool +http_send_outgoing(isc_nm_http_session_t *session, isc_nmhandle_t *httphandle, + isc_nm_cb_t cb, void *cbarg) { + isc_http_send_req_t *send = NULL; + size_t total = 0; + isc_region_t send_data = { 0 }; + isc_nmhandle_t *transphandle = NULL; +#ifdef ENABLE_HTTP_WRITE_BUFFERING + size_t max_total_write_size = 0; +#endif /* ENABLE_HTTP_WRITE_BUFFERING */ + + if (!http_session_active(session) || + (!nghttp2_session_want_write(session->ngsession) && + session->pending_write_data == NULL)) + { + return (false); + } + + /* We need to attach to the session->handle earlier because as an + * indirect result of the nghttp2_session_mem_send() the session + * might get closed and the handle detached. However, there is + * still some outgoing data to handle and we need to call it + * anyway if only to get the write callback passed here to get + * called properly. */ + isc_nmhandle_attach(session->handle, &transphandle); + + while (nghttp2_session_want_write(session->ngsession)) { + const uint8_t *data = NULL; + const size_t pending = + nghttp2_session_mem_send(session->ngsession, &data); + const size_t new_total = total + pending; + + /* Sometimes nghttp2_session_mem_send() does not return any + * data to send even though nghttp2_session_want_write() + * returns success. */ + if (pending == 0 || data == NULL) { + break; + } + + /* reallocate buffer if required */ + if (session->pending_write_data == NULL) { + isc_buffer_allocate(session->mctx, + &session->pending_write_data, + INITIAL_DNS_MESSAGE_BUFFER_SIZE); + isc_buffer_setautorealloc(session->pending_write_data, + true); + } + isc_buffer_putmem(session->pending_write_data, data, pending); + total = new_total; + } + +#ifdef ENABLE_HTTP_WRITE_BUFFERING + if (session->pending_write_data != NULL) { + max_total_write_size = + isc_buffer_usedlength(session->pending_write_data); + } + + /* Here we are trying to flush the pending writes buffer earlier + * to avoid hitting unnecessary limitations on a TLS record size + * within some tools (e.g. flamethrower). */ + if (max_total_write_size >= FLUSH_HTTP_WRITE_BUFFER_AFTER) { + /* Case 1: We have equal or more than + * FLUSH_HTTP_WRITE_BUFFER_AFTER bytes to send. Let's flush it. + */ + total = max_total_write_size; + } else if (session->sending > 0 && total > 0) { + /* Case 2: There is one or more write requests in flight and + * we have some new data form nghttp2 to send. Let's put the + * write callback (if any) into the pending write callbacks + * list. Then let's return from the function: as soon as the + * "in-flight" write callback get's called or we have reached + * FLUSH_HTTP_WRITE_BUFFER_AFTER bytes in the write buffer, we + * will flush the buffer. */ + if (cb != NULL) { + isc__nm_uvreq_t *newcb = isc__nm_uvreq_get( + httphandle->sock->mgr, httphandle->sock); + + INSIST(VALID_NMHANDLE(httphandle)); + newcb->cb.send = cb; + newcb->cbarg = cbarg; + isc_nmhandle_attach(httphandle, &newcb->handle); + ISC_LIST_APPEND(session->pending_write_callbacks, newcb, + link); + } + goto nothing_to_send; + } else if (session->sending == 0 && total == 0 && + session->pending_write_data != NULL) + { + /* Case 3: There is no write in flight and we haven't got + * anything new from nghttp2, but there is some data pending + * in the write buffer. Let's flush the buffer. */ + isc_region_t region = { 0 }; + total = isc_buffer_usedlength(session->pending_write_data); + INSIST(total > 0); + isc_buffer_usedregion(session->pending_write_data, ®ion); + INSIST(total == region.length); + } else { + /* The other cases are, uninteresting, fall-through ones. */ + /* In the following cases (4-6) we will just bail out. */ + /* Case 4: There is nothing new to send, nor anything in the + * write buffer. */ + /* Case 5: There is nothing new to send and there is write + * request(s) in flight. */ + /* Case 6: There is nothing new to send nor there are any + * write requests in flight. */ + + /* Case 7: There is some new data to send and there are no any + * write requests in flight: Let's send the data.*/ + INSIST((total == 0 && session->pending_write_data == NULL) || + (total == 0 && session->sending > 0) || + (total == 0 && session->sending == 0) || + (total > 0 && session->sending == 0)); + } +#else + INSIST(ISC_LIST_EMPTY(session->pending_write_callbacks)); +#endif /* ENABLE_HTTP_WRITE_BUFFERING */ + + if (total == 0) { + /* No data returned */ + goto nothing_to_send; + } + + /* If we have reached the point it means that we need to send some + * data and flush the outgoing buffer. The code below does that. */ + send = isc_mem_get(session->mctx, sizeof(*send)); + + *send = (isc_http_send_req_t){ .pending_write_data = + session->pending_write_data, + .cb = cb, + .cbarg = cbarg }; + session->pending_write_data = NULL; + move_pending_send_callbacks(session, send); + + send->transphandle = transphandle; + isc__nm_httpsession_attach(session, &send->session); + + if (cb != NULL) { + INSIST(VALID_NMHANDLE(httphandle)); + isc_nmhandle_attach(httphandle, &send->httphandle); + } + + session->sending++; + isc_buffer_usedregion(send->pending_write_data, &send_data); + isc_nm_send(transphandle, &send_data, http_writecb, send); + return (true); +nothing_to_send: + isc_nmhandle_detach(&transphandle); + return (false); +} + +static void +http_do_bio(isc_nm_http_session_t *session, isc_nmhandle_t *send_httphandle, + isc_nm_cb_t send_cb, void *send_cbarg) { + REQUIRE(VALID_HTTP2_SESSION(session)); + + if (session->closed) { + return; + } else if (session->closing) { + /* + * There might be leftover callbacks waiting to be received + */ + if (session->sending == 0) { + finish_http_session(session); + } + return; + } else if (nghttp2_session_want_read(session->ngsession) == 0 && + nghttp2_session_want_write(session->ngsession) == 0 && + session->pending_write_data == NULL) + { + session->closing = true; + return; + } + + if (nghttp2_session_want_read(session->ngsession) != 0) { + if (!session->reading) { + /* We have not yet started reading from this handle */ + isc_nm_read(session->handle, http_readcb, session); + session->reading = true; + } else if (session->buf != NULL) { + size_t remaining = + isc_buffer_remaininglength(session->buf); + /* Leftover data in the buffer, use it */ + size_t readlen = nghttp2_session_mem_recv( + session->ngsession, + isc_buffer_current(session->buf), remaining); + + if (readlen == remaining) { + isc_buffer_free(&session->buf); + } else { + isc_buffer_forward(session->buf, readlen); + } + + http_do_bio(session, send_httphandle, send_cb, + send_cbarg); + return; + } else { + /* Resume reading, it's idempotent, wait for more */ + isc_nm_resumeread(session->handle); + } + } else { + /* We don't want more data, stop reading for now */ + isc_nm_pauseread(session->handle); + } + + if (send_cb != NULL) { + INSIST(VALID_NMHANDLE(send_httphandle)); + (void)http_send_outgoing(session, send_httphandle, send_cb, + send_cbarg); + } else { + INSIST(send_httphandle == NULL); + INSIST(send_cb == NULL); + INSIST(send_cbarg == NULL); + (void)http_send_outgoing(session, NULL, NULL, NULL); + } + + return; +} + +static isc_result_t +get_http_cstream(isc_nmsocket_t *sock, http_cstream_t **streamp) { + http_cstream_t *cstream = sock->h2.connect.cstream; + isc_result_t result; + + REQUIRE(streamp != NULL && *streamp == NULL); + + sock->h2.connect.cstream = NULL; + + if (cstream == NULL) { + result = new_http_cstream(sock, &cstream); + if (result != ISC_R_SUCCESS) { + INSIST(cstream == NULL); + return (result); + } + } + + *streamp = cstream; + return (ISC_R_SUCCESS); +} + +static void +http_call_connect_cb(isc_nmsocket_t *sock, isc_nm_http_session_t *session, + isc_result_t result) { + isc__nm_uvreq_t *req = NULL; + isc_nmhandle_t *httphandle = isc__nmhandle_get(sock, &sock->peer, + &sock->iface); + + REQUIRE(sock->connect_cb != NULL); + + if (result == ISC_R_SUCCESS) { + req = isc__nm_uvreq_get(sock->mgr, sock); + req->cb.connect = sock->connect_cb; + req->cbarg = sock->connect_cbarg; + if (session != NULL) { + session->client_httphandle = httphandle; + req->handle = NULL; + isc_nmhandle_attach(httphandle, &req->handle); + } else { + req->handle = httphandle; + } + + isc__nmsocket_clearcb(sock); + isc__nm_connectcb(sock, req, result, true); + } else { + void *cbarg = sock->connect_cbarg; + isc_nm_cb_t connect_cb = sock->connect_cb; + + isc__nmsocket_clearcb(sock); + connect_cb(httphandle, result, cbarg); + isc_nmhandle_detach(&httphandle); + } +} + +static void +transport_connect_cb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) { + isc_nmsocket_t *http_sock = (isc_nmsocket_t *)cbarg; + isc_nmsocket_t *transp_sock = NULL; + isc_nm_http_session_t *session = NULL; + http_cstream_t *cstream = NULL; + isc_mem_t *mctx = NULL; + + REQUIRE(VALID_NMSOCK(http_sock)); + REQUIRE(VALID_NMHANDLE(handle)); + + transp_sock = handle->sock; + + REQUIRE(VALID_NMSOCK(transp_sock)); + + mctx = transp_sock->mgr->mctx; + + INSIST(http_sock->h2.connect.uri != NULL); + + http_sock->tid = transp_sock->tid; + http_sock->h2.connect.tls_peer_verify_string = + isc_nm_verify_tls_peer_result_string(handle); + if (result != ISC_R_SUCCESS) { + goto error; + } + + new_session(mctx, http_sock->h2.connect.tlsctx, &session); + session->client = true; + transp_sock->h2.session = session; + http_sock->h2.connect.tlsctx = NULL; + /* otherwise we will get some garbage output in DIG */ + http_sock->iface = handle->sock->iface; + http_sock->peer = handle->sock->peer; + + transp_sock->h2.connect.post = http_sock->h2.connect.post; + transp_sock->h2.connect.uri = http_sock->h2.connect.uri; + http_sock->h2.connect.uri = NULL; + isc__nm_httpsession_attach(session, &http_sock->h2.session); + + if (session->tlsctx != NULL) { + const unsigned char *alpn = NULL; + unsigned int alpnlen = 0; + + INSIST(transp_sock->type == isc_nm_tlssocket); + + isc_tls_get_selected_alpn(transp_sock->tlsstream.tls, &alpn, + &alpnlen); + if (alpn == NULL || alpnlen != NGHTTP2_PROTO_VERSION_ID_LEN || + memcmp(NGHTTP2_PROTO_VERSION_ID, alpn, + NGHTTP2_PROTO_VERSION_ID_LEN) != 0) + { + /* + * HTTP/2 negotiation error. Any sensible DoH + * client will fail if HTTP/2 cannot be + * negotiated via ALPN. + */ + result = ISC_R_HTTP2ALPNERROR; + goto error; + } + } + + isc_nmhandle_attach(handle, &session->handle); + + initialize_nghttp2_client_session(session); + if (!send_client_connection_header(session)) { + goto error; + } + + result = get_http_cstream(http_sock, &cstream); + http_sock->h2.connect.cstream = cstream; + if (result != ISC_R_SUCCESS) { + goto error; + } + + http_transpost_tcp_nodelay(handle); + + http_call_connect_cb(http_sock, session, result); + + http_do_bio(session, NULL, NULL, NULL); + isc__nmsocket_detach(&http_sock); + return; + +error: + http_call_connect_cb(http_sock, session, result); + + if (http_sock->h2.connect.uri != NULL) { + isc_mem_free(mctx, http_sock->h2.connect.uri); + } + + isc__nmsocket_prep_destroy(http_sock); + isc__nmsocket_detach(&http_sock); +} + +void +isc_nm_httpconnect(isc_nm_t *mgr, isc_sockaddr_t *local, isc_sockaddr_t *peer, + const char *uri, bool post, isc_nm_cb_t cb, void *cbarg, + isc_tlsctx_t *tlsctx, + isc_tlsctx_client_session_cache_t *client_sess_cache, + unsigned int timeout, size_t extrahandlesize) { + isc_sockaddr_t local_interface; + isc_nmsocket_t *sock = NULL; + + REQUIRE(VALID_NM(mgr)); + REQUIRE(cb != NULL); + REQUIRE(peer != NULL); + REQUIRE(uri != NULL); + REQUIRE(*uri != '\0'); + + if (local == NULL) { + isc_sockaddr_anyofpf(&local_interface, peer->type.sa.sa_family); + local = &local_interface; + } + + sock = isc_mem_get(mgr->mctx, sizeof(*sock)); + isc__nmsocket_init(sock, mgr, isc_nm_httpsocket, local); + + sock->extrahandlesize = extrahandlesize; + sock->connect_timeout = timeout; + sock->result = ISC_R_UNSET; + sock->connect_cb = cb; + sock->connect_cbarg = cbarg; + atomic_init(&sock->client, true); + + if (isc__nm_closing(sock)) { + isc__nm_uvreq_t *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); + + if (isc__nm_in_netthread()) { + sock->tid = isc_nm_tid(); + } + + isc__nmsocket_clearcb(sock); + isc__nm_connectcb(sock, req, ISC_R_SHUTTINGDOWN, true); + isc__nmsocket_prep_destroy(sock); + isc__nmsocket_detach(&sock); + return; + } + + sock->h2 = (isc_nmsocket_h2_t){ .connect.uri = isc_mem_strdup(mgr->mctx, + uri), + .connect.post = post, + .connect.tlsctx = tlsctx }; + ISC_LINK_INIT(&sock->h2, link); + + /* + * We need to prevent the interface object data from going out of + * scope too early. + */ + if (local == &local_interface) { + sock->h2.connect.local_interface = local_interface; + sock->iface = sock->h2.connect.local_interface; + } + + if (tlsctx != NULL) { + isc_nm_tlsconnect(mgr, local, peer, transport_connect_cb, sock, + tlsctx, client_sess_cache, timeout, 0); + } else { + isc_nm_tcpconnect(mgr, local, peer, transport_connect_cb, sock, + timeout, 0); + } +} + +static isc_result_t +client_send(isc_nmhandle_t *handle, const isc_region_t *region) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *sock = handle->sock; + isc_mem_t *mctx = sock->mgr->mctx; + isc_nm_http_session_t *session = sock->h2.session; + http_cstream_t *cstream = sock->h2.connect.cstream; + + REQUIRE(VALID_HTTP2_SESSION(handle->sock->h2.session)); + REQUIRE(session->client); + REQUIRE(region != NULL); + REQUIRE(region->base != NULL); + REQUIRE(region->length <= MAX_DNS_MESSAGE_SIZE); + + if (session->closed) { + return (ISC_R_CANCELED); + } + + INSIST(cstream != NULL); + + if (cstream->post) { + /* POST */ + isc_buffer_allocate(mctx, &cstream->postdata, region->length); + isc_buffer_putmem(cstream->postdata, region->base, + region->length); + } else { + /* GET */ + size_t path_size = 0; + char *base64url_data = NULL; + size_t base64url_data_len = 0; + isc_buffer_t *buf = NULL; + isc_region_t data = *region; + isc_region_t base64_region; + size_t base64_len = ((4 * data.length / 3) + 3) & ~3; + + isc_buffer_allocate(mctx, &buf, base64_len); + + result = isc_base64_totext(&data, -1, "", buf); + if (result != ISC_R_SUCCESS) { + isc_buffer_free(&buf); + goto error; + } + + isc_buffer_usedregion(buf, &base64_region); + INSIST(base64_region.length == base64_len); + + base64url_data = isc__nm_base64_to_base64url( + mctx, (const char *)base64_region.base, + base64_region.length, &base64url_data_len); + isc_buffer_free(&buf); + if (base64url_data == NULL) { + goto error; + } + + /* len("?dns=") + len(path) + len(base64url) + len("\0") */ + path_size = cstream->pathlen + base64url_data_len + 5 + 1; + cstream->GET_path = isc_mem_allocate(mctx, path_size); + cstream->GET_path_len = (size_t)snprintf( + cstream->GET_path, path_size, "%.*s?dns=%s", + (int)cstream->pathlen, cstream->path, base64url_data); + + INSIST(cstream->GET_path_len == (path_size - 1)); + isc_mem_free(mctx, base64url_data); + } + + cstream->sending = true; + + sock->h2.connect.cstream = NULL; + result = client_submit_request(session, cstream); + if (result != ISC_R_SUCCESS) { + put_http_cstream(session->mctx, cstream); + goto error; + } + +error: + return (result); +} + +isc_result_t +isc__nm_http_request(isc_nmhandle_t *handle, isc_region_t *region, + isc_nm_recv_cb_t cb, void *cbarg) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *sock = NULL; + http_cstream_t *cstream = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + REQUIRE(handle->sock->tid == isc_nm_tid()); + REQUIRE(atomic_load(&handle->sock->client)); + + REQUIRE(cb != NULL); + + sock = handle->sock; + + isc__nm_http_read(handle, cb, cbarg); + if (!http_session_active(handle->sock->h2.session)) { + /* the callback was called by isc__nm_http_read() */ + return (ISC_R_CANCELED); + } + result = client_send(handle, region); + if (result != ISC_R_SUCCESS) { + goto error; + } + + return (ISC_R_SUCCESS); + +error: + cstream = sock->h2.connect.cstream; + if (cstream->read_cb != NULL) { + cstream->read_cb(handle, result, NULL, cstream->read_cbarg); + } + return (result); +} + +static int +server_on_begin_headers_callback(nghttp2_session *ngsession, + const nghttp2_frame *frame, void *user_data) { + isc_nm_http_session_t *session = (isc_nm_http_session_t *)user_data; + isc_nmsocket_t *socket = NULL; + + if (frame->hd.type != NGHTTP2_HEADERS || + frame->headers.cat != NGHTTP2_HCAT_REQUEST) + { + return (0); + } else if (frame->hd.length > MAX_ALLOWED_DATA_IN_HEADERS) { + return (NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE); + } + + if (session->nsstreams >= session->max_concurrent_streams) { + return (NGHTTP2_ERR_CALLBACK_FAILURE); + } + + socket = isc_mem_get(session->mctx, sizeof(isc_nmsocket_t)); + isc__nmsocket_init(socket, session->serversocket->mgr, + isc_nm_httpsocket, + (isc_sockaddr_t *)&session->handle->sock->iface); + socket->peer = session->handle->sock->peer; + socket->h2 = (isc_nmsocket_h2_t){ + .psock = socket, + .stream_id = frame->hd.stream_id, + .headers_error_code = ISC_HTTP_ERROR_SUCCESS, + .request_type = ISC_HTTP_REQ_UNSUPPORTED, + .request_scheme = ISC_HTTP_SCHEME_UNSUPPORTED + }; + isc_buffer_initnull(&socket->h2.rbuf); + isc_buffer_initnull(&socket->h2.wbuf); + session->nsstreams++; + isc__nm_httpsession_attach(session, &socket->h2.session); + socket->tid = session->handle->sock->tid; + ISC_LINK_INIT(&socket->h2, link); + ISC_LIST_APPEND(session->sstreams, &socket->h2, link); + + nghttp2_session_set_stream_user_data(ngsession, frame->hd.stream_id, + socket); + return (0); +} + +static isc_nm_httphandler_t * +find_server_request_handler(const char *request_path, + isc_nmsocket_t *serversocket, const int tid) { + isc_nm_httphandler_t *handler = NULL; + + REQUIRE(VALID_NMSOCK(serversocket)); + + if (atomic_load(&serversocket->listening)) { + handler = http_endpoints_find( + request_path, + http_get_listener_endpoints(serversocket, tid)); + } + return (handler); +} + +static isc_http_error_responses_t +server_handle_path_header(isc_nmsocket_t *socket, const uint8_t *value, + const size_t valuelen) { + isc_nm_httphandler_t *handler = NULL; + const uint8_t *qstr = NULL; + size_t vlen = valuelen; + + qstr = memchr(value, '?', valuelen); + if (qstr != NULL) { + vlen = qstr - value; + } + + if (socket->h2.request_path != NULL) { + isc_mem_free(socket->mgr->mctx, socket->h2.request_path); + } + socket->h2.request_path = isc_mem_strndup( + socket->mgr->mctx, (const char *)value, vlen + 1); + + if (!isc_nm_http_path_isvalid(socket->h2.request_path)) { + isc_mem_free(socket->mgr->mctx, socket->h2.request_path); + socket->h2.request_path = NULL; + return (ISC_HTTP_ERROR_BAD_REQUEST); + } + + handler = find_server_request_handler(socket->h2.request_path, + socket->h2.session->serversocket, + socket->tid); + if (handler != NULL) { + socket->h2.cb = handler->cb; + socket->h2.cbarg = handler->cbarg; + socket->extrahandlesize = handler->extrahandlesize; + } else { + isc_mem_free(socket->mgr->mctx, socket->h2.request_path); + socket->h2.request_path = NULL; + return (ISC_HTTP_ERROR_NOT_FOUND); + } + + if (qstr != NULL) { + const char *dns_value = NULL; + size_t dns_value_len = 0; + + if (isc__nm_parse_httpquery((const char *)qstr, &dns_value, + &dns_value_len)) + { + const size_t decoded_size = dns_value_len / 4 * 3; + if (decoded_size <= MAX_DNS_MESSAGE_SIZE) { + if (socket->h2.query_data != NULL) { + isc_mem_free(socket->mgr->mctx, + socket->h2.query_data); + } + socket->h2.query_data = + isc__nm_base64url_to_base64( + socket->mgr->mctx, dns_value, + dns_value_len, + &socket->h2.query_data_len); + } else { + socket->h2.query_too_large = true; + return (ISC_HTTP_ERROR_PAYLOAD_TOO_LARGE); + } + } else { + return (ISC_HTTP_ERROR_BAD_REQUEST); + } + } + return (ISC_HTTP_ERROR_SUCCESS); +} + +static isc_http_error_responses_t +server_handle_method_header(isc_nmsocket_t *socket, const uint8_t *value, + const size_t valuelen) { + const char get[] = "GET"; + const char post[] = "POST"; + + if (HEADER_MATCH(get, value, valuelen)) { + socket->h2.request_type = ISC_HTTP_REQ_GET; + } else if (HEADER_MATCH(post, value, valuelen)) { + socket->h2.request_type = ISC_HTTP_REQ_POST; + } else { + return (ISC_HTTP_ERROR_NOT_IMPLEMENTED); + } + return (ISC_HTTP_ERROR_SUCCESS); +} + +static isc_http_error_responses_t +server_handle_scheme_header(isc_nmsocket_t *socket, const uint8_t *value, + const size_t valuelen) { + const char http[] = "http"; + const char http_secure[] = "https"; + + if (HEADER_MATCH(http_secure, value, valuelen)) { + socket->h2.request_scheme = ISC_HTTP_SCHEME_HTTP_SECURE; + } else if (HEADER_MATCH(http, value, valuelen)) { + socket->h2.request_scheme = ISC_HTTP_SCHEME_HTTP; + } else { + return (ISC_HTTP_ERROR_BAD_REQUEST); + } + return (ISC_HTTP_ERROR_SUCCESS); +} + +static isc_http_error_responses_t +server_handle_content_length_header(isc_nmsocket_t *socket, + const uint8_t *value, + const size_t valuelen) { + char tmp[32] = { 0 }; + const size_t tmplen = sizeof(tmp) - 1; + + strncpy(tmp, (const char *)value, + valuelen > tmplen ? tmplen : valuelen); + socket->h2.content_length = strtoul(tmp, NULL, 10); + if (socket->h2.content_length > MAX_DNS_MESSAGE_SIZE) { + return (ISC_HTTP_ERROR_PAYLOAD_TOO_LARGE); + } else if (socket->h2.content_length == 0) { + return (ISC_HTTP_ERROR_BAD_REQUEST); + } + return (ISC_HTTP_ERROR_SUCCESS); +} + +static isc_http_error_responses_t +server_handle_content_type_header(isc_nmsocket_t *socket, const uint8_t *value, + const size_t valuelen) { + const char type_dns_message[] = DNS_MEDIA_TYPE; + isc_http_error_responses_t resp = ISC_HTTP_ERROR_SUCCESS; + + UNUSED(socket); + + if (!HEADER_MATCH(type_dns_message, value, valuelen)) { + resp = ISC_HTTP_ERROR_UNSUPPORTED_MEDIA_TYPE; + } + return (resp); +} + +static isc_http_error_responses_t +server_handle_header(isc_nmsocket_t *socket, const uint8_t *name, + size_t namelen, const uint8_t *value, + const size_t valuelen) { + isc_http_error_responses_t code = ISC_HTTP_ERROR_SUCCESS; + bool was_error; + const char path[] = ":path"; + const char method[] = ":method"; + const char scheme[] = ":scheme"; + const char content_length[] = "Content-Length"; + const char content_type[] = "Content-Type"; + + was_error = socket->h2.headers_error_code != ISC_HTTP_ERROR_SUCCESS; + /* + * process Content-Length even when there was an error, + * to drop the connection earlier if required. + */ + if (HEADER_MATCH(content_length, name, namelen)) { + code = server_handle_content_length_header(socket, value, + valuelen); + } else if (!was_error && HEADER_MATCH(path, name, namelen)) { + code = server_handle_path_header(socket, value, valuelen); + } else if (!was_error && HEADER_MATCH(method, name, namelen)) { + code = server_handle_method_header(socket, value, valuelen); + } else if (!was_error && HEADER_MATCH(scheme, name, namelen)) { + code = server_handle_scheme_header(socket, value, valuelen); + } else if (!was_error && HEADER_MATCH(content_type, name, namelen)) { + code = server_handle_content_type_header(socket, value, + valuelen); + } + + return (code); +} + +static int +server_on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, + const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, uint8_t flags, + void *user_data) { + isc_nmsocket_t *socket = NULL; + isc_http_error_responses_t code = ISC_HTTP_ERROR_SUCCESS; + + UNUSED(flags); + UNUSED(user_data); + + socket = nghttp2_session_get_stream_user_data(session, + frame->hd.stream_id); + if (socket == NULL) { + return (NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE); + } + + socket->h2.headers_data_processed += (namelen + valuelen); + + switch (frame->hd.type) { + case NGHTTP2_HEADERS: + if (frame->headers.cat != NGHTTP2_HCAT_REQUEST) { + break; + } + code = server_handle_header(socket, name, namelen, value, + valuelen); + break; + } + + INSIST(socket != NULL); + + if (socket->h2.headers_data_processed > MAX_ALLOWED_DATA_IN_HEADERS) { + return (NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE); + } else if (socket->h2.content_length > MAX_ALLOWED_DATA_IN_POST) { + return (NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE); + } + + if (code == ISC_HTTP_ERROR_SUCCESS) { + return (0); + } else { + socket->h2.headers_error_code = code; + } + + return (0); +} + +static ssize_t +server_read_callback(nghttp2_session *ngsession, int32_t stream_id, + uint8_t *buf, size_t length, uint32_t *data_flags, + nghttp2_data_source *source, void *user_data) { + isc_nm_http_session_t *session = (isc_nm_http_session_t *)user_data; + isc_nmsocket_t *socket = (isc_nmsocket_t *)source->ptr; + size_t buflen; + + REQUIRE(socket->h2.stream_id == stream_id); + + UNUSED(ngsession); + UNUSED(session); + + buflen = isc_buffer_remaininglength(&socket->h2.wbuf); + if (buflen > length) { + buflen = length; + } + + if (buflen > 0) { + (void)memmove(buf, isc_buffer_current(&socket->h2.wbuf), + buflen); + isc_buffer_forward(&socket->h2.wbuf, buflen); + } + + if (isc_buffer_remaininglength(&socket->h2.wbuf) == 0) { + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + } + + return (buflen); +} + +static isc_result_t +server_send_response(nghttp2_session *ngsession, int32_t stream_id, + const nghttp2_nv *nva, size_t nvlen, + isc_nmsocket_t *socket) { + nghttp2_data_provider data_prd; + int rv; + + if (socket->h2.response_submitted) { + /* NGHTTP2 will gladly accept new response (write request) + * from us even though we cannot send more than one over the + * same HTTP/2 stream. Thus, we need to handle this case + * manually. We will return failure code so that it will be + * passed to the write callback. */ + return (ISC_R_FAILURE); + } + + data_prd.source.ptr = socket; + data_prd.read_callback = server_read_callback; + + rv = nghttp2_submit_response(ngsession, stream_id, nva, nvlen, + &data_prd); + if (rv != 0) { + return (ISC_R_FAILURE); + } + + socket->h2.response_submitted = true; + return (ISC_R_SUCCESS); +} + +#define MAKE_ERROR_REPLY(tag, code, desc) \ + { \ + tag, MAKE_NV2(":status", #code), desc \ + } + +/* + * Here we use roughly the same error codes that Unbound uses. + * (https://blog.nlnetlabs.nl/dns-over-https-in-unbound/) + */ + +static struct http_error_responses { + const isc_http_error_responses_t type; + const nghttp2_nv header; + const char *desc; +} error_responses[] = { + MAKE_ERROR_REPLY(ISC_HTTP_ERROR_BAD_REQUEST, 400, "Bad Request"), + MAKE_ERROR_REPLY(ISC_HTTP_ERROR_NOT_FOUND, 404, "Not Found"), + MAKE_ERROR_REPLY(ISC_HTTP_ERROR_PAYLOAD_TOO_LARGE, 413, + "Payload Too Large"), + MAKE_ERROR_REPLY(ISC_HTTP_ERROR_URI_TOO_LONG, 414, "URI Too Long"), + MAKE_ERROR_REPLY(ISC_HTTP_ERROR_UNSUPPORTED_MEDIA_TYPE, 415, + "Unsupported Media Type"), + MAKE_ERROR_REPLY(ISC_HTTP_ERROR_GENERIC, 500, "Internal Server Error"), + MAKE_ERROR_REPLY(ISC_HTTP_ERROR_NOT_IMPLEMENTED, 501, "Not Implemented") +}; + +static void +log_server_error_response(const isc_nmsocket_t *socket, + const struct http_error_responses *response) { + const int log_level = ISC_LOG_DEBUG(1); + isc_sockaddr_t client_addr; + isc_sockaddr_t local_addr; + char client_sabuf[ISC_SOCKADDR_FORMATSIZE]; + char local_sabuf[ISC_SOCKADDR_FORMATSIZE]; + + if (!isc_log_wouldlog(isc_lctx, log_level)) { + return; + } + + client_addr = isc_nmhandle_peeraddr(socket->h2.session->handle); + local_addr = isc_nmhandle_localaddr(socket->h2.session->handle); + isc_sockaddr_format(&client_addr, client_sabuf, sizeof(client_sabuf)); + isc_sockaddr_format(&local_addr, local_sabuf, sizeof(local_sabuf)); + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, ISC_LOGMODULE_NETMGR, + log_level, "HTTP/2 request from %s (on %s) failed: %s %s", + client_sabuf, local_sabuf, response->header.value, + response->desc); +} + +static isc_result_t +server_send_error_response(const isc_http_error_responses_t error, + nghttp2_session *ngsession, isc_nmsocket_t *socket) { + void *base; + + REQUIRE(error != ISC_HTTP_ERROR_SUCCESS); + + base = isc_buffer_base(&socket->h2.rbuf); + if (base != NULL) { + isc_mem_free(socket->h2.session->mctx, base); + isc_buffer_initnull(&socket->h2.rbuf); + } + + /* We do not want the error response to be cached anywhere. */ + socket->h2.min_ttl = 0; + + for (size_t i = 0; + i < sizeof(error_responses) / sizeof(error_responses[0]); i++) + { + if (error_responses[i].type == error) { + log_server_error_response(socket, &error_responses[i]); + return (server_send_response( + ngsession, socket->h2.stream_id, + &error_responses[i].header, 1, socket)); + } + } + + return (server_send_error_response(ISC_HTTP_ERROR_GENERIC, ngsession, + socket)); +} + +static void +server_call_cb(isc_nmsocket_t *socket, isc_nm_http_session_t *session, + const isc_result_t result, isc_region_t *data) { + isc_sockaddr_t addr; + isc_nmhandle_t *handle = NULL; + + REQUIRE(VALID_NMSOCK(socket)); + REQUIRE(VALID_HTTP2_SESSION(session)); + REQUIRE(socket->h2.cb != NULL); + + addr = isc_nmhandle_peeraddr(session->handle); + handle = isc__nmhandle_get(socket, &addr, NULL); + socket->h2.cb(handle, result, data, socket->h2.cbarg); + isc_nmhandle_detach(&handle); +} + +void +isc__nm_http_bad_request(isc_nmhandle_t *handle) { + isc_nmsocket_t *sock = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + sock = handle->sock; + REQUIRE(sock->type == isc_nm_httpsocket); + REQUIRE(!atomic_load(&sock->client)); + REQUIRE(VALID_HTTP2_SESSION(sock->h2.session)); + + (void)server_send_error_response(ISC_HTTP_ERROR_BAD_REQUEST, + sock->h2.session->ngsession, sock); +} + +static int +server_on_request_recv(nghttp2_session *ngsession, + isc_nm_http_session_t *session, isc_nmsocket_t *socket) { + isc_result_t result; + isc_http_error_responses_t code = ISC_HTTP_ERROR_SUCCESS; + isc_region_t data; + uint8_t tmp_buf[MAX_DNS_MESSAGE_SIZE]; + + code = socket->h2.headers_error_code; + if (code != ISC_HTTP_ERROR_SUCCESS) { + goto error; + } + + if (socket->h2.request_path == NULL || socket->h2.cb == NULL) { + code = ISC_HTTP_ERROR_NOT_FOUND; + } else if (socket->h2.request_type == ISC_HTTP_REQ_POST && + socket->h2.content_length == 0) + { + code = ISC_HTTP_ERROR_BAD_REQUEST; + } else if (socket->h2.request_type == ISC_HTTP_REQ_POST && + isc_buffer_usedlength(&socket->h2.rbuf) > + socket->h2.content_length) + { + code = ISC_HTTP_ERROR_PAYLOAD_TOO_LARGE; + } else if (socket->h2.request_type == ISC_HTTP_REQ_POST && + isc_buffer_usedlength(&socket->h2.rbuf) != + socket->h2.content_length) + { + code = ISC_HTTP_ERROR_BAD_REQUEST; + } else if (socket->h2.request_type == ISC_HTTP_REQ_POST && + socket->h2.query_data != NULL) + { + /* The spec does not mention which value the query string for + * POST should have. For GET we use its value to decode a DNS + * message from it, for POST the message is transferred in the + * body of the request. Taking it into account, it is much safer + * to treat POST + * requests with query strings as malformed ones. */ + code = ISC_HTTP_ERROR_BAD_REQUEST; + } else if (socket->h2.request_type == ISC_HTTP_REQ_GET && + socket->h2.content_length > 0) + { + code = ISC_HTTP_ERROR_BAD_REQUEST; + } else if (socket->h2.request_type == ISC_HTTP_REQ_GET && + socket->h2.query_data == NULL) + { + /* A GET request without any query data - there is nothing to + * decode. */ + INSIST(socket->h2.query_data_len == 0); + code = ISC_HTTP_ERROR_BAD_REQUEST; + } + + if (code != ISC_HTTP_ERROR_SUCCESS) { + goto error; + } + + if (socket->h2.request_type == ISC_HTTP_REQ_GET) { + isc_buffer_t decoded_buf; + isc_buffer_init(&decoded_buf, tmp_buf, sizeof(tmp_buf)); + if (isc_base64_decodestring(socket->h2.query_data, + &decoded_buf) != ISC_R_SUCCESS) + { + code = ISC_HTTP_ERROR_BAD_REQUEST; + goto error; + } + isc_buffer_usedregion(&decoded_buf, &data); + } else if (socket->h2.request_type == ISC_HTTP_REQ_POST) { + INSIST(socket->h2.content_length > 0); + isc_buffer_usedregion(&socket->h2.rbuf, &data); + } else { + UNREACHABLE(); + } + + server_call_cb(socket, session, ISC_R_SUCCESS, &data); + + return (0); + +error: + result = server_send_error_response(code, ngsession, socket); + if (result != ISC_R_SUCCESS) { + return (NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE); + } + return (0); +} + +void +isc__nm_http_send(isc_nmhandle_t *handle, const isc_region_t *region, + isc_nm_cb_t cb, void *cbarg) { + isc_nmsocket_t *sock = NULL; + isc__netievent_httpsend_t *ievent = NULL; + isc__nm_uvreq_t *uvreq = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + + sock = handle->sock; + + REQUIRE(VALID_NMSOCK(sock)); + + uvreq = isc__nm_uvreq_get(sock->mgr, sock); + isc_nmhandle_attach(handle, &uvreq->handle); + uvreq->cb.send = cb; + uvreq->cbarg = cbarg; + + uvreq->uvbuf.base = (char *)region->base; + uvreq->uvbuf.len = region->length; + + ievent = isc__nm_get_netievent_httpsend(sock->mgr, sock, uvreq); + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); +} + +static void +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); + } +} + +static void +client_httpsend(isc_nmhandle_t *handle, isc_nmsocket_t *sock, + isc__nm_uvreq_t *req) { + isc_result_t result = ISC_R_SUCCESS; + isc_nm_cb_t cb = req->cb.send; + void *cbarg = req->cbarg; + + result = client_send( + handle, + &(isc_region_t){ (uint8_t *)req->uvbuf.base, req->uvbuf.len }); + if (result != ISC_R_SUCCESS) { + failed_send_cb(sock, req, result); + return; + } + + http_do_bio(sock->h2.session, handle, cb, cbarg); + isc__nm_uvreq_put(&req, sock); +} + +static void +server_httpsend(isc_nmhandle_t *handle, isc_nmsocket_t *sock, + isc__nm_uvreq_t *req) { + size_t content_len_buf_len, cache_control_buf_len; + isc_result_t result = ISC_R_SUCCESS; + isc_nm_cb_t cb = req->cb.send; + void *cbarg = req->cbarg; + if (isc__nmsocket_closing(sock) || + !http_session_active(handle->httpsession)) + { + failed_send_cb(sock, req, ISC_R_CANCELED); + return; + } + + INSIST(handle->httpsession->handle->sock->tid == isc_nm_tid()); + INSIST(VALID_NMHANDLE(handle->httpsession->handle)); + INSIST(VALID_NMSOCK(handle->httpsession->handle->sock)); + + isc_buffer_init(&sock->h2.wbuf, req->uvbuf.base, req->uvbuf.len); + isc_buffer_add(&sock->h2.wbuf, req->uvbuf.len); + + content_len_buf_len = snprintf(sock->h2.clenbuf, + sizeof(sock->h2.clenbuf), "%lu", + (unsigned long)req->uvbuf.len); + if (sock->h2.min_ttl == 0) { + cache_control_buf_len = + snprintf(sock->h2.cache_control_buf, + sizeof(sock->h2.cache_control_buf), "%s", + DEFAULT_CACHE_CONTROL); + } else { + cache_control_buf_len = + snprintf(sock->h2.cache_control_buf, + sizeof(sock->h2.cache_control_buf), + "max-age=%" PRIu32, sock->h2.min_ttl); + } + const nghttp2_nv hdrs[] = { MAKE_NV2(":status", "200"), + MAKE_NV2("Content-Type", DNS_MEDIA_TYPE), + MAKE_NV("Content-Length", sock->h2.clenbuf, + content_len_buf_len), + MAKE_NV("Cache-Control", + sock->h2.cache_control_buf, + cache_control_buf_len) }; + + result = server_send_response(handle->httpsession->ngsession, + sock->h2.stream_id, hdrs, + sizeof(hdrs) / sizeof(nghttp2_nv), sock); + + if (result == ISC_R_SUCCESS) { + http_do_bio(handle->httpsession, handle, cb, cbarg); + } else { + cb(handle, result, cbarg); + } + isc__nm_uvreq_put(&req, sock); +} + +void +isc__nm_async_httpsend(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_httpsend_t *ievent = (isc__netievent_httpsend_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + isc__nm_uvreq_t *req = ievent->req; + isc_nmhandle_t *handle = NULL; + isc_nm_http_session_t *session = NULL; + + UNUSED(worker); + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(VALID_UVREQ(req)); + REQUIRE(VALID_HTTP2_SESSION(sock->h2.session)); + + ievent->req = NULL; + handle = req->handle; + + REQUIRE(VALID_NMHANDLE(handle)); + + session = sock->h2.session; + if (session != NULL && session->client) { + client_httpsend(handle, sock, req); + } else { + server_httpsend(handle, sock, req); + } +} + +void +isc__nm_http_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg) { + isc_result_t result; + http_cstream_t *cstream = NULL; + isc_nm_http_session_t *session = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + + session = handle->sock->h2.session; + if (!http_session_active(session)) { + cb(handle, ISC_R_CANCELED, NULL, cbarg); + return; + } + + result = get_http_cstream(handle->sock, &cstream); + if (result != ISC_R_SUCCESS) { + return; + } + + handle->sock->h2.connect.cstream = cstream; + cstream->read_cb = cb; + cstream->read_cbarg = cbarg; + cstream->reading = true; + + if (cstream->sending) { + result = client_submit_request(session, cstream); + if (result != ISC_R_SUCCESS) { + put_http_cstream(session->mctx, cstream); + return; + } + + http_do_bio(session, NULL, NULL, NULL); + } +} + +static int +server_on_frame_recv_callback(nghttp2_session *ngsession, + const nghttp2_frame *frame, void *user_data) { + isc_nm_http_session_t *session = (isc_nm_http_session_t *)user_data; + isc_nmsocket_t *socket = NULL; + + switch (frame->hd.type) { + case NGHTTP2_DATA: + case NGHTTP2_HEADERS: + /* Check that the client request has finished */ + if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + socket = nghttp2_session_get_stream_user_data( + ngsession, frame->hd.stream_id); + + /* + * For DATA and HEADERS frame, this callback may be + * called after on_stream_close_callback. Check that + * the stream is still alive. + */ + if (socket == NULL) { + return (0); + } + + return (server_on_request_recv(ngsession, session, + socket)); + } + break; + default: + break; + } + return (0); +} + +static void +initialize_nghttp2_server_session(isc_nm_http_session_t *session) { + nghttp2_session_callbacks *callbacks = NULL; + nghttp2_mem mem; + + init_nghttp2_mem(session->mctx, &mem); + + RUNTIME_CHECK(nghttp2_session_callbacks_new(&callbacks) == 0); + + nghttp2_session_callbacks_set_on_data_chunk_recv_callback( + callbacks, on_data_chunk_recv_callback); + + nghttp2_session_callbacks_set_on_stream_close_callback( + callbacks, on_stream_close_callback); + + nghttp2_session_callbacks_set_on_header_callback( + callbacks, server_on_header_callback); + + nghttp2_session_callbacks_set_on_begin_headers_callback( + callbacks, server_on_begin_headers_callback); + + nghttp2_session_callbacks_set_on_frame_recv_callback( + callbacks, server_on_frame_recv_callback); + + RUNTIME_CHECK(nghttp2_session_server_new3(&session->ngsession, + callbacks, session, NULL, + &mem) == 0); + + nghttp2_session_callbacks_del(callbacks); +} + +static int +server_send_connection_header(isc_nm_http_session_t *session) { + nghttp2_settings_entry iv[1] = { + { NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, + session->max_concurrent_streams } + }; + int rv; + + rv = nghttp2_submit_settings(session->ngsession, NGHTTP2_FLAG_NONE, iv, + 1); + if (rv != 0) { + return (-1); + } + return (0); +} + +/* + * It is advisable to disable Nagle's algorithm for HTTP/2 + * connections because multiple HTTP/2 streams could be multiplexed + * over one transport connection. Thus, delays when delivering small + * packets could bring down performance for the whole session. + * HTTP/2 is meant to be used this way. + */ +static void +http_transpost_tcp_nodelay(isc_nmhandle_t *transphandle) { + isc_nmsocket_t *tcpsock = NULL; + uv_os_fd_t tcp_fd = (uv_os_fd_t)-1; + + if (transphandle->sock->type == isc_nm_tlssocket) { + tcpsock = transphandle->sock->outerhandle->sock; + } else { + tcpsock = transphandle->sock; + } + + (void)uv_fileno((uv_handle_t *)&tcpsock->uv_handle.tcp, &tcp_fd); + RUNTIME_CHECK(tcp_fd != (uv_os_fd_t)-1); + (void)isc__nm_socket_tcp_nodelay((uv_os_sock_t)tcp_fd); +} + +static isc_result_t +httplisten_acceptcb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) { + isc_nmsocket_t *httplistensock = (isc_nmsocket_t *)cbarg; + isc_nm_http_session_t *session = NULL; + isc_nmsocket_t *listener = NULL, *httpserver = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + + if (handle->sock->type == isc_nm_tlssocket) { + REQUIRE(VALID_NMSOCK(handle->sock->listener)); + listener = handle->sock->listener; + httpserver = listener->h2.httpserver; + } else { + REQUIRE(VALID_NMSOCK(handle->sock->server)); + listener = handle->sock->server; + REQUIRE(VALID_NMSOCK(listener->parent)); + httpserver = listener->parent->h2.httpserver; + } + + /* + * NOTE: HTTP listener socket might be destroyed by the time this + * function gets invoked, so we need to do extra sanity checks to + * detect this case. + */ + if (isc__nmsocket_closing(handle->sock) || httpserver == NULL) { + return (ISC_R_CANCELED); + } + + if (result != ISC_R_SUCCESS) { + /* XXXWPK do nothing? */ + return (result); + } + + REQUIRE(VALID_NMSOCK(httplistensock)); + INSIST(httplistensock == httpserver); + + if (isc__nmsocket_closing(httplistensock) || + !atomic_load(&httplistensock->listening)) + { + return (ISC_R_CANCELED); + } + + http_transpost_tcp_nodelay(handle); + + new_session(httplistensock->mgr->mctx, NULL, &session); + session->max_concurrent_streams = + atomic_load(&httplistensock->h2.max_concurrent_streams); + initialize_nghttp2_server_session(session); + handle->sock->h2.session = session; + + isc_nmhandle_attach(handle, &session->handle); + isc__nmsocket_attach(httplistensock, &session->serversocket); + server_send_connection_header(session); + + /* TODO H2 */ + http_do_bio(session, NULL, NULL, NULL); + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_nm_listenhttp(isc_nm_t *mgr, isc_sockaddr_t *iface, int backlog, + isc_quota_t *quota, isc_tlsctx_t *ctx, + isc_nm_http_endpoints_t *eps, uint32_t max_concurrent_streams, + isc_nmsocket_t **sockp) { + isc_nmsocket_t *sock = NULL; + isc_result_t result; + + REQUIRE(!ISC_LIST_EMPTY(eps->handlers)); + REQUIRE(!ISC_LIST_EMPTY(eps->handler_cbargs)); + REQUIRE(atomic_load(&eps->in_use) == false); + + sock = isc_mem_get(mgr->mctx, sizeof(*sock)); + isc__nmsocket_init(sock, mgr, isc_nm_httplistener, iface); + atomic_init(&sock->h2.max_concurrent_streams, + NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS); + + isc_nmsocket_set_max_streams(sock, max_concurrent_streams); + + atomic_store(&eps->in_use, true); + http_init_listener_endpoints(sock, eps); + + if (ctx != NULL) { + result = isc_nm_listentls(mgr, iface, httplisten_acceptcb, sock, + sizeof(isc_nm_http_session_t), + backlog, quota, ctx, &sock->outer); + } else { + result = isc_nm_listentcp(mgr, iface, httplisten_acceptcb, sock, + sizeof(isc_nm_http_session_t), + backlog, quota, &sock->outer); + } + + if (result != ISC_R_SUCCESS) { + atomic_store(&sock->closed, true); + isc__nmsocket_detach(&sock); + return (result); + } + + isc__nmsocket_attach(sock, &sock->outer->h2.httpserver); + + sock->nchildren = sock->outer->nchildren; + sock->result = ISC_R_UNSET; + sock->tid = 0; + sock->fd = (uv_os_sock_t)-1; + + isc__nmsocket_barrier_init(sock); + atomic_init(&sock->rchildren, sock->nchildren); + + atomic_store(&sock->listening, true); + *sockp = sock; + return (ISC_R_SUCCESS); +} + +isc_nm_http_endpoints_t * +isc_nm_http_endpoints_new(isc_mem_t *mctx) { + isc_nm_http_endpoints_t *restrict eps; + REQUIRE(mctx != NULL); + + eps = isc_mem_get(mctx, sizeof(*eps)); + *eps = (isc_nm_http_endpoints_t){ .mctx = NULL }; + + isc_mem_attach(mctx, &eps->mctx); + ISC_LIST_INIT(eps->handler_cbargs); + ISC_LIST_INIT(eps->handlers); + isc_refcount_init(&eps->references, 1); + atomic_init(&eps->in_use, false); + eps->magic = HTTP_ENDPOINTS_MAGIC; + + return eps; +} + +void +isc_nm_http_endpoints_detach(isc_nm_http_endpoints_t **restrict epsp) { + isc_nm_http_endpoints_t *restrict eps; + isc_mem_t *mctx; + isc_nm_httphandler_t *handler = NULL; + isc_nm_httpcbarg_t *httpcbarg = NULL; + + REQUIRE(epsp != NULL); + eps = *epsp; + REQUIRE(VALID_HTTP_ENDPOINTS(eps)); + + if (isc_refcount_decrement(&eps->references) > 1) { + *epsp = NULL; + return; + } + + mctx = eps->mctx; + + /* Delete all handlers */ + handler = ISC_LIST_HEAD(eps->handlers); + while (handler != NULL) { + isc_nm_httphandler_t *next = NULL; + + next = ISC_LIST_NEXT(handler, link); + ISC_LIST_DEQUEUE(eps->handlers, handler, link); + isc_mem_free(mctx, handler->path); + isc_mem_put(mctx, handler, sizeof(*handler)); + handler = next; + } + + httpcbarg = ISC_LIST_HEAD(eps->handler_cbargs); + while (httpcbarg != NULL) { + isc_nm_httpcbarg_t *next = NULL; + + next = ISC_LIST_NEXT(httpcbarg, link); + ISC_LIST_DEQUEUE(eps->handler_cbargs, httpcbarg, link); + isc_mem_put(mctx, httpcbarg, sizeof(isc_nm_httpcbarg_t)); + httpcbarg = next; + } + + eps->magic = 0; + + isc_mem_putanddetach(&mctx, eps, sizeof(*eps)); + *epsp = NULL; +} + +void +isc_nm_http_endpoints_attach(isc_nm_http_endpoints_t *source, + isc_nm_http_endpoints_t **targetp) { + REQUIRE(VALID_HTTP_ENDPOINTS(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + isc_refcount_increment(&source->references); + + *targetp = source; +} + +static isc_nm_httphandler_t * +http_endpoints_find(const char *request_path, + const isc_nm_http_endpoints_t *restrict eps) { + isc_nm_httphandler_t *handler = NULL; + + REQUIRE(VALID_HTTP_ENDPOINTS(eps)); + + if (request_path == NULL || *request_path == '\0') { + return (NULL); + } + + for (handler = ISC_LIST_HEAD(eps->handlers); handler != NULL; + handler = ISC_LIST_NEXT(handler, link)) + { + if (!strcmp(request_path, handler->path)) { + break; + } + } + + return (handler); +} + +/* + * In DoH we just need to intercept the request - the response can be sent + * to the client code via the nmhandle directly as it's always just the + * http content. + */ +static void +http_callback(isc_nmhandle_t *handle, isc_result_t result, isc_region_t *data, + void *arg) { + isc_nm_httpcbarg_t *httpcbarg = arg; + + REQUIRE(VALID_NMHANDLE(handle)); + + if (result != ISC_R_SUCCESS) { + /* Shut down the client, then ourselves */ + httpcbarg->cb(handle, result, NULL, httpcbarg->cbarg); + /* XXXWPK FREE */ + return; + } + httpcbarg->cb(handle, result, data, httpcbarg->cbarg); +} + +isc_result_t +isc_nm_http_endpoints_add(isc_nm_http_endpoints_t *restrict eps, + const char *uri, const isc_nm_recv_cb_t cb, + void *cbarg, const size_t extrahandlesize) { + isc_mem_t *mctx; + isc_nm_httphandler_t *restrict handler = NULL; + isc_nm_httpcbarg_t *restrict httpcbarg = NULL; + bool newhandler = false; + + REQUIRE(VALID_HTTP_ENDPOINTS(eps)); + REQUIRE(isc_nm_http_path_isvalid(uri)); + REQUIRE(atomic_load(&eps->in_use) == false); + + mctx = eps->mctx; + + httpcbarg = isc_mem_get(mctx, sizeof(isc_nm_httpcbarg_t)); + *httpcbarg = (isc_nm_httpcbarg_t){ .cb = cb, .cbarg = cbarg }; + ISC_LINK_INIT(httpcbarg, link); + + if (http_endpoints_find(uri, eps) == NULL) { + handler = isc_mem_get(mctx, sizeof(*handler)); + *handler = (isc_nm_httphandler_t){ + .cb = http_callback, + .cbarg = httpcbarg, + .extrahandlesize = extrahandlesize, + .path = isc_mem_strdup(mctx, uri) + }; + ISC_LINK_INIT(handler, link); + + newhandler = true; + } + + if (newhandler) { + ISC_LIST_APPEND(eps->handlers, handler, link); + } + ISC_LIST_APPEND(eps->handler_cbargs, httpcbarg, link); + return (ISC_R_SUCCESS); +} + +void +isc__nm_http_stoplistening(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->type == isc_nm_httplistener); + + isc__nmsocket_stop(sock); +} + +static void +http_close_direct(isc_nmsocket_t *sock) { + isc_nm_http_session_t *session = NULL; + + REQUIRE(VALID_NMSOCK(sock)); + + atomic_store(&sock->closed, true); + atomic_store(&sock->active, false); + session = sock->h2.session; + + if (session != NULL && session->sending == 0 && !session->reading) { + /* + * The socket is going to be closed too early without been + * used even once (might happen in a case of low level + * error). + */ + finish_http_session(session); + } else if (session != NULL && session->handle) { + http_do_bio(session, NULL, NULL, NULL); + } +} + +void +isc__nm_http_close(isc_nmsocket_t *sock) { + bool destroy = false; + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->type == isc_nm_httpsocket); + REQUIRE(!isc__nmsocket_active(sock)); + + if (!atomic_compare_exchange_strong(&sock->closing, &(bool){ false }, + true)) + { + return; + } + + if (sock->h2.session != NULL && sock->h2.session->closed && + sock->tid == isc_nm_tid()) + { + isc__nm_httpsession_detach(&sock->h2.session); + destroy = true; + } else if (sock->h2.session == NULL && sock->tid == isc_nm_tid()) { + destroy = true; + } + + if (destroy) { + http_close_direct(sock); + isc__nmsocket_prep_destroy(sock); + return; + } + + isc__netievent_httpclose_t *ievent = + isc__nm_get_netievent_httpclose(sock->mgr, sock); + + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); +} + +void +isc__nm_async_httpclose(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_httpclose_t *ievent = (isc__netievent_httpclose_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + + UNUSED(worker); + + http_close_direct(sock); +} + +static void +failed_httpstream_read_cb(isc_nmsocket_t *sock, isc_result_t result, + isc_nm_http_session_t *session) { + isc_region_t data; + REQUIRE(VALID_NMSOCK(sock)); + INSIST(sock->type == isc_nm_httpsocket); + + if (sock->h2.request_path == NULL) { + return; + } + + INSIST(sock->h2.cbarg != NULL); + + (void)nghttp2_submit_rst_stream( + session->ngsession, NGHTTP2_FLAG_END_STREAM, sock->h2.stream_id, + NGHTTP2_REFUSED_STREAM); + isc_buffer_usedregion(&sock->h2.rbuf, &data); + server_call_cb(sock, session, result, &data); +} + +static void +client_call_failed_read_cb(isc_result_t result, + isc_nm_http_session_t *session) { + http_cstream_t *cstream = NULL; + + REQUIRE(VALID_HTTP2_SESSION(session)); + REQUIRE(result != ISC_R_SUCCESS); + + cstream = ISC_LIST_HEAD(session->cstreams); + while (cstream != NULL) { + http_cstream_t *next = ISC_LIST_NEXT(cstream, link); + + /* + * read_cb could be NULL if cstream was allocated and added + * to the tracking list, but was not properly initialized due + * to a low-level error. It is safe to get rid of the object + * in such a case. + */ + if (cstream->read_cb != NULL) { + isc_region_t read_data; + isc_buffer_usedregion(cstream->rbuf, &read_data); + cstream->read_cb(session->client_httphandle, result, + &read_data, cstream->read_cbarg); + } + + if (result != ISC_R_TIMEDOUT || cstream->read_cb == NULL || + !isc__nmsocket_timer_running(session->handle->sock)) + { + ISC_LIST_DEQUEUE(session->cstreams, cstream, link); + put_http_cstream(session->mctx, cstream); + } + + cstream = next; + } +} + +static void +server_call_failed_read_cb(isc_result_t result, + isc_nm_http_session_t *session) { + isc_nmsocket_h2_t *h2data = NULL; /* stream socket */ + + REQUIRE(VALID_HTTP2_SESSION(session)); + REQUIRE(result != ISC_R_SUCCESS); + + for (h2data = ISC_LIST_HEAD(session->sstreams); h2data != NULL; + h2data = ISC_LIST_NEXT(h2data, link)) + { + failed_httpstream_read_cb(h2data->psock, result, session); + } + + h2data = ISC_LIST_HEAD(session->sstreams); + while (h2data != NULL) { + isc_nmsocket_h2_t *next = ISC_LIST_NEXT(h2data, link); + ISC_LIST_DEQUEUE(session->sstreams, h2data, link); + /* Cleanup socket in place */ + atomic_store(&h2data->psock->active, false); + atomic_store(&h2data->psock->closed, true); + isc__nmsocket_detach(&h2data->psock); + + h2data = next; + } +} + +static void +failed_read_cb(isc_result_t result, isc_nm_http_session_t *session) { + if (session->client) { + client_call_failed_read_cb(result, session); + /* + * If result was ISC_R_TIMEDOUT and the timer was reset, + * then we still have active streams and should not close + * the session. + */ + if (ISC_LIST_EMPTY(session->cstreams)) { + finish_http_session(session); + } + } else { + server_call_failed_read_cb(result, session); + /* + * All streams are now destroyed; close the session. + */ + finish_http_session(session); + } +} + +void +isc__nm_http_set_maxage(isc_nmhandle_t *handle, const uint32_t ttl) { + isc_nm_http_session_t *session; + isc_nmsocket_t *sock; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + + sock = handle->sock; + session = sock->h2.session; + + INSIST(VALID_HTTP2_SESSION(session)); + INSIST(!session->client); + + sock->h2.min_ttl = ttl; +} + +bool +isc__nm_http_has_encryption(const isc_nmhandle_t *handle) { + isc_nm_http_session_t *session; + isc_nmsocket_t *sock; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + + sock = handle->sock; + session = sock->h2.session; + + INSIST(VALID_HTTP2_SESSION(session)); + + return (isc_nm_socket_type(session->handle) == isc_nm_tlssocket); +} + +const char * +isc__nm_http_verify_tls_peer_result_string(const isc_nmhandle_t *handle) { + isc_nmsocket_t *sock = NULL; + isc_nm_http_session_t *session; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + REQUIRE(handle->sock->type == isc_nm_httpsocket); + + sock = handle->sock; + session = sock->h2.session; + + /* + * In the case of a low-level error the session->handle is not + * attached nor session object is created. + */ + if (session == NULL && sock->h2.connect.tls_peer_verify_string != NULL) + { + return (sock->h2.connect.tls_peer_verify_string); + } + + if (session == NULL) { + return (NULL); + } + + INSIST(VALID_HTTP2_SESSION(session)); + + return (isc_nm_verify_tls_peer_result_string(session->handle)); +} + +void +isc__nm_http_set_tlsctx(isc_nmsocket_t *listener, isc_tlsctx_t *tlsctx) { + REQUIRE(VALID_NMSOCK(listener)); + REQUIRE(listener->type == isc_nm_httplistener); + + isc_nmsocket_set_tlsctx(listener->outer, tlsctx); +} + +void +isc__nm_http_set_max_streams(isc_nmsocket_t *listener, + const uint32_t max_concurrent_streams) { + uint32_t max_streams = NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS; + + REQUIRE(VALID_NMSOCK(listener)); + REQUIRE(listener->type == isc_nm_httplistener); + + if (max_concurrent_streams > 0 && + max_concurrent_streams < NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS) + { + max_streams = max_concurrent_streams; + } + + atomic_store(&listener->h2.max_concurrent_streams, max_streams); +} + +void +isc_nm_http_set_endpoints(isc_nmsocket_t *listener, + isc_nm_http_endpoints_t *eps) { + size_t nworkers; + + REQUIRE(VALID_NMSOCK(listener)); + REQUIRE(listener->type == isc_nm_httplistener); + REQUIRE(VALID_HTTP_ENDPOINTS(eps)); + + atomic_store(&eps->in_use, true); + + nworkers = (size_t)listener->mgr->nworkers; + for (size_t i = 0; i < nworkers; i++) { + isc__netievent__http_eps_t *ievent = + isc__nm_get_netievent_httpendpoints(listener->mgr, + listener, eps); + isc__nm_enqueue_ievent(&listener->mgr->workers[i], + (isc__netievent_t *)ievent); + } +} + +void +isc__nm_async_httpendpoints(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent__http_eps_t *ievent = (isc__netievent__http_eps_t *)ev0; + const int tid = isc_nm_tid(); + isc_nmsocket_t *listener = ievent->sock; + isc_nm_http_endpoints_t *eps = ievent->endpoints; + UNUSED(worker); + + isc_nm_http_endpoints_detach(&listener->h2.listener_endpoints[tid]); + isc_nm_http_endpoints_attach(eps, + &listener->h2.listener_endpoints[tid]); +} + +static void +http_init_listener_endpoints(isc_nmsocket_t *listener, + isc_nm_http_endpoints_t *epset) { + size_t nworkers; + + REQUIRE(VALID_NMSOCK(listener)); + REQUIRE(VALID_NM(listener->mgr)); + REQUIRE(VALID_HTTP_ENDPOINTS(epset)); + + nworkers = (size_t)listener->mgr->nworkers; + INSIST(nworkers > 0); + + listener->h2.listener_endpoints = + isc_mem_get(listener->mgr->mctx, + sizeof(isc_nm_http_endpoints_t *) * nworkers); + listener->h2.n_listener_endpoints = nworkers; + for (size_t i = 0; i < nworkers; i++) { + listener->h2.listener_endpoints[i] = NULL; + isc_nm_http_endpoints_attach( + epset, &listener->h2.listener_endpoints[i]); + } +} + +static void +http_cleanup_listener_endpoints(isc_nmsocket_t *listener) { + REQUIRE(VALID_NM(listener->mgr)); + + if (listener->h2.listener_endpoints == NULL) { + return; + } + + for (size_t i = 0; i < listener->h2.n_listener_endpoints; i++) { + isc_nm_http_endpoints_detach( + &listener->h2.listener_endpoints[i]); + } + isc_mem_put(listener->mgr->mctx, listener->h2.listener_endpoints, + sizeof(isc_nm_http_endpoints_t *) * + listener->h2.n_listener_endpoints); + listener->h2.n_listener_endpoints = 0; +} + +static isc_nm_http_endpoints_t * +http_get_listener_endpoints(isc_nmsocket_t *listener, const int tid) { + isc_nm_http_endpoints_t *eps; + REQUIRE(VALID_NMSOCK(listener)); + REQUIRE(tid >= 0); + REQUIRE((size_t)tid < listener->h2.n_listener_endpoints); + + eps = listener->h2.listener_endpoints[tid]; + INSIST(eps != NULL); + return (eps); +} + +static const bool base64url_validation_table[256] = { + false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, true, false, false, true, true, + true, true, true, true, true, true, true, true, false, false, + false, false, false, false, false, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, false, false, false, false, true, false, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, + true, true, true, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, false, false, + false, false, false, false, false, false +}; + +char * +isc__nm_base64url_to_base64(isc_mem_t *mem, const char *base64url, + const size_t base64url_len, size_t *res_len) { + char *res = NULL; + size_t i, k, len; + + if (mem == NULL || base64url == NULL || base64url_len == 0) { + return (NULL); + } + + len = base64url_len % 4 ? base64url_len + (4 - base64url_len % 4) + : base64url_len; + res = isc_mem_allocate(mem, len + 1); /* '\0' */ + + for (i = 0; i < base64url_len; i++) { + switch (base64url[i]) { + case '-': + res[i] = '+'; + break; + case '_': + res[i] = '/'; + break; + default: + if (base64url_validation_table[(size_t)base64url[i]]) { + res[i] = base64url[i]; + } else { + isc_mem_free(mem, res); + return (NULL); + } + break; + } + } + + if (base64url_len % 4 != 0) { + for (k = 0; k < (4 - base64url_len % 4); k++, i++) { + res[i] = '='; + } + } + + INSIST(i == len); + + if (res_len != NULL) { + *res_len = len; + } + + res[len] = '\0'; + + return (res); +} + +char * +isc__nm_base64_to_base64url(isc_mem_t *mem, const char *base64, + const size_t base64_len, size_t *res_len) { + char *res = NULL; + size_t i; + + if (mem == NULL || base64 == NULL || base64_len == 0) { + return (NULL); + } + + res = isc_mem_allocate(mem, base64_len + 1); /* '\0' */ + + for (i = 0; i < base64_len; i++) { + switch (base64[i]) { + case '+': + res[i] = '-'; + break; + case '/': + res[i] = '_'; + break; + case '=': + goto end; + break; + default: + /* + * All other characters from the alphabet are the same + * for both base64 and base64url, so we can reuse the + * validation table for the rest of the characters. + */ + if (base64[i] != '-' && base64[i] != '_' && + base64url_validation_table[(size_t)base64[i]]) + { + res[i] = base64[i]; + } else { + isc_mem_free(mem, res); + return (NULL); + } + break; + } + } +end: + if (res_len) { + *res_len = i; + } + + res[i] = '\0'; + + return (res); +} + +void +isc__nm_http_initsocket(isc_nmsocket_t *sock) { + REQUIRE(sock != NULL); + + sock->h2 = (isc_nmsocket_h2_t){ + .request_type = ISC_HTTP_REQ_UNSUPPORTED, + .request_scheme = ISC_HTTP_SCHEME_UNSUPPORTED, + }; +} + +void +isc__nm_http_cleanup_data(isc_nmsocket_t *sock) { + if ((sock->type == isc_nm_tcplistener || + sock->type == isc_nm_tlslistener) && + sock->h2.httpserver != NULL) + { + isc__nmsocket_detach(&sock->h2.httpserver); + } + + if (sock->type == isc_nm_httplistener || + sock->type == isc_nm_httpsocket) + { + if (sock->type == isc_nm_httplistener && + sock->h2.listener_endpoints != NULL) + { + /* Delete all handlers */ + http_cleanup_listener_endpoints(sock); + } + + if (sock->h2.request_path != NULL) { + isc_mem_free(sock->mgr->mctx, sock->h2.request_path); + sock->h2.request_path = NULL; + } + + if (sock->h2.query_data != NULL) { + isc_mem_free(sock->mgr->mctx, sock->h2.query_data); + sock->h2.query_data = NULL; + } + + INSIST(sock->h2.connect.cstream == NULL); + + if (isc_buffer_base(&sock->h2.rbuf) != NULL) { + void *base = isc_buffer_base(&sock->h2.rbuf); + isc_mem_free(sock->mgr->mctx, base); + isc_buffer_initnull(&sock->h2.rbuf); + } + } + + if ((sock->type == isc_nm_httplistener || + sock->type == isc_nm_httpsocket || + sock->type == isc_nm_tcpsocket || + sock->type == isc_nm_tlssocket) && + sock->h2.session != NULL) + { + if (sock->h2.connect.uri != NULL) { + isc_mem_free(sock->mgr->mctx, sock->h2.connect.uri); + sock->h2.connect.uri = NULL; + } + isc__nm_httpsession_detach(&sock->h2.session); + } +} + +void +isc__nm_http_cleartimeout(isc_nmhandle_t *handle) { + isc_nmsocket_t *sock = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + REQUIRE(handle->sock->type == isc_nm_httpsocket); + + sock = handle->sock; + if (sock->h2.session != NULL && sock->h2.session->handle != NULL) { + INSIST(VALID_HTTP2_SESSION(sock->h2.session)); + INSIST(VALID_NMHANDLE(sock->h2.session->handle)); + isc_nmhandle_cleartimeout(sock->h2.session->handle); + } +} + +void +isc__nm_http_settimeout(isc_nmhandle_t *handle, uint32_t timeout) { + isc_nmsocket_t *sock = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + REQUIRE(handle->sock->type == isc_nm_httpsocket); + + sock = handle->sock; + if (sock->h2.session != NULL && sock->h2.session->handle != NULL) { + INSIST(VALID_HTTP2_SESSION(sock->h2.session)); + INSIST(VALID_NMHANDLE(sock->h2.session->handle)); + isc_nmhandle_settimeout(sock->h2.session->handle, timeout); + } +} + +void +isc__nmhandle_http_keepalive(isc_nmhandle_t *handle, bool value) { + isc_nmsocket_t *sock = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + REQUIRE(handle->sock->type == isc_nm_httpsocket); + + sock = handle->sock; + if (sock->h2.session != NULL && sock->h2.session->handle) { + INSIST(VALID_HTTP2_SESSION(sock->h2.session)); + INSIST(VALID_NMHANDLE(sock->h2.session->handle)); + + isc_nmhandle_keepalive(sock->h2.session->handle, value); + } +} + +void +isc_nm_http_makeuri(const bool https, const isc_sockaddr_t *sa, + const char *hostname, const uint16_t http_port, + const char *abs_path, char *outbuf, + const size_t outbuf_len) { + char saddr[INET6_ADDRSTRLEN] = { 0 }; + int family; + bool ipv6_addr = false; + struct sockaddr_in6 sa6; + uint16_t host_port = http_port; + const char *host = NULL; + + REQUIRE(outbuf != NULL); + REQUIRE(outbuf_len != 0); + REQUIRE(isc_nm_http_path_isvalid(abs_path)); + + /* If hostname is specified, use that. */ + if (hostname != NULL && hostname[0] != '\0') { + /* + * The host name could be an IPv6 address. If so, + * wrap it between [ and ]. + */ + if (inet_pton(AF_INET6, hostname, &sa6) == 1 && + hostname[0] != '[') + { + ipv6_addr = true; + } + host = hostname; + } else { + /* + * A hostname was not specified; build one from + * the given IP address. + */ + INSIST(sa != NULL); + family = ((const struct sockaddr *)&sa->type.sa)->sa_family; + host_port = ntohs(family == AF_INET ? sa->type.sin.sin_port + : sa->type.sin6.sin6_port); + ipv6_addr = family == AF_INET6; + (void)inet_ntop( + family, + family == AF_INET + ? (const struct sockaddr *)&sa->type.sin.sin_addr + : (const struct sockaddr *)&sa->type.sin6 + .sin6_addr, + saddr, sizeof(saddr)); + host = saddr; + } + + /* + * If the port number was not specified, the default + * depends on whether we're using encryption or not. + */ + if (host_port == 0) { + host_port = https ? 443 : 80; + } + + (void)snprintf(outbuf, outbuf_len, "%s://%s%s%s:%u%s", + https ? "https" : "http", ipv6_addr ? "[" : "", host, + ipv6_addr ? "]" : "", host_port, abs_path); +} + +/* + * DoH GET Query String Scanner-less Recursive Descent Parser/Verifier + * + * It is based on the following grammar (using WSN/EBNF): + * + * S = query-string. + * query-string = ['?'] { key-value-pair } EOF. + * key-value-pair = key '=' value [ '&' ]. + * key = ('_' | alpha) { '_' | alnum}. + * value = value-char {value-char}. + * value-char = unreserved-char | percent-charcode. + * unreserved-char = alnum |'_' | '.' | '-' | '~'. (* RFC3986, Section 2.3 *) + * percent-charcode = '%' hexdigit hexdigit. + * ... + * + * Should be good enough. + */ +typedef struct isc_httpparser_state { + const char *str; + + const char *last_key; + size_t last_key_len; + + const char *last_value; + size_t last_value_len; + + bool query_found; + const char *query; + size_t query_len; +} isc_httpparser_state_t; + +#define MATCH(ch) (st->str[0] == (ch)) +#define MATCH_ALPHA() isalpha((unsigned char)(st->str[0])) +#define MATCH_DIGIT() isdigit((unsigned char)(st->str[0])) +#define MATCH_ALNUM() isalnum((unsigned char)(st->str[0])) +#define MATCH_XDIGIT() isxdigit((unsigned char)(st->str[0])) +#define ADVANCE() st->str++ +#define GETP() (st->str) + +static bool +rule_query_string(isc_httpparser_state_t *st); + +bool +isc__nm_parse_httpquery(const char *query_string, const char **start, + size_t *len) { + isc_httpparser_state_t state; + + REQUIRE(start != NULL); + REQUIRE(len != NULL); + + if (query_string == NULL || query_string[0] == '\0') { + return (false); + } + + state = (isc_httpparser_state_t){ .str = query_string }; + if (!rule_query_string(&state)) { + return (false); + } + + if (!state.query_found) { + return (false); + } + + *start = state.query; + *len = state.query_len; + + return (true); +} + +static bool +rule_key_value_pair(isc_httpparser_state_t *st); + +static bool +rule_key(isc_httpparser_state_t *st); + +static bool +rule_value(isc_httpparser_state_t *st); + +static bool +rule_value_char(isc_httpparser_state_t *st); + +static bool +rule_percent_charcode(isc_httpparser_state_t *st); + +static bool +rule_unreserved_char(isc_httpparser_state_t *st); + +static bool +rule_query_string(isc_httpparser_state_t *st) { + if (MATCH('?')) { + ADVANCE(); + } + + while (rule_key_value_pair(st)) { + /* skip */; + } + + if (!MATCH('\0')) { + return (false); + } + + ADVANCE(); + return (true); +} + +static bool +rule_key_value_pair(isc_httpparser_state_t *st) { + if (!rule_key(st)) { + return (false); + } + + if (MATCH('=')) { + ADVANCE(); + } else { + return (false); + } + + if (rule_value(st)) { + const char dns[] = "dns"; + if (st->last_key_len == sizeof(dns) - 1 && + memcmp(st->last_key, dns, sizeof(dns) - 1) == 0) + { + st->query_found = true; + st->query = st->last_value; + st->query_len = st->last_value_len; + } + } else { + return (false); + } + + if (MATCH('&')) { + ADVANCE(); + } + + return (true); +} + +static bool +rule_key(isc_httpparser_state_t *st) { + if (MATCH('_') || MATCH_ALPHA()) { + st->last_key = GETP(); + ADVANCE(); + } else { + return (false); + } + + while (MATCH('_') || MATCH_ALNUM()) { + ADVANCE(); + } + + st->last_key_len = GETP() - st->last_key; + return (true); +} + +static bool +rule_value(isc_httpparser_state_t *st) { + const char *s = GETP(); + if (!rule_value_char(st)) { + return (false); + } + + st->last_value = s; + while (rule_value_char(st)) { + /* skip */; + } + st->last_value_len = GETP() - st->last_value; + return (true); +} + +static bool +rule_value_char(isc_httpparser_state_t *st) { + if (rule_unreserved_char(st)) { + return (true); + } + + return (rule_percent_charcode(st)); +} + +static bool +rule_unreserved_char(isc_httpparser_state_t *st) { + if (MATCH_ALNUM() || MATCH('_') || MATCH('.') || MATCH('-') || + MATCH('~')) + { + ADVANCE(); + return (true); + } + return (false); +} + +static bool +rule_percent_charcode(isc_httpparser_state_t *st) { + if (MATCH('%')) { + ADVANCE(); + } else { + return (false); + } + + if (!MATCH_XDIGIT()) { + return (false); + } + ADVANCE(); + + if (!MATCH_XDIGIT()) { + return (false); + } + ADVANCE(); + + return (true); +} + +/* + * DoH URL Location Verifier. Based on the following grammar (EBNF/WSN + * notation): + * + * S = path_absolute. + * path_absolute = '/' [ segments ] '\0'. + * segments = segment_nz { slash_segment }. + * slash_segment = '/' segment. + * segment = { pchar }. + * segment_nz = pchar { pchar }. + * pchar = unreserved | pct_encoded | sub_delims | ':' | '@'. + * unreserved = ALPHA | DIGIT | '-' | '.' | '_' | '~'. + * pct_encoded = '%' XDIGIT XDIGIT. + * sub_delims = '!' | '$' | '&' | '\'' | '(' | ')' | '*' | '+' | + * ',' | ';' | '='. + * + * The grammar is extracted from RFC 3986. It is slightly modified to + * aid in parser creation, but the end result is the same + * (path_absolute is defined slightly differently - split into + * multiple productions). + * + * https://datatracker.ietf.org/doc/html/rfc3986#appendix-A + */ + +typedef struct isc_http_location_parser_state { + const char *str; +} isc_http_location_parser_state_t; + +static bool +rule_loc_path_absolute(isc_http_location_parser_state_t *); + +static bool +rule_loc_segments(isc_http_location_parser_state_t *); + +static bool +rule_loc_slash_segment(isc_http_location_parser_state_t *); + +static bool +rule_loc_segment(isc_http_location_parser_state_t *); + +static bool +rule_loc_segment_nz(isc_http_location_parser_state_t *); + +static bool +rule_loc_pchar(isc_http_location_parser_state_t *); + +static bool +rule_loc_unreserved(isc_http_location_parser_state_t *); + +static bool +rule_loc_pct_encoded(isc_http_location_parser_state_t *); + +static bool +rule_loc_sub_delims(isc_http_location_parser_state_t *); + +static bool +rule_loc_path_absolute(isc_http_location_parser_state_t *st) { + if (MATCH('/')) { + ADVANCE(); + } else { + return (false); + } + + (void)rule_loc_segments(st); + + if (MATCH('\0')) { + ADVANCE(); + } else { + return (false); + } + + return (true); +} + +static bool +rule_loc_segments(isc_http_location_parser_state_t *st) { + if (!rule_loc_segment_nz(st)) { + return (false); + } + + while (rule_loc_slash_segment(st)) { + /* zero or more */; + } + + return (true); +} + +static bool +rule_loc_slash_segment(isc_http_location_parser_state_t *st) { + if (MATCH('/')) { + ADVANCE(); + } else { + return (false); + } + + return (rule_loc_segment(st)); +} + +static bool +rule_loc_segment(isc_http_location_parser_state_t *st) { + while (rule_loc_pchar(st)) { + /* zero or more */; + } + + return (true); +} + +static bool +rule_loc_segment_nz(isc_http_location_parser_state_t *st) { + if (!rule_loc_pchar(st)) { + return (false); + } + + while (rule_loc_pchar(st)) { + /* zero or more */; + } + + return (true); +} + +static bool +rule_loc_pchar(isc_http_location_parser_state_t *st) { + if (rule_loc_unreserved(st)) { + return (true); + } else if (rule_loc_pct_encoded(st)) { + return (true); + } else if (rule_loc_sub_delims(st)) { + return (true); + } else if (MATCH(':') || MATCH('@')) { + ADVANCE(); + return (true); + } + + return (false); +} + +static bool +rule_loc_unreserved(isc_http_location_parser_state_t *st) { + if (MATCH_ALPHA() | MATCH_DIGIT() | MATCH('-') | MATCH('.') | + MATCH('_') | MATCH('~')) + { + ADVANCE(); + return (true); + } + return (false); +} + +static bool +rule_loc_pct_encoded(isc_http_location_parser_state_t *st) { + if (!MATCH('%')) { + return (false); + } + ADVANCE(); + + if (!MATCH_XDIGIT()) { + return (false); + } + ADVANCE(); + + if (!MATCH_XDIGIT()) { + return (false); + } + ADVANCE(); + + return (true); +} + +static bool +rule_loc_sub_delims(isc_http_location_parser_state_t *st) { + if (MATCH('!') | MATCH('$') | MATCH('&') | MATCH('\'') | MATCH('(') | + MATCH(')') | MATCH('*') | MATCH('+') | MATCH(',') | MATCH(';') | + MATCH('=')) + { + ADVANCE(); + return (true); + } + + return (false); +} + +bool +isc_nm_http_path_isvalid(const char *path) { + isc_http_location_parser_state_t state = { 0 }; + + REQUIRE(path != NULL); + + state.str = path; + + return (rule_loc_path_absolute(&state)); +} diff --git a/lib/isc/netmgr/netmgr-int.h b/lib/isc/netmgr/netmgr-int.h new file mode 100644 index 0000000..364a933 --- /dev/null +++ b/lib/isc/netmgr/netmgr-int.h @@ -0,0 +1,2273 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/sockaddr.h> +#include <isc/stats.h> +#include <isc/thread.h> +#include <isc/tls.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) + +/* + * Make sure our RECVBUF size is large enough + */ + +STATIC_ASSERT(ISC_NETMGR_UDP_RECVBUF_SIZE <= ISC_NETMGR_RECVBUF_SIZE, + "UDP receive buffer size must be smaller or equal than worker " + "receive buffer size"); + +STATIC_ASSERT(ISC_NETMGR_TCP_RECVBUF_SIZE <= ISC_NETMGR_RECVBUF_SIZE, + "TCP receive buffer size must be smaller or equal than worker " + "receive buffer size"); + +/*% + * 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 or TLSDNS connections, so there's no risk of overrun + * when using a buffer this size. + */ +#define NM_BIG_BUF ISC_NETMGR_TCP_RECVBUF_SIZE * 2 + +/*% + * Maximum segment size (MSS) of TCP socket on which the server responds to + * queries. Value lower than common MSS on Ethernet (1220, that is 1280 (IPv6 + * minimum link MTU) - 40 (IPv6 fixed header) - 20 (TCP fixed header)) will + * address path MTU problem. + */ +#define NM_MAXSEG (1280 - 20 - 40) + +/* + * 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) +#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 *); +typedef struct isc_nm_http_session isc_nm_http_session_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_nm_http_session_t *httpsession; + + 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_routeconnect, + + 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_tlsclose, + netievent_tlssend, + netievent_tlsstartread, + netievent_tlsconnect, + netievent_tlsdobio, + netievent_tlscancel, + + netievent_tlsdnsaccept, + netievent_tlsdnsconnect, + netievent_tlsdnsclose, + netievent_tlsdnssend, + netievent_tlsdnsread, + netievent_tlsdnscancel, + netievent_tlsdnscycle, + netievent_tlsdnsshutdown, + + netievent_httpclose, + netievent_httpsend, + netievent_httpendpoints, + + netievent_shutdown, + netievent_stop, + netievent_pause, + + netievent_connectcb, + netievent_readcb, + netievent_sendcb, + + netievent_detach, + netievent_close, + + netievent_task, + netievent_privilegedtask, + + netievent_settlsctx, + + /* + * 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_tlsdnslisten, + netievent_tlsdnsstop, + netievent_sockstop, /* for multilayer sockets */ + + 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 */ + int connect_tries; /* connect retries */ + + 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; +}; + +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_tlsconnect { + NETIEVENT__SOCKET; + SSL_CTX *ctx; + isc_sockaddr_t local; /* local address */ + isc_sockaddr_t peer; /* peer address */ +} isc__netievent_tlsconnect_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 struct isc__netievent__tlsctx { + NETIEVENT__SOCKET; + isc_tlsctx_t *tlsctx; +} isc__netievent__tlsctx_t; + +#define NETIEVENT_SOCKET_TLSCTX_TYPE(type) \ + typedef isc__netievent__tlsctx_t isc__netievent_##type##_t; + +#define NETIEVENT_SOCKET_TLSCTX_DECL(type) \ + isc__netievent_##type##_t *isc__nm_get_netievent_##type( \ + isc_nm_t *nm, isc_nmsocket_t *sock, isc_tlsctx_t *tlsctx); \ + void isc__nm_put_netievent_##type(isc_nm_t *nm, \ + isc__netievent_##type##_t *ievent); + +#define NETIEVENT_SOCKET_TLSCTX_DEF(type) \ + isc__netievent_##type##_t *isc__nm_get_netievent_##type( \ + isc_nm_t *nm, isc_nmsocket_t *sock, isc_tlsctx_t *tlsctx) { \ + isc__netievent_##type##_t *ievent = \ + isc__nm_get_netievent(nm, netievent_##type); \ + isc__nmsocket_attach(sock, &ievent->sock); \ + isc_tlsctx_attach(tlsctx, &ievent->tlsctx); \ + \ + return (ievent); \ + } \ + \ + void isc__nm_put_netievent_##type(isc_nm_t *nm, \ + isc__netievent_##type##_t *ievent) { \ + isc_tlsctx_free(&ievent->tlsctx); \ + isc__nmsocket_detach(&ievent->sock); \ + isc__nm_put_netievent(nm, ievent); \ + } + +#ifdef HAVE_LIBNGHTTP2 +typedef struct isc__netievent__http_eps { + NETIEVENT__SOCKET; + isc_nm_http_endpoints_t *endpoints; +} isc__netievent__http_eps_t; + +#define NETIEVENT_SOCKET_HTTP_EPS_TYPE(type) \ + typedef isc__netievent__http_eps_t isc__netievent_##type##_t; + +#define NETIEVENT_SOCKET_HTTP_EPS_DECL(type) \ + isc__netievent_##type##_t *isc__nm_get_netievent_##type( \ + isc_nm_t *nm, isc_nmsocket_t *sock, \ + isc_nm_http_endpoints_t *endpoints); \ + void isc__nm_put_netievent_##type(isc_nm_t *nm, \ + isc__netievent_##type##_t *ievent); + +#define NETIEVENT_SOCKET_HTTP_EPS_DEF(type) \ + isc__netievent_##type##_t *isc__nm_get_netievent_##type( \ + isc_nm_t *nm, isc_nmsocket_t *sock, \ + isc_nm_http_endpoints_t *endpoints) { \ + isc__netievent_##type##_t *ievent = \ + isc__nm_get_netievent(nm, netievent_##type); \ + isc__nmsocket_attach(sock, &ievent->sock); \ + isc_nm_http_endpoints_attach(endpoints, &ievent->endpoints); \ + \ + return (ievent); \ + } \ + \ + void isc__nm_put_netievent_##type(isc_nm_t *nm, \ + isc__netievent_##type##_t *ievent) { \ + isc_nm_http_endpoints_detach(&ievent->endpoints); \ + isc__nmsocket_detach(&ievent->sock); \ + isc__nm_put_netievent(nm, ievent); \ + } +#endif /* HAVE_LIBNGHTTP2 */ + +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_tlsconnect_t nitc; + isc__netievent__tlsctx_t nitls; +#ifdef HAVE_LIBNGHTTP2 + isc__netievent__http_eps_t nihttpeps; +#endif /* HAVE_LIBNGHTTP2 */ +} 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; + + /* + * Socket SO_RCVBUF and SO_SNDBUF values + */ + atomic_int_fast32_t recv_udp_buffer_size; + atomic_int_fast32_t send_udp_buffer_size; + atomic_int_fast32_t recv_tcp_buffer_size; + atomic_int_fast32_t send_tcp_buffer_size; + +#ifdef NETMGR_TRACE + ISC_LIST(isc_nmsocket_t) active_sockets; +#endif +}; + +/*% + * 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. + */ +typedef 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, + STATID_MAX = 11, +} isc__nm_statid_t; + +#if HAVE_LIBNGHTTP2 +typedef struct isc_nmsocket_tls_send_req { + isc_nmsocket_t *tlssock; + isc_region_t data; + isc_nm_cb_t cb; + void *cbarg; + isc_nmhandle_t *handle; + bool finish; + uint8_t smallbuf[512]; +} isc_nmsocket_tls_send_req_t; + +typedef enum isc_http_request_type { + ISC_HTTP_REQ_GET, + ISC_HTTP_REQ_POST, + ISC_HTTP_REQ_UNSUPPORTED +} isc_http_request_type_t; + +typedef enum isc_http_scheme_type { + ISC_HTTP_SCHEME_HTTP, + ISC_HTTP_SCHEME_HTTP_SECURE, + ISC_HTTP_SCHEME_UNSUPPORTED +} isc_http_scheme_type_t; + +typedef struct isc_nm_httpcbarg { + isc_nm_recv_cb_t cb; + void *cbarg; + LINK(struct isc_nm_httpcbarg) link; +} isc_nm_httpcbarg_t; + +typedef struct isc_nm_httphandler { + char *path; + isc_nm_recv_cb_t cb; + void *cbarg; + size_t extrahandlesize; + LINK(struct isc_nm_httphandler) link; +} isc_nm_httphandler_t; + +struct isc_nm_http_endpoints { + uint32_t magic; + isc_mem_t *mctx; + + ISC_LIST(isc_nm_httphandler_t) handlers; + ISC_LIST(isc_nm_httpcbarg_t) handler_cbargs; + + isc_refcount_t references; + atomic_bool in_use; +}; + +typedef struct isc_nmsocket_h2 { + isc_nmsocket_t *psock; /* owner of the structure */ + char *request_path; + char *query_data; + size_t query_data_len; + bool query_too_large; + isc_nm_httphandler_t *handler; + + isc_buffer_t rbuf; + isc_buffer_t wbuf; + + int32_t stream_id; + isc_nm_http_session_t *session; + + isc_nmsocket_t *httpserver; + + /* maximum concurrent streams (server-side) */ + atomic_uint_fast32_t max_concurrent_streams; + + uint32_t min_ttl; /* used to set "max-age" in responses */ + + isc_http_request_type_t request_type; + isc_http_scheme_type_t request_scheme; + + size_t content_length; + char clenbuf[128]; + + char cache_control_buf[128]; + + int headers_error_code; + size_t headers_data_processed; + + isc_nm_recv_cb_t cb; + void *cbarg; + LINK(struct isc_nmsocket_h2) link; + + isc_nm_http_endpoints_t **listener_endpoints; + size_t n_listener_endpoints; + + bool response_submitted; + struct { + char *uri; + bool post; + isc_tlsctx_t *tlsctx; + isc_sockaddr_t local_interface; + void *cstream; + const char *tls_peer_verify_string; + } connect; +} isc_nmsocket_h2_t; +#endif /* HAVE_LIBNGHTTP2 */ + +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; + + /*% TLS stuff */ + struct tls { + isc_tls_t *tls; + isc_tlsctx_t *ctx; + isc_tlsctx_client_session_cache_t *client_sess_cache; + bool client_session_saved; + BIO *app_rbio; + BIO *app_wbio; + BIO *ssl_rbio; + BIO *ssl_wbio; + enum { + TLS_STATE_NONE, + TLS_STATE_HANDSHAKE, + TLS_STATE_IO, + TLS_STATE_ERROR, + TLS_STATE_CLOSING + } state; + isc_region_t senddata; + ISC_LIST(isc__nm_uvreq_t) sendreqs; + bool cycle; + isc_result_t pending_error; + /* List of active send requests. */ + isc__nm_uvreq_t *pending_req; + bool alpn_negotiated; + const char *tls_verify_errmsg; + } tls; + +#if HAVE_LIBNGHTTP2 + /*% TLS stuff */ + struct tlsstream { + bool server; + BIO *bio_in; + BIO *bio_out; + isc_tls_t *tls; + isc_tlsctx_t *ctx; + isc_tlsctx_t **listener_tls_ctx; /*%< A context reference per + worker */ + size_t n_listener_tls_ctx; + isc_tlsctx_client_session_cache_t *client_sess_cache; + bool client_session_saved; + isc_nmsocket_t *tlslistener; + isc_nmsocket_t *tlssocket; + atomic_bool result_updated; + enum { + TLS_INIT, + TLS_HANDSHAKE, + TLS_IO, + TLS_CLOSED + } state; /*%< The order of these is significant */ + size_t nsending; + bool reading; + } tlsstream; + + isc_nmsocket_h2_t h2; +#endif /* HAVE_LIBNGHTTP2 */ + /*% + * 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; + + bool route_sock; + + /*% + * 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; + atomic_bool accepting; + atomic_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; + + isc_barrier_t barrier; + bool barrier_initialised; +#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. + */ + +void +isc__nmsocket_reset(isc_nmsocket_t *sock); +/*%< + * Reset and close the socket. + */ + +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_async_routeconnect(isc__networker_t *worker, isc__netievent_t *ev0); +/*%< + * Callback handler for route socket events. + */ + +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_tlsclose(isc__networker_t *worker, isc__netievent_t *ev0); + +void +isc__nm_async_tlssend(isc__networker_t *worker, isc__netievent_t *ev0); + +void +isc__nm_async_tlsstartread(isc__networker_t *worker, isc__netievent_t *ev0); + +void +isc__nm_async_tlsdobio(isc__networker_t *worker, isc__netievent_t *ev0); + +void +isc__nm_async_tlscancel(isc__networker_t *worker, isc__netievent_t *ev0); +/*%< + * Callback handlers for asynchronous TLS events. + */ + +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. + */ + +void +isc__nm_tlsdns_send(isc_nmhandle_t *handle, isc_region_t *region, + isc_nm_cb_t cb, void *cbarg); + +void +isc__nm_tlsdns_shutdown(isc_nmsocket_t *sock); + +void +isc__nm_tlsdns_close(isc_nmsocket_t *sock); +/*%< + * Close a TLSDNS socket. + */ + +void +isc__nm_tlsdns_stoplistening(isc_nmsocket_t *sock); +/*%< + * Stop listening on 'sock'. + */ + +void +isc__nm_tlsdns_settimeout(isc_nmhandle_t *handle, uint32_t timeout); +/*%< + * Set the read timeout and reset the timer for the TLSDNS socket + * associated with 'handle', and the TCP socket it wraps around. + */ + +void +isc__nm_tlsdns_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg); +/* + * Back-end implementation of isc_nm_read() for TLSDNS handles. + */ + +void +isc__nm_tlsdns_cancelread(isc_nmhandle_t *handle); +/*%< + * Stop reading on a connected TLSDNS handle. + */ + +const char * +isc__nm_tlsdns_verify_tls_peer_result_string(const isc_nmhandle_t *handle); + +void +isc__nm_async_tlsdnscycle(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_tlsdnsaccept(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_tlsdnsconnect(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_tlsdnslisten(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_tlsdnscancel(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_tlsdnsclose(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_tlsdnssend(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_tlsdnsstop(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_tlsdnsshutdown(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_tlsdnsread(isc__networker_t *worker, isc__netievent_t *ev0); +void +isc__nm_async_tlsdns_set_tlsctx(isc_nmsocket_t *listener, isc_tlsctx_t *tlsctx, + const int tid); +/*%< + * Callback handlers for asynchronous TLSDNS events. + */ + +isc_result_t +isc__nm_tlsdns_xfr_checkperm(isc_nmsocket_t *sock); +/*%< + * Check if it is permitted to do a zone transfer over the given TLSDNS + * socket. + * + * Returns: + * \li #ISC_R_SUCCESS Success, permission check passed successfully + * \li #ISC_R_DOTALPNERROR No permission because of ALPN tag mismatch + * \li any other result indicates failure (i.e. no permission) + * + * Requires: + * \li 'sock' is a valid TLSDNS socket. + */ + +void +isc__nm_tlsdns_cleanup_data(isc_nmsocket_t *sock); + +#if HAVE_LIBNGHTTP2 +void +isc__nm_tls_send(isc_nmhandle_t *handle, const isc_region_t *region, + isc_nm_cb_t cb, void *cbarg); + +void +isc__nm_tls_cancelread(isc_nmhandle_t *handle); + +/*%< + * Back-end implementation of isc_nm_send() for TLSDNS handles. + */ + +void +isc__nm_tls_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg); + +void +isc__nm_tls_close(isc_nmsocket_t *sock); +/*%< + * Close a TLS socket. + */ + +void +isc__nm_tls_pauseread(isc_nmhandle_t *handle); +/*%< + * Pause reading on this handle, while still remembering the callback. + */ + +void +isc__nm_tls_resumeread(isc_nmhandle_t *handle); +/*%< + * Resume reading from the handle. + * + */ + +void +isc__nm_tls_cleanup_data(isc_nmsocket_t *sock); + +void +isc__nm_tls_stoplistening(isc_nmsocket_t *sock); + +void +isc__nm_tls_settimeout(isc_nmhandle_t *handle, uint32_t timeout); +void +isc__nm_tls_cleartimeout(isc_nmhandle_t *handle); +/*%< + * Set the read timeout and reset the timer for the socket + * associated with 'handle', and the TCP socket it wraps + * around. + */ + +const char * +isc__nm_tls_verify_tls_peer_result_string(const isc_nmhandle_t *handle); + +void +isc__nmhandle_tls_keepalive(isc_nmhandle_t *handle, bool value); +/*%< + * Set the keepalive value on the underlying TCP handle. + */ + +void +isc__nm_async_tls_set_tlsctx(isc_nmsocket_t *listener, isc_tlsctx_t *tlsctx, + const int tid); + +void +isc__nmhandle_tls_setwritetimeout(isc_nmhandle_t *handle, + uint64_t write_timeout); + +void +isc__nm_http_stoplistening(isc_nmsocket_t *sock); + +void +isc__nm_http_settimeout(isc_nmhandle_t *handle, uint32_t timeout); +void +isc__nm_http_cleartimeout(isc_nmhandle_t *handle); +/*%< + * Set the read timeout and reset the timer for the socket + * associated with 'handle', and the TLS/TCP socket it wraps + * around. + */ + +void +isc__nmhandle_http_keepalive(isc_nmhandle_t *handle, bool value); +/*%< + * Set the keepalive value on the underlying session handle + */ + +void +isc__nm_http_initsocket(isc_nmsocket_t *sock); + +void +isc__nm_http_cleanup_data(isc_nmsocket_t *sock); + +isc_result_t +isc__nm_http_request(isc_nmhandle_t *handle, isc_region_t *region, + isc_nm_recv_cb_t reply_cb, void *cbarg); + +void +isc__nm_http_send(isc_nmhandle_t *handle, const isc_region_t *region, + isc_nm_cb_t cb, void *cbarg); + +void +isc__nm_http_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg); + +void +isc__nm_http_close(isc_nmsocket_t *sock); + +void +isc__nm_http_bad_request(isc_nmhandle_t *handle); +/*%< + * Respond to the request with 400 "Bad Request" status. + * + * Requires: + * \li 'handle' is a valid HTTP netmgr handle object, referencing a server-side + * socket + */ + +bool +isc__nm_http_has_encryption(const isc_nmhandle_t *handle); + +void +isc__nm_http_set_maxage(isc_nmhandle_t *handle, const uint32_t ttl); + +const char * +isc__nm_http_verify_tls_peer_result_string(const isc_nmhandle_t *handle); + +void +isc__nm_async_httpsend(isc__networker_t *worker, isc__netievent_t *ev0); + +void +isc__nm_async_httpclose(isc__networker_t *worker, isc__netievent_t *ev0); + +void +isc__nm_async_httpendpoints(isc__networker_t *worker, isc__netievent_t *ev0); + +bool +isc__nm_parse_httpquery(const char *query_string, const char **start, + size_t *len); + +char * +isc__nm_base64url_to_base64(isc_mem_t *mem, const char *base64url, + const size_t base64url_len, size_t *res_len); + +char * +isc__nm_base64_to_base64url(isc_mem_t *mem, const char *base64, + const size_t base64_len, size_t *res_len); + +void +isc__nm_httpsession_attach(isc_nm_http_session_t *source, + isc_nm_http_session_t **targetp); +void +isc__nm_httpsession_detach(isc_nm_http_session_t **sessionp); + +void +isc__nm_http_set_tlsctx(isc_nmsocket_t *sock, isc_tlsctx_t *tlsctx); + +void +isc__nm_http_set_max_streams(isc_nmsocket_t *listener, + const uint32_t max_concurrent_streams); + +#endif + +void +isc__nm_async_settlsctx(isc__networker_t *worker, isc__netievent_t *ev0); + +#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_async_sockstop(isc__networker_t *worker, isc__netievent_t *ev0); + +void +isc__nm_incstats(isc_nmsocket_t *sock, isc__nm_statid_t id); +/*%< + * Increment socket-related statistics counters. + */ + +void +isc__nm_decstats(isc_nmsocket_t *sock, isc__nm_statid_t id); +/*%< + * 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_v6only(uv_os_sock_t fd, sa_family_t sa_family); +/*%< + * Restrict the socket to sending and receiving IPv6 packets only + */ + +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). + */ + +isc_result_t +isc__nm_socket_tcp_maxseg(uv_os_sock_t fd, int size); +/*%< + * Set the TCP maximum segment size + */ + +isc_result_t +isc__nm_socket_min_mtu(uv_os_sock_t fd, sa_family_t sa_family); +/*%< + * Use minimum MTU on IPv6 sockets + */ + +void +isc__nm_set_network_buffers(isc_nm_t *nm, uv_handle_t *handle); +/*%> + * Sets the pre-configured network buffers size on the handle. + */ + +void +isc__nmsocket_barrier_init(isc_nmsocket_t *listener); +/*%> + * Initialise the socket synchronisation barrier according to the + * number of children. + */ + +void +isc__nmsocket_stop(isc_nmsocket_t *listener); +/*%> + * Broadcast "stop" event for a listener socket across all workers and + * wait its processing completion - then, stop and close the underlying + * transport listener socket. + * + * The primitive is used in multi-layer transport listener sockets to + * implement shutdown properly: after the broadcasted events has been + * processed it is safe to destroy the shared data within the listener + * socket (including shutting down the underlying transport listener + * socket). + */ + +/* + * 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(tlsclose); +/* NETIEVENT_SOCKET_TYPE(tlsconnect); */ /* unique type, defined independently + */ +NETIEVENT_SOCKET_TYPE(tlsdobio); +NETIEVENT_SOCKET_TYPE(tlsstartread); +NETIEVENT_SOCKET_HANDLE_TYPE(tlscancel); +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_TYPE(tlsdnsclose); +NETIEVENT_SOCKET_TYPE(tlsdnsread); +NETIEVENT_SOCKET_TYPE(tlsdnsstop); +NETIEVENT_SOCKET_TYPE(tlsdnsshutdown); +NETIEVENT_SOCKET_TYPE(tlsdnslisten); +NETIEVENT_SOCKET_REQ_TYPE(tlsdnsconnect); +NETIEVENT_SOCKET_REQ_TYPE(tlsdnssend); +NETIEVENT_SOCKET_HANDLE_TYPE(tlsdnscancel); +NETIEVENT_SOCKET_QUOTA_TYPE(tlsdnsaccept); +NETIEVENT_SOCKET_TYPE(tlsdnscycle); + +#ifdef HAVE_LIBNGHTTP2 +NETIEVENT_SOCKET_REQ_TYPE(httpsend); +NETIEVENT_SOCKET_TYPE(httpclose); +NETIEVENT_SOCKET_HTTP_EPS_TYPE(httpendpoints); +#endif /* HAVE_LIBNGHTTP2 */ + +NETIEVENT_SOCKET_REQ_TYPE(tcpconnect); +NETIEVENT_SOCKET_REQ_TYPE(tcpsend); +NETIEVENT_SOCKET_TYPE(tcpstartread); +NETIEVENT_SOCKET_REQ_TYPE(tlssend); +NETIEVENT_SOCKET_REQ_TYPE(udpconnect); + +NETIEVENT_SOCKET_REQ_TYPE(routeconnect); + +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); + +NETIEVENT_SOCKET_TLSCTX_TYPE(settlsctx); +NETIEVENT_SOCKET_TYPE(sockstop); + +/* 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(tlsclose); +NETIEVENT_SOCKET_DECL(tlsconnect); +NETIEVENT_SOCKET_DECL(tlsdobio); +NETIEVENT_SOCKET_DECL(tlsstartread); +NETIEVENT_SOCKET_HANDLE_DECL(tlscancel); +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_DECL(tlsdnsclose); +NETIEVENT_SOCKET_DECL(tlsdnsread); +NETIEVENT_SOCKET_DECL(tlsdnsstop); +NETIEVENT_SOCKET_DECL(tlsdnsshutdown); +NETIEVENT_SOCKET_DECL(tlsdnslisten); +NETIEVENT_SOCKET_REQ_DECL(tlsdnsconnect); +NETIEVENT_SOCKET_REQ_DECL(tlsdnssend); +NETIEVENT_SOCKET_HANDLE_DECL(tlsdnscancel); +NETIEVENT_SOCKET_QUOTA_DECL(tlsdnsaccept); +NETIEVENT_SOCKET_DECL(tlsdnscycle); + +#ifdef HAVE_LIBNGHTTP2 +NETIEVENT_SOCKET_REQ_DECL(httpsend); +NETIEVENT_SOCKET_DECL(httpclose); +NETIEVENT_SOCKET_HTTP_EPS_DECL(httpendpoints); +#endif /* HAVE_LIBNGHTTP2 */ + +NETIEVENT_SOCKET_REQ_DECL(tcpconnect); +NETIEVENT_SOCKET_REQ_DECL(tcpsend); +NETIEVENT_SOCKET_REQ_DECL(tlssend); +NETIEVENT_SOCKET_REQ_DECL(udpconnect); + +NETIEVENT_SOCKET_REQ_DECL(routeconnect); + +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); + +NETIEVENT_SOCKET_TLSCTX_DECL(settlsctx); +NETIEVENT_SOCKET_DECL(sockstop); + +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); +void +isc__nm_tlsdns_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result, + bool async); + +isc_result_t +isc__nm_tcpdns_processbuffer(isc_nmsocket_t *sock); +isc_result_t +isc__nm_tlsdns_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); +void +isc__nm_tlsdns_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); + +#define UV_RUNTIME_CHECK(func, ret) \ + if (ret != 0) { \ + FATAL_ERROR("%s failed: %s\n", #func, uv_strerror(ret)); \ + } + +void +isc__nmsocket_log_tls_session_reuse(isc_nmsocket_t *sock, isc_tls_t *tls); diff --git a/lib/isc/netmgr/netmgr.c b/lib/isc/netmgr/netmgr.c new file mode 100644 index 0000000..b19d468 --- /dev/null +++ b/lib/isc/netmgr/netmgr.c @@ -0,0 +1,3991 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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> + +#include <isc/atomic.h> +#include <isc/backtrace.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/task.h> +#include <isc/thread.h> +#include <isc/tls.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. + */ + +static thread_local int isc__nm_tid_v = ISC_NETMGR_TID_UNKNOWN; + +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); + +/*%< + * 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); +} + +void +isc__nm_force_tid(int tid) { + isc__nm_tid_v = tid; +} + +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_LINUX_RECVERR +#define MINIMAL_UV_VERSION UV_VERSION(1, 42, 0) +#elif 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) +#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) { + FATAL_ERROR("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) { + FATAL_ERROR("libuv version too old: running with libuv %s " + "when compiled with libuv %s will lead to " + "libuv failures", + uv_version_string(), UV_VERSION_STRING); + } + + 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); + atomic_init(&mgr->recv_tcp_buffer_size, 0); + atomic_init(&mgr->send_tcp_buffer_size, 0); + atomic_init(&mgr->recv_udp_buffer_size, 0); + atomic_init(&mgr->send_udp_buffer_size, 0); +#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)); +} + +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); + + atomic_compare_exchange_enforced(&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); + + atomic_compare_exchange_enforced(&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)); + REQUIRE(handle->sock->tid == isc_nm_tid()); + + switch (handle->sock->type) { + case isc_nm_tcpsocket: + case isc_nm_udpsocket: + case isc_nm_tcpdnssocket: + case isc_nm_tlsdnssocket: + handle->sock->write_timeout = write_timeout; + break; +#ifdef HAVE_LIBNGHTTP2 + case isc_nm_tlssocket: + isc__nmhandle_tls_setwritetimeout(handle, write_timeout); + break; +#endif /* HAVE_LIBNGHTTP2 */ + default: + UNREACHABLE(); + break; + } +} + +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); +} + +void +isc_nm_setnetbuffers(isc_nm_t *mgr, int32_t recv_tcp, int32_t send_tcp, + int32_t recv_udp, int32_t send_udp) { + REQUIRE(VALID_NM(mgr)); + + atomic_store(&mgr->recv_tcp_buffer_size, recv_tcp); + atomic_store(&mgr->send_tcp_buffer_size, send_tcp); + atomic_store(&mgr->recv_udp_buffer_size, recv_udp); + atomic_store(&mgr->send_udp_buffer_size, send_udp); +} + +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); + + /* + * Tasks can block for a long time, especially when used by tools in + * interactive mode. Update the event loop's time to avoid unexpected + * errors when processing later events during the same callback. + * For example, newly started timers can fire too early, because the + * current time was stale. See the note about uv_update_time() in the + * https://docs.libuv.org/en/v1.x/timer.html#c.uv_timer_start page. + */ + uv_update_time(&worker->loop); + + 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(routeconnect); + + 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(tlsdnscycle); + NETIEVENT_CASE(tlsdnsaccept); + NETIEVENT_CASE(tlsdnslisten); + NETIEVENT_CASE(tlsdnsconnect); + NETIEVENT_CASE(tlsdnssend); + NETIEVENT_CASE(tlsdnscancel); + NETIEVENT_CASE(tlsdnsclose); + NETIEVENT_CASE(tlsdnsread); + NETIEVENT_CASE(tlsdnsstop); + NETIEVENT_CASE(tlsdnsshutdown); + +#if HAVE_LIBNGHTTP2 + NETIEVENT_CASE(tlsstartread); + NETIEVENT_CASE(tlssend); + NETIEVENT_CASE(tlsclose); + NETIEVENT_CASE(tlsdobio); + NETIEVENT_CASE(tlscancel); + + NETIEVENT_CASE(httpsend); + NETIEVENT_CASE(httpclose); + NETIEVENT_CASE(httpendpoints); +#endif + NETIEVENT_CASE(settlsctx); + NETIEVENT_CASE(sockstop); + + 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(tlsclose); +NETIEVENT_SOCKET_DEF(tlsconnect); +NETIEVENT_SOCKET_DEF(tlsdobio); +NETIEVENT_SOCKET_DEF(tlsstartread); +NETIEVENT_SOCKET_HANDLE_DEF(tlscancel); +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_DEF(tlsdnsclose); +NETIEVENT_SOCKET_DEF(tlsdnsread); +NETIEVENT_SOCKET_DEF(tlsdnsstop); +NETIEVENT_SOCKET_DEF(tlsdnslisten); +NETIEVENT_SOCKET_REQ_DEF(tlsdnsconnect); +NETIEVENT_SOCKET_REQ_DEF(tlsdnssend); +NETIEVENT_SOCKET_HANDLE_DEF(tlsdnscancel); +NETIEVENT_SOCKET_QUOTA_DEF(tlsdnsaccept); +NETIEVENT_SOCKET_DEF(tlsdnscycle); +NETIEVENT_SOCKET_DEF(tlsdnsshutdown); + +#ifdef HAVE_LIBNGHTTP2 +NETIEVENT_SOCKET_REQ_DEF(httpsend); +NETIEVENT_SOCKET_DEF(httpclose); +NETIEVENT_SOCKET_HTTP_EPS_DEF(httpendpoints); +#endif /* HAVE_LIBNGHTTP2 */ + +NETIEVENT_SOCKET_REQ_DEF(tcpconnect); +NETIEVENT_SOCKET_REQ_DEF(tcpsend); +NETIEVENT_SOCKET_REQ_DEF(tlssend); +NETIEVENT_SOCKET_REQ_DEF(udpconnect); +NETIEVENT_SOCKET_REQ_DEF(routeconnect); +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); + +NETIEVENT_SOCKET_TLSCTX_DEF(settlsctx); +NETIEVENT_SOCKET_DEF(sockstop); + +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)); + + isc__nm_decstats(sock, STATID_ACTIVE); + + 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; + } + + 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_put(sock->mgr->mctx, sock->buf, sock->buf_size); + } + + 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); + isc__nm_tlsdns_cleanup_data(sock); +#if HAVE_LIBNGHTTP2 + isc__nm_tls_cleanup_data(sock); + isc__nm_http_cleanup_data(sock); +#endif + + INSIST(ISC_LIST_EMPTY(sock->tls.sendreqs)); + + if (sock->barrier_initialised) { + isc_barrier_destroy(&sock->barrier); + } + +#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; + case isc_nm_tlsdnssocket: + isc__nm_tlsdns_close(sock); + return; +#if HAVE_LIBNGHTTP2 + case isc_nm_tlssocket: + isc__nm_tls_close(sock); + break; + case isc_nm_httpsocket: + isc__nm_http_close(sock); + return; +#endif + 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 || + (*sockp)->type == isc_nm_tlsdnslistener || + (*sockp)->type == isc_nm_tlslistener || + (*sockp)->type == isc_nm_httplistener); + + 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); + + *sock = (isc_nmsocket_t){ .type = type, + .fd = -1, + .inactivehandles = isc_astack_new( + mgr->mctx, ISC_NM_HANDLES_STACK_SIZE), + .inactivereqs = isc_astack_new( + mgr->mctx, ISC_NM_REQS_STACK_SIZE) }; + + ISC_LIST_INIT(sock->tls.sendreqs); + + if (iface != NULL) { + family = iface->type.sa.sa_family; + sock->iface = *iface; + } else { + family = AF_UNSPEC; + } + +#if NETMGR_TRACE + sock->backtrace_size = isc_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: + switch (family) { + case AF_INET: + sock->statsindex = udp4statsindex; + break; + case AF_INET6: + sock->statsindex = udp6statsindex; + break; + case AF_UNSPEC: + /* + * Route sockets are AF_UNSPEC, and don't + * have stats counters. + */ + break; + default: + UNREACHABLE(); + } + break; + case isc_nm_tcpsocket: + case isc_nm_tcplistener: + case isc_nm_tcpdnssocket: + case isc_nm_tcpdnslistener: + case isc_nm_tlsdnssocket: + case isc_nm_tlsdnslistener: + case isc_nm_httpsocket: + case isc_nm_httplistener: + switch (family) { + case AF_INET: + sock->statsindex = tcp4statsindex; + break; + case AF_INET6: + sock->statsindex = tcp6statsindex; + break; + default: + UNREACHABLE(); + } + break; + default: + break; + } + + isc_mutex_init(&sock->lock); + isc_condition_init(&sock->cond); + isc_condition_init(&sock->scond); + isc_refcount_init(&sock->references, 1); + +#if HAVE_LIBNGHTTP2 + memset(&sock->tlsstream, 0, sizeof(sock->tlsstream)); +#endif /* HAVE_LIBNGHTTP2 */ + + 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); + +#if HAVE_LIBNGHTTP2 + isc__nm_http_initsocket(sock); +#endif + + sock->magic = NMSOCK_MAGIC; + + isc__nm_incstats(sock, STATID_ACTIVE); +} + +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 = isc_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: + case isc_nm_tlsdnssocket: + if (!atomic_load(&sock->client)) { + break; + } + FALLTHROUGH; + case isc_nm_tcpsocket: + case isc_nm_tlssocket: + 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; + } + +#if HAVE_LIBNGHTTP2 + if (sock->type == isc_nm_httpsocket && sock->h2.session) { + isc__nm_httpsession_attach(sock->h2.session, + &handle->httpsession); + } +#endif + + 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 || + handle->sock->type == isc_nm_tlssocket || + handle->sock->type == isc_nm_tlsdnssocket || + handle->sock->type == isc_nm_httpsocket); +} + +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; + uint_fast32_t ah; + + /* + * 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 + + ah = atomic_fetch_sub(&sock->ah, 1); + INSIST(ah > 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); + } +} + +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); + } + +#if HAVE_LIBNGHTTP2 + if (sock->type == isc_nm_httpsocket && handle->httpsession != NULL) { + isc__nm_httpsession_detach(&handle->httpsession); + } +#endif + + 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_get(sock->mgr->mctx, alloc_len); + sock->buf_size = alloc_len; + } else { + /* We have the buffer but it's too small */ + sock->buf = isc_mem_reget(sock->mgr->mctx, sock->buf, + sock->buf_size, 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(atomic_load(&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); + + atomic_store(&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__nm_incstats(sock, STATID_CONNECTFAIL); + + isc__nmsocket_timer_stop(sock); + uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock); + + atomic_compare_exchange_enforced(&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)); + 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; + case isc_nm_tlsdnssocket: + isc__nm_tlsdns_failed_read_cb(sock, result, async); + 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. + */ + atomic_compare_exchange_enforced(&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(atomic_load(&sock->reading)); + + if (atomic_load(&sock->client)) { + uv_timer_stop(timer); + + 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, 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 (uv_is_closing((uv_handle_t *)&sock->read_timer)) { + return; + } + + 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: + case isc_nm_tlssocket: + isc_nmhandle_attach(sock->statichandle, &req->handle); + break; + default: + if (atomic_load(&sock->client) && sock->statichandle != NULL) { + 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: + case isc_nm_tlsdnssocket: + 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 (atomic_load(&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; + case isc_nm_tlsdnssocket: + r = uv_read_start(&sock->uv_handle.stream, isc__nm_alloc_cb, + isc__nm_tlsdns_read_cb); + break; + default: + UNREACHABLE(); + } + if (r != 0) { + result = isc__nm_uverr2result(r); + } else { + atomic_store(&sock->reading, true); + } + + return (result); +} + +void +isc__nm_stop_reading(isc_nmsocket_t *sock) { + int r; + + if (!atomic_load(&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: + case isc_nm_tlsdnssocket: + r = uv_read_stop(&sock->uv_handle.stream); + UV_RUNTIME_CHECK(uv_read_stop, r); + break; + default: + UNREACHABLE(); + } + atomic_store(&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) || + isc__nm_closing(sock) || + (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)); + case isc_nm_tlsdnssocket: + return (isc__nm_tlsdns_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. In this case we'll be called again + * later by isc__nm_resume_processing(). + */ +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)) + { + 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) { +#if HAVE_LIBNGHTTP2 + case isc_nm_httpsocket: + isc__nm_http_cleartimeout(handle); + return; + case isc_nm_tlssocket: + isc__nm_tls_cleartimeout(handle); + return; +#endif + 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) { +#if HAVE_LIBNGHTTP2 + case isc_nm_httpsocket: + isc__nm_http_settimeout(handle, timeout); + return; + case isc_nm_tlssocket: + isc__nm_tls_settimeout(handle, timeout); + return; +#endif + 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: + case isc_nm_tlsdnssocket: + 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; +#if HAVE_LIBNGHTTP2 + case isc_nm_tlssocket: + isc__nmhandle_tls_keepalive(handle, value); + break; + case isc_nm_httpsocket: + isc__nmhandle_http_keepalive(handle, value); + break; +#endif /* HAVE_LIBNGHTTP2 */ + default: + /* + * For any other protocol, this is a no-op. + */ + return; + } +} + +bool +isc_nmhandle_timer_running(isc_nmhandle_t *handle) { + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + + return (isc__nmsocket_timer_running(handle->sock)); +} + +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, + .connect_tries = 3, + }; + 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; + case isc_nm_tlsdnssocket: + isc__nm_tlsdns_send(handle, region, cb, cbarg); + break; +#if HAVE_LIBNGHTTP2 + case isc_nm_tlssocket: + isc__nm_tls_send(handle, region, cb, cbarg); + break; + case isc_nm_httpsocket: + isc__nm_http_send(handle, region, cb, cbarg); + break; +#endif + default: + UNREACHABLE(); + } +} + +void +isc_nm_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg) { + REQUIRE(VALID_NMHANDLE(handle)); + + 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; + case isc_nm_tlsdnssocket: + isc__nm_tlsdns_read(handle, cb, cbarg); + break; +#if HAVE_LIBNGHTTP2 + case isc_nm_tlssocket: + isc__nm_tls_read(handle, cb, cbarg); + break; + case isc_nm_httpsocket: + isc__nm_http_read(handle, cb, cbarg); + break; +#endif + 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; + case isc_nm_tlsdnssocket: + isc__nm_tlsdns_cancelread(handle); + break; +#if HAVE_LIBNGHTTP2 + case isc_nm_tlssocket: + isc__nm_tls_cancelread(handle); + break; +#endif + 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; +#if HAVE_LIBNGHTTP2 + case isc_nm_tlssocket: + isc__nm_tls_pauseread(handle); + break; +#endif + 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; +#if HAVE_LIBNGHTTP2 + case isc_nm_tlssocket: + isc__nm_tls_resumeread(handle); + break; +#endif + 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; + case isc_nm_tlsdnslistener: + isc__nm_tlsdns_stoplistening(sock); + break; +#if HAVE_LIBNGHTTP2 + case isc_nm_tlslistener: + isc__nm_tls_stoplistening(sock); + break; + case isc_nm_httplistener: + isc__nm_http_stoplistening(sock); + break; +#endif + default: + UNREACHABLE(); + } +} + +void +isc__nmsocket_stop(isc_nmsocket_t *listener) { + isc__netievent_sockstop_t ievent = { .sock = listener }; + + REQUIRE(VALID_NMSOCK(listener)); + + if (!atomic_compare_exchange_strong(&listener->closing, + &(bool){ false }, true)) + { + UNREACHABLE(); + } + + for (size_t i = 0; i < listener->nchildren; i++) { + isc__networker_t *worker = &listener->mgr->workers[i]; + isc__netievent_sockstop_t *ev; + + if (isc__nm_in_netthread() && i == (size_t)isc_nm_tid()) { + continue; + } + + ev = isc__nm_get_netievent_sockstop(listener->mgr, listener); + isc__nm_enqueue_ievent(worker, (isc__netievent_t *)ev); + } + + if (isc__nm_in_netthread()) { + isc__nm_async_sockstop(&listener->mgr->workers[isc_nm_tid()], + (isc__netievent_t *)&ievent); + } +} + +void +isc__nmsocket_barrier_init(isc_nmsocket_t *listener) { + REQUIRE(listener->nchildren > 0); + isc_barrier_init(&listener->barrier, listener->nchildren); + listener->barrier_initialised = true; +} + +void +isc__nm_async_sockstop(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_sockstop_t *ievent = (isc__netievent_sockstop_t *)ev0; + isc_nmsocket_t *listener = ievent->sock; + UNUSED(worker); + + (void)atomic_fetch_sub(&listener->rchildren, 1); + isc_barrier_wait(&listener->barrier); + + if (listener->tid != isc_nm_tid()) { + return; + } + + if (!atomic_compare_exchange_strong(&listener->listening, + &(bool){ true }, false)) + { + UNREACHABLE(); + } + + INSIST(atomic_load(&listener->rchildren) == 0); + + listener->accept_cb = NULL; + listener->accept_cbarg = NULL; + listener->recv_cb = NULL; + listener->recv_cbarg = NULL; + + if (listener->outer != NULL) { + isc_nm_stoplistening(listener->outer); + isc__nmsocket_detach(&listener->outer); + } + + atomic_store(&listener->closed, true); +} + +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: + case isc_nm_tlsdnssocket: + /* + * This can be called from the TCP write timeout, or + * from the TCPDNS or TLSDNS branches of isc_nm_bad_request(). + */ + 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_tlsdnssocket: + isc__nm_tlsdns_shutdown(sock); + break; + case isc_nm_udplistener: + case isc_nm_tcplistener: + case isc_nm_tcpdnslistener: + case isc_nm_tlsdnslistener: + 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: + case isc_nm_tlsdnssocket: + 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_nmsocket_t *sock, isc__nm_statid_t id) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(id < STATID_MAX); + + if (sock->statsindex != NULL && sock->mgr->stats != NULL) { + isc_stats_increment(sock->mgr->stats, sock->statsindex[id]); + } +} + +void +isc__nm_decstats(isc_nmsocket_t *sock, isc__nm_statid_t id) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(id < STATID_MAX); + + if (sock->statsindex != NULL && sock->mgr->stats != NULL) { + isc_stats_decrement(sock->mgr->stats, sock->statsindex[id]); + } +} + +isc_result_t +isc__nm_socket(int domain, int type, int protocol, uv_os_sock_t *sockp) { + int sock = socket(domain, type, protocol); + if (sock < 0) { + return (isc_errno_toresult(errno)); + } + + *sockp = (uv_os_sock_t)sock; + return (ISC_R_SUCCESS); +} + +void +isc__nm_closesocket(uv_os_sock_t sock) { + close(sock); +} + +#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); +} + +isc_result_t +isc__nm_socket_v6only(uv_os_sock_t fd, sa_family_t sa_family) { + /* + * Enable the IPv6-only option on IPv6 sockets + */ + if (sa_family == AF_INET6) { +#if defined(IPV6_V6ONLY) + if (setsockopt_on(fd, IPPROTO_IPV6, IPV6_V6ONLY) == -1) { + return (ISC_R_FAILURE); + } else { + return (ISC_R_SUCCESS); + } +#else + UNUSED(fd); +#endif + } + return (ISC_R_NOTIMPLEMENTED); +} + +isc_result_t +isc_nm_checkaddr(const isc_sockaddr_t *addr, isc_socktype_t type) { + int proto, pf, addrlen, fd, r; + + REQUIRE(addr != NULL); + + switch (type) { + case isc_socktype_tcp: + proto = SOCK_STREAM; + break; + case isc_socktype_udp: + proto = SOCK_DGRAM; + break; + default: + return (ISC_R_NOTIMPLEMENTED); + } + + pf = isc_sockaddr_pf(addr); + if (pf == AF_INET) { + addrlen = sizeof(struct sockaddr_in); + } else { + addrlen = sizeof(struct sockaddr_in6); + } + + fd = socket(pf, proto, 0); + if (fd < 0) { + return (isc_errno_toresult(errno)); + } + + r = bind(fd, (const struct sockaddr *)&addr->type.sa, addrlen); + if (r < 0) { + close(fd); + return (isc_errno_toresult(errno)); + } + + close(fd); + return (ISC_R_SUCCESS); +} + +#if 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 +} + +isc_result_t +isc__nm_socket_tcp_maxseg(uv_os_sock_t fd, int size) { +#ifdef TCP_MAXSEG + if (setsockopt(fd, IPPROTO_TCP, TCP_MAXSEG, (void *)&size, + sizeof(size))) + { + return (ISC_R_FAILURE); + } else { + return (ISC_R_SUCCESS); + } +#else + UNUSED(fd); + UNUSED(size); + return (ISC_R_SUCCESS); +#endif +} + +isc_result_t +isc__nm_socket_min_mtu(uv_os_sock_t fd, sa_family_t sa_family) { + if (sa_family != AF_INET6) { + return (ISC_R_SUCCESS); + } +#ifdef IPV6_USE_MIN_MTU + if (setsockopt_on(fd, IPPROTO_IPV6, IPV6_USE_MIN_MTU) == -1) { + return (ISC_R_FAILURE); + } +#elif defined(IPV6_MTU) + if (setsockopt(fd, IPPROTO_IPV6, IPV6_MTU, &(int){ 1280 }, + sizeof(int)) == -1) + { + return (ISC_R_FAILURE); + } +#else + UNUSED(fd); +#endif + + return (ISC_R_SUCCESS); +} + +void +isc__nm_set_network_buffers(isc_nm_t *nm, uv_handle_t *handle) { + int32_t recv_buffer_size = 0; + int32_t send_buffer_size = 0; + + switch (handle->type) { + case UV_TCP: + recv_buffer_size = + atomic_load_relaxed(&nm->recv_tcp_buffer_size); + send_buffer_size = + atomic_load_relaxed(&nm->send_tcp_buffer_size); + break; + case UV_UDP: + recv_buffer_size = + atomic_load_relaxed(&nm->recv_udp_buffer_size); + send_buffer_size = + atomic_load_relaxed(&nm->send_udp_buffer_size); + break; + default: + UNREACHABLE(); + } + + if (recv_buffer_size > 0) { + int r = uv_recv_buffer_size(handle, &recv_buffer_size); + UV_RUNTIME_CHECK(uv_recv_buffer_size, r); + } + + if (send_buffer_size > 0) { + int r = uv_send_buffer_size(handle, &send_buffer_size); + UV_RUNTIME_CHECK(uv_send_buffer_size, r); + } +} + +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_sequential(isc_nmhandle_t *handle) { + isc_nmsocket_t *sock = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + + sock = handle->sock; + + switch (sock->type) { + case isc_nm_tcpdnssocket: + case isc_nm_tlsdnssocket: + break; + case isc_nm_httpsocket: + return; + default: + UNREACHABLE(); + } + + /* + * 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 isc__nm_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); +} + +void +isc_nm_bad_request(isc_nmhandle_t *handle) { + isc_nmsocket_t *sock = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + + sock = handle->sock; + + switch (sock->type) { + case isc_nm_udpsocket: + return; + case isc_nm_tcpdnssocket: + case isc_nm_tlsdnssocket: + REQUIRE(sock->parent == NULL); + isc__nmsocket_reset(sock); + return; +#if HAVE_LIBNGHTTP2 + case isc_nm_httpsocket: + isc__nm_http_bad_request(handle); + return; +#endif /* HAVE_LIBNGHTTP2 */ + case isc_nm_tcpsocket: +#if HAVE_LIBNGHTTP2 + case isc_nm_tlssocket: +#endif /* HAVE_LIBNGHTTP2 */ + default: + UNREACHABLE(); + break; + } +} + +isc_result_t +isc_nm_xfr_checkperm(isc_nmhandle_t *handle) { + isc_nmsocket_t *sock = NULL; + isc_result_t result = ISC_R_NOPERM; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + + sock = handle->sock; + + switch (sock->type) { + case isc_nm_tcpdnssocket: + result = ISC_R_SUCCESS; + break; + case isc_nm_tlsdnssocket: + result = isc__nm_tlsdns_xfr_checkperm(sock); + break; + default: + break; + } + + return (result); +} + +bool +isc_nm_is_http_handle(isc_nmhandle_t *handle) { + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + + return (handle->sock->type == isc_nm_httpsocket); +} + +void +isc_nm_set_maxage(isc_nmhandle_t *handle, const uint32_t ttl) { + isc_nmsocket_t *sock = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + REQUIRE(!atomic_load(&handle->sock->client)); + +#if !HAVE_LIBNGHTTP2 + UNUSED(ttl); +#endif + + sock = handle->sock; + switch (sock->type) { +#if HAVE_LIBNGHTTP2 + case isc_nm_httpsocket: + isc__nm_http_set_maxage(handle, ttl); + break; +#endif /* HAVE_LIBNGHTTP2 */ + case isc_nm_udpsocket: + case isc_nm_tcpdnssocket: + case isc_nm_tlsdnssocket: + return; + break; + + case isc_nm_tcpsocket: +#if HAVE_LIBNGHTTP2 + case isc_nm_tlssocket: +#endif /* HAVE_LIBNGHTTP2 */ + default: + UNREACHABLE(); + break; + } +} + +isc_nmsocket_type +isc_nm_socket_type(const isc_nmhandle_t *handle) { + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + + return (handle->sock->type); +} + +bool +isc_nm_has_encryption(const isc_nmhandle_t *handle) { + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + + switch (handle->sock->type) { + case isc_nm_tlsdnssocket: +#if HAVE_LIBNGHTTP2 + case isc_nm_tlssocket: +#endif /* HAVE_LIBNGHTTP2 */ + return (true); +#if HAVE_LIBNGHTTP2 + case isc_nm_httpsocket: + return (isc__nm_http_has_encryption(handle)); +#endif /* HAVE_LIBNGHTTP2 */ + default: + return (false); + }; + + return (false); +} + +void +isc__nm_async_settlsctx(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent__tlsctx_t *ev_tlsctx = (isc__netievent__tlsctx_t *)ev0; + const int tid = isc_nm_tid(); + isc_nmsocket_t *listener = ev_tlsctx->sock; + isc_tlsctx_t *tlsctx = ev_tlsctx->tlsctx; + + UNUSED(worker); + + switch (listener->type) { + case isc_nm_tlsdnslistener: + isc__nm_async_tlsdns_set_tlsctx(listener, tlsctx, tid); + break; +#if HAVE_LIBNGHTTP2 + case isc_nm_tlslistener: + isc__nm_async_tls_set_tlsctx(listener, tlsctx, tid); + break; +#endif /* HAVE_LIBNGHTTP2 */ + default: + UNREACHABLE(); + break; + }; +} + +static void +set_tlsctx_workers(isc_nmsocket_t *listener, isc_tlsctx_t *tlsctx) { + /* Update the TLS context reference for every worker thread. */ + for (size_t i = 0; i < (size_t)listener->mgr->nworkers; i++) { + isc__netievent__tlsctx_t *ievent = + isc__nm_get_netievent_settlsctx(listener->mgr, listener, + tlsctx); + isc__nm_enqueue_ievent(&listener->mgr->workers[i], + (isc__netievent_t *)ievent); + } +} + +void +isc_nmsocket_set_tlsctx(isc_nmsocket_t *listener, isc_tlsctx_t *tlsctx) { + REQUIRE(VALID_NMSOCK(listener)); + REQUIRE(tlsctx != NULL); + + switch (listener->type) { +#if HAVE_LIBNGHTTP2 + case isc_nm_httplistener: + /* + * We handle HTTP listener sockets differently, as they rely + * on underlying TLS sockets for networking. The TLS context + * will get passed to these underlying sockets via the call to + * isc__nm_http_set_tlsctx(). + */ + isc__nm_http_set_tlsctx(listener, tlsctx); + break; + case isc_nm_tlslistener: + set_tlsctx_workers(listener, tlsctx); + break; +#endif /* HAVE_LIBNGHTTP2 */ + case isc_nm_tlsdnslistener: + set_tlsctx_workers(listener, tlsctx); + break; + default: + UNREACHABLE(); + break; + }; +} + +const char * +isc_nm_verify_tls_peer_result_string(const isc_nmhandle_t *handle) { + isc_nmsocket_t *sock; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + + sock = handle->sock; + switch (sock->type) { + case isc_nm_tlsdnssocket: + return (isc__nm_tlsdns_verify_tls_peer_result_string(handle)); + break; +#if HAVE_LIBNGHTTP2 + case isc_nm_tlssocket: + return (isc__nm_tls_verify_tls_peer_result_string(handle)); + break; + case isc_nm_httpsocket: + return (isc__nm_http_verify_tls_peer_result_string(handle)); + break; +#endif /* HAVE_LIBNGHTTP2 */ + default: + break; + } + + return (NULL); +} + +void +isc_nmsocket_set_max_streams(isc_nmsocket_t *listener, + const uint32_t max_streams) { + REQUIRE(VALID_NMSOCK(listener)); + switch (listener->type) { +#if HAVE_LIBNGHTTP2 + case isc_nm_httplistener: + isc__nm_http_set_max_streams(listener, max_streams); + break; +#endif /* HAVE_LIBNGHTTP2 */ + default: + UNUSED(max_streams); + break; + }; + return; +} + +void +isc__nmsocket_log_tls_session_reuse(isc_nmsocket_t *sock, isc_tls_t *tls) { + const int log_level = ISC_LOG_DEBUG(1); + char client_sabuf[ISC_SOCKADDR_FORMATSIZE]; + char local_sabuf[ISC_SOCKADDR_FORMATSIZE]; + + REQUIRE(tls != NULL); + + if (!isc_log_wouldlog(isc_lctx, log_level)) { + return; + }; + + isc_sockaddr_format(&sock->peer, client_sabuf, sizeof(client_sabuf)); + isc_sockaddr_format(&sock->iface, local_sabuf, sizeof(local_sabuf)); + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, ISC_LOGMODULE_NETMGR, + log_level, "TLS %s session %s for %s on %s", + SSL_is_server(tls) ? "server" : "client", + SSL_session_reused(tls) ? "resumed" : "created", + client_sabuf, local_sabuf); +} + +#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"); + case isc_nm_tlssocket: + return ("isc_nm_tlssocket"); + case isc_nm_tlslistener: + return ("isc_nm_tlslistener"); + case isc_nm_tlsdnslistener: + return ("isc_nm_tlsdnslistener"); + case isc_nm_tlsdnssocket: + return ("isc_nm_tlsdnssocket"); + case isc_nm_httplistener: + return ("isc_nm_httplistener"); + case isc_nm_httpsocket: + return ("isc_nm_httpsocket"); + 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"); + isc_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" : "", + atomic_load(&sock->accepting) ? " accepting" : ""); + fprintf(stderr, "Created by:\n"); + isc_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..2a644fe --- /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(atomic_load(&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); + + atomic_store(&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, STATID_OPENFAIL); + goto done; + } + isc__nm_incstats(sock, 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, STATID_BINDFAIL); + goto done; + } + } + + isc__nm_set_network_buffers(sock->mgr, &sock->uv_handle.handle); + + 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, STATID_CONNECTFAIL); + goto done; + } + + 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_R_UNSET; + 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()); + + 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; + } else 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__nm_closing(sock)) { + /* Network manager shutting down */ + result = ISC_R_SHUTTINGDOWN; + goto error; + } else if (isc__nmsocket_closing(sock)) { + /* Connection canceled */ + 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 == UV_EADDRINUSE) { + /* + * On FreeBSD the TCP connect() call sometimes results in a + * spurious transient EADDRINUSE. Try a few more times before + * giving up. + */ + if (--req->connect_tries > 0) { + r = uv_tcp_connect(&req->uv_req.connect, + &sock->uv_handle.tcp, + &req->peer.type.sa, tcp_connect_cb); + if (r != 0) { + result = isc__nm_uverr2result(r); + goto error; + } + return; + } + result = isc__nm_uverr2result(status); + goto error; + } else if (status != 0) { + result = isc__nm_uverr2result(status); + goto error; + } + + isc__nmsocket_timer_stop(sock); + uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock); + + isc__nm_incstats(sock, 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; + } + + (void)isc__nm_socket_min_mtu(sock->fd, sa_family); + (void)isc__nm_socket_tcp_maxseg(sock->fd, NM_MAXSEG); + + 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); + (void)isc__nm_socket_v6only(sock, sa_family); + + /* FIXME: set mss */ + + result = isc__nm_socket_reuse(sock); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + if (mgr->load_balance_sockets) { + result = isc__nm_socket_reuse_lb(sock); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } + + 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); + + 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); + } + 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); + sock->nchildren = mgr->nworkers; + 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; + + if (!mgr->load_balance_sockets) { + fd = isc__nm_tcp_lb_socket(mgr, iface->type.sa.sa_family); + } + + 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()); + } + + if (!mgr->load_balance_sockets) { + isc__nm_closesocket(fd); + } + + 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()); + + (void)isc__nm_socket_min_mtu(sock->fd, sa_family); + (void)isc__nm_socket_tcp_maxseg(sock->fd, NM_MAXSEG); + + 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, STATID_OPENFAIL); + goto done; + } + isc__nm_incstats(sock, STATID_OPEN); + + if (sa_family == AF_INET6) { + flags = UV_TCP_IPV6ONLY; + } + + 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, 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, 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; + } + } + + isc__nm_set_network_buffers(sock->mgr, &sock->uv_handle.handle); + + /* + * 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, 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, 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); + + 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) { + atomic_store(&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)) { + atomic_store(&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(atomic_load(&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, 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 (atomic_load(&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; + atomic_init(&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; + } + + atomic_store(&csock->accepting, false); + + isc__nm_incstats(csock, 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, 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, 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, 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, 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 (atomic_load(&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) { + if (isc__nm_closing(sock)) { + isc__nm_failed_read_cb(sock, ISC_R_SHUTTINGDOWN, false); + } else { + 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_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..eda6aa6 --- /dev/null +++ b/lib/isc/netmgr/tcpdns.c @@ -0,0 +1,1500 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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_SHUTTINGDOWN; + goto error; + } + + r = uv_tcp_open(&sock->uv_handle.tcp, sock->fd); + if (r != 0) { + isc__nm_closesocket(sock->fd); + isc__nm_incstats(sock, STATID_OPENFAIL); + goto done; + } + isc__nm_incstats(sock, 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, STATID_BINDFAIL); + goto done; + } + } + + isc__nm_set_network_buffers(sock->mgr, &sock->uv_handle.handle); + + 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, STATID_CONNECTFAIL); + goto done; + } + + 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_R_UNSET; + 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()); + + 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; + } else if (isc__nm_closing(sock)) { + /* Network manager shutting down */ + result = ISC_R_SHUTTINGDOWN; + goto error; + } else if (isc__nmsocket_closing(sock)) { + /* Connection canceled */ + 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 == UV_EADDRINUSE) { + /* + * On FreeBSD the TCP connect() call sometimes results in a + * spurious transient EADDRINUSE. Try a few more times before + * giving up. + */ + if (--req->connect_tries > 0) { + r = uv_tcp_connect( + &req->uv_req.connect, &sock->uv_handle.tcp, + &req->peer.type.sa, tcpdns_connect_cb); + if (r != 0) { + result = isc__nm_uverr2result(r); + goto error; + } + return; + } + result = isc__nm_uverr2result(status); + goto error; + } else if (status != 0) { + result = isc__nm_uverr2result(status); + goto error; + } + + isc__nmsocket_timer_stop(sock); + uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock); + + isc__nm_incstats(sock, 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; + } + + (void)isc__nm_socket_min_mtu(sock->fd, sa_family); + (void)isc__nm_socket_tcp_maxseg(sock->fd, NM_MAXSEG); + + /* 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); + (void)isc__nm_socket_v6only(sock, sa_family); + + /* FIXME: set mss */ + + result = isc__nm_socket_reuse(sock); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + if (mgr->load_balance_sockets) { + result = isc__nm_socket_reuse_lb(sock); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } + + 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); + + 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); + } + 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); + sock->nchildren = mgr->nworkers; + 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; + + if (!mgr->load_balance_sockets) { + fd = isc__nm_tcpdns_lb_socket(mgr, iface->type.sa.sa_family); + } + + 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()); + } + + if (!mgr->load_balance_sockets) { + isc__nm_closesocket(fd); + } + + 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()); + + (void)isc__nm_socket_min_mtu(sock->fd, sa_family); + (void)isc__nm_socket_tcp_maxseg(sock->fd, NM_MAXSEG); + + 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, STATID_OPENFAIL); + goto done; + } + isc__nm_incstats(sock, STATID_OPEN); + + if (sa_family == AF_INET6) { + flags = UV_TCP_IPV6ONLY; + } + + 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, 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, 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; + } + } + + isc__nm_set_network_buffers(sock->mgr, &sock->uv_handle.handle); + + /* + * 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, 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, 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); + + 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) { + atomic_store(&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); + } + + if (sock->recv_cb == NULL) { + /* + * recv_cb has been cleared - there is + * nothing to do + */ + return (ISC_R_CANCELED); + } else if (sock->statichandle == NULL && + atomic_load(&sock->connected) && + !atomic_load(&sock->connecting)) + { + /* + * It seems that some unexpected data (a DNS message) has + * arrived while we are wrapping up. + */ + return (ISC_R_CANCELED); + } + + req = isc__nm_get_read_req(sock, NULL); + REQUIRE(VALID_UVREQ(req)); + + /* + * We need to launch isc__nm_resume_processing() after the buffer + * has been consumed, thus we must delay 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; + + /* + * An assertion failure here means that there's an erroneous + * extra nmhandle detach happening in the callback and + * isc__nm_resume_processing() is called while we're + * 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(atomic_load(&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, 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; + atomic_init(&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; + } + + atomic_store(&csock->accepting, false); + + isc__nm_incstats(csock, 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) { + isc__netievent_tcpdnssend_t *ievent = NULL; + isc__nm_uvreq_t *uvreq = NULL; + isc_nmsocket_t *sock = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + + sock = handle->sock; + + REQUIRE(VALID_NMSOCK(sock)); + 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, 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_result_t result; + isc__netievent_tcpdnssend_t *ievent = + (isc__netievent_tcpdnssend_t *)ev0; + isc_nmsocket_t *sock = NULL; + isc__nm_uvreq_t *uvreq = NULL; + int r, nbufs = 2; + + UNUSED(worker); + + REQUIRE(VALID_UVREQ(ievent->req)); + REQUIRE(VALID_NMSOCK(ievent->sock)); + REQUIRE(ievent->sock->type == isc_nm_tcpdnssocket); + REQUIRE(ievent->sock->tid == isc_nm_tid()); + + sock = ievent->sock; + 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 } }; + + 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: + isc__nm_incstats(sock, 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, 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, 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 (atomic_load(&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) { + if (isc__nm_closing(sock)) { + isc__nm_failed_read_cb(sock, ISC_R_SHUTTINGDOWN, false); + } else { + 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); +} diff --git a/lib/isc/netmgr/timer.c b/lib/isc/netmgr/timer.c new file mode 100644 index 0000000..8328775 --- /dev/null +++ b/lib/isc/netmgr/timer.c @@ -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. + */ + +#include <uv.h> + +#include <isc/netmgr.h> +#include <isc/util.h> + +#include "netmgr-int.h" + +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_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()]; + + /* TODO: per-loop object cache */ + 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); +} diff --git a/lib/isc/netmgr/tlsdns.c b/lib/isc/netmgr/tlsdns.c new file mode 100644 index 0000000..d30e33f --- /dev/null +++ b/lib/isc/netmgr/tlsdns.c @@ -0,0 +1,2363 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 "openssl_shim.h" +#include "uv-compat.h" + +static atomic_uint_fast32_t last_tlsdnsquota_log = 0; + +static void +tls_error(isc_nmsocket_t *sock, isc_result_t result); + +static isc_result_t +tlsdns_connect_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req); + +static void +tlsdns_close_direct(isc_nmsocket_t *sock); + +static isc_result_t +tlsdns_send_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req); +static void +tlsdns_connect_cb(uv_connect_t *uvreq, int status); + +static void +tlsdns_connection_cb(uv_stream_t *server, int status); + +static void +tlsdns_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_tlsdns_parent(isc_nmsocket_t *sock); +static void +stop_tlsdns_child(isc_nmsocket_t *sock); + +static void +async_tlsdns_cycle(isc_nmsocket_t *sock) __attribute__((unused)); + +static isc_result_t +tls_cycle(isc_nmsocket_t *sock); + +static void +call_pending_send_callbacks(isc_nmsocket_t *sock, const isc_result_t result); + +static void +tlsdns_keep_client_tls_session(isc_nmsocket_t *sock); + +static void +tlsdns_set_tls_shutdown(isc_tls_t *tls) { + (void)SSL_set_shutdown(tls, SSL_SENT_SHUTDOWN); +} + +static bool +peer_verification_has_failed(isc_nmsocket_t *sock) { + if (sock->tls.tls != NULL && sock->tls.state == TLS_STATE_HANDSHAKE && + SSL_get_verify_result(sock->tls.tls) != X509_V_OK) + { + return (true); + } + + return (false); +} + +static bool +can_log_tlsdns_quota(void) { + isc_stdtime_t now, last; + + isc_stdtime_get(&now); + last = atomic_exchange_relaxed(&last_tlsdnsquota_log, now); + if (now != last) { + return (true); + } + + return (false); +} + +static isc_result_t +tlsdns_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); + + if (isc__nm_closing(sock)) { + result = ISC_R_SHUTTINGDOWN; + goto error; + } + + r = uv_tcp_open(&sock->uv_handle.tcp, sock->fd); + if (r != 0) { + isc__nm_closesocket(sock->fd); + isc__nm_incstats(sock, STATID_OPENFAIL); + goto done; + } + isc__nm_incstats(sock, 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, STATID_BINDFAIL); + goto done; + } + } + + isc__nm_set_network_buffers(sock->mgr, &sock->uv_handle.handle); + + 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, tlsdns_connect_cb); + if (r != 0) { + isc__nm_incstats(sock, STATID_CONNECTFAIL); + goto done; + } + + 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_tlsdnsconnect(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_tlsdnsconnect_t *ievent = + (isc__netievent_tlsdnsconnect_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_tlsdnssocket); + REQUIRE(sock->parent == NULL); + REQUIRE(sock->tid == isc_nm_tid()); + + result = tlsdns_connect_direct(sock, req); + if (result != ISC_R_SUCCESS) { + atomic_compare_exchange_enforced(&sock->connecting, + &(bool){ true }, false); + isc__nmsocket_clearcb(sock); + isc__nm_connectcb(sock, req, result, true); + atomic_store(&sock->active, false); + isc__nm_tlsdns_close(sock); + } + + /* + * The sock is now attached to the handle. + */ + isc__nmsocket_detach(&sock); +} + +static void +tlsdns_connect_cb(uv_connect_t *uvreq, int status) { + isc_result_t result = ISC_R_UNSET; + 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()); + + 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; + } else if (isc__nm_closing(sock)) { + /* Network manager shutting down */ + result = ISC_R_SHUTTINGDOWN; + goto error; + } else if (isc__nmsocket_closing(sock)) { + /* Connection canceled */ + 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 == UV_EADDRINUSE) { + /* + * On FreeBSD the TCP connect() call sometimes results in a + * spurious transient EADDRINUSE. Try a few more times before + * giving up. + */ + if (--req->connect_tries > 0) { + r = uv_tcp_connect( + &req->uv_req.connect, &sock->uv_handle.tcp, + &req->peer.type.sa, tlsdns_connect_cb); + if (r != 0) { + result = isc__nm_uverr2result(r); + goto error; + } + return; + } + result = isc__nm_uverr2result(status); + goto error; + } else if (status != 0) { + result = isc__nm_uverr2result(status); + goto error; + } + + isc__nm_incstats(sock, 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; + } + + sock->tls.state = TLS_STATE_NONE; + sock->tls.tls = isc_tls_create(sock->tls.ctx); + RUNTIME_CHECK(sock->tls.tls != NULL); + + /* + * + */ + r = BIO_new_bio_pair(&sock->tls.ssl_wbio, ISC_NETMGR_TCP_RECVBUF_SIZE, + &sock->tls.app_rbio, ISC_NETMGR_TCP_RECVBUF_SIZE); + RUNTIME_CHECK(r == 1); + + r = BIO_new_bio_pair(&sock->tls.ssl_rbio, ISC_NETMGR_TCP_RECVBUF_SIZE, + &sock->tls.app_wbio, ISC_NETMGR_TCP_RECVBUF_SIZE); + RUNTIME_CHECK(r == 1); + +#if HAVE_SSL_SET0_RBIO && HAVE_SSL_SET0_WBIO + /* + * Note that if the rbio and wbio are the same then + * SSL_set0_rbio() and SSL_set0_wbio() each take ownership of + * one reference. Therefore it may be necessary to increment the + * number of references available using BIO_up_ref(3) before + * calling the set0 functions. + */ + SSL_set0_rbio(sock->tls.tls, sock->tls.ssl_rbio); + SSL_set0_wbio(sock->tls.tls, sock->tls.ssl_wbio); +#else + SSL_set_bio(sock->tls.tls, sock->tls.ssl_rbio, sock->tls.ssl_wbio); +#endif + + result = isc_sockaddr_fromsockaddr(&sock->peer, (struct sockaddr *)&ss); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + if (sock->tls.client_sess_cache != NULL) { + isc_tlsctx_client_session_cache_reuse_sockaddr( + sock->tls.client_sess_cache, &sock->peer, + sock->tls.tls); + } + + SSL_set_connect_state(sock->tls.tls); + + /* Setting pending req */ + sock->tls.pending_req = req; + + result = isc__nm_process_sock_buffer(sock); + if (result != ISC_R_SUCCESS) { + sock->tls.pending_req = NULL; + goto error; + } + + result = tls_cycle(sock); + if (result != ISC_R_SUCCESS) { + sock->tls.pending_req = NULL; + goto error; + } + + return; + +error: + isc__nm_failed_connect_cb(sock, req, result, false); +} + +void +isc_nm_tlsdnsconnect(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_tlsctx_t *sslctx, + isc_tlsctx_client_session_cache_t *client_sess_cache) { + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *sock = NULL; + isc__netievent_tlsdnsconnect_t *ievent = NULL; + isc__nm_uvreq_t *req = NULL; + sa_family_t sa_family; + + REQUIRE(VALID_NM(mgr)); + REQUIRE(local != NULL); + REQUIRE(peer != NULL); + REQUIRE(sslctx != NULL); + + sa_family = peer->type.sa.sa_family; + + sock = isc_mem_get(mgr->mctx, sizeof(*sock)); + isc__nmsocket_init(sock, mgr, isc_nm_tlsdnssocket, local); + + sock->extrahandlesize = extrahandlesize; + sock->connect_timeout = timeout; + sock->result = ISC_R_UNSET; + isc_tlsctx_attach(sslctx, &sock->tls.ctx); + atomic_init(&sock->client, true); + atomic_init(&sock->connecting, 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); + + if (client_sess_cache != NULL) { + INSIST(isc_tlsctx_client_session_cache_getctx( + client_sess_cache) == sslctx); + isc_tlsctx_client_session_cache_attach( + client_sess_cache, &sock->tls.client_sess_cache); + } + + result = isc__nm_socket(sa_family, SOCK_STREAM, 0, &sock->fd); + if (result != ISC_R_SUCCESS) { + goto failure; + } + + if (isc__nm_closing(sock)) { + result = ISC_R_SHUTTINGDOWN; + goto failure; + } + + (void)isc__nm_socket_min_mtu(sock->fd, sa_family); + (void)isc__nm_socket_tcp_maxseg(sock->fd, NM_MAXSEG); + + /* 2 minute timeout */ + result = isc__nm_socket_connectiontimeout(sock->fd, 120 * 1000); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + ievent = isc__nm_get_netievent_tlsdnsconnect(mgr, sock, req); + + if (isc__nm_in_netthread()) { + atomic_store(&sock->active, true); + sock->tid = isc_nm_tid(); + isc__nm_async_tlsdnsconnect(&mgr->workers[sock->tid], + (isc__netievent_t *)ievent); + isc__nm_put_netievent_tlsdnsconnect(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); + return; + +failure: + if (isc__nm_in_netthread()) { + sock->tid = isc_nm_tid(); + } + + atomic_compare_exchange_enforced(&sock->connecting, &(bool){ true }, + false); + isc__nmsocket_clearcb(sock); + isc__nm_connectcb(sock, req, result, true); + atomic_store(&sock->closed, true); + isc__nmsocket_detach(&sock); +} + +static uv_os_sock_t +isc__nm_tlsdns_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); + (void)isc__nm_socket_v6only(sock, sa_family); + + /* FIXME: set mss */ + + result = isc__nm_socket_reuse(sock); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + if (mgr->load_balance_sockets) { + result = isc__nm_socket_reuse_lb(sock); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } + + return (sock); +} + +static void +start_tlsdns_child(isc_nm_t *mgr, isc_sockaddr_t *iface, isc_nmsocket_t *sock, + uv_os_sock_t fd, int tid) { + isc__netievent_tlsdnslisten_t *ievent = NULL; + isc_nmsocket_t *csock = &sock->children[tid]; + + isc__nmsocket_init(csock, mgr, isc_nm_tlsdnssocket, 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; + isc_tlsctx_attach(sock->tls.ctx, &csock->tls.ctx); + + /* + * 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); + + if (mgr->load_balance_sockets) { + UNUSED(fd); + csock->fd = isc__nm_tlsdns_lb_socket(mgr, + iface->type.sa.sa_family); + } else { + csock->fd = dup(fd); + } + REQUIRE(csock->fd >= 0); + + ievent = isc__nm_get_netievent_tlsdnslisten(mgr, csock); + isc__nm_maybe_enqueue_ievent(&mgr->workers[tid], + (isc__netievent_t *)ievent); +} + +static void +enqueue_stoplistening(isc_nmsocket_t *sock) { + isc__netievent_tlsdnsstop_t *ievent = + isc__nm_get_netievent_tlsdnsstop(sock->mgr, sock); + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); +} + +isc_result_t +isc_nm_listentlsdns(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_tlsctx_t *sslctx, 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_tlsdnslistener, iface); + + atomic_init(&sock->rchildren, 0); + sock->nchildren = mgr->nworkers; + 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; + + isc_tlsctx_attach(sslctx, &sock->tls.ctx); + + sock->tid = 0; + sock->fd = -1; + + if (!mgr->load_balance_sockets) { + fd = isc__nm_tlsdns_lb_socket(mgr, iface->type.sa.sa_family); + } + + isc_barrier_init(&sock->startlistening, sock->nchildren); + + for (size_t i = 0; i < sock->nchildren; i++) { + if ((int)i == isc_nm_tid()) { + continue; + } + start_tlsdns_child(mgr, iface, sock, fd, i); + } + + if (isc__nm_in_netthread()) { + start_tlsdns_child(mgr, iface, sock, fd, isc_nm_tid()); + } + + if (!mgr->load_balance_sockets) { + isc__nm_closesocket(fd); + } + + 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_tlsdnslisten(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_tlsdnslisten_t *ievent = + (isc__netievent_tlsdnslisten_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; + + 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_tlsdnssocket); + REQUIRE(sock->parent != NULL); + REQUIRE(sock->tid == isc_nm_tid()); + + (void)isc__nm_socket_min_mtu(sock->fd, sa_family); + (void)isc__nm_socket_tcp_maxseg(sock->fd, NM_MAXSEG); + + 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, STATID_OPENFAIL); + goto done; + } + isc__nm_incstats(sock, STATID_OPEN); + + if (sa_family == AF_INET6) { + flags = UV_TCP_IPV6ONLY; + } + + 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, 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, 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; + } + } + + isc__nm_set_network_buffers(sock->mgr, &sock->uv_handle.handle); + + /* + * The callback will run in the same thread uv_listen() was + * called from, so a race with tlsdns_connection_cb() isn't + * possible. + */ + r = uv_listen((uv_stream_t *)&sock->uv_handle.tcp, sock->backlog, + tlsdns_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, 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 +tlsdns_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, STATID_ACCEPTFAIL); + goto done; + } + } + + result = accept_connection(ssock, quota); +done: + isc__nm_accept_connection_log(result, can_log_tlsdns_quota()); +} + +void +isc__nm_tlsdns_stoplistening(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->type == isc_nm_tlsdnslistener); + + if (!atomic_compare_exchange_strong(&sock->closing, &(bool){ false }, + true)) + { + UNREACHABLE(); + } + + if (!isc__nm_in_netthread()) { + enqueue_stoplistening(sock); + } else { + stop_tlsdns_parent(sock); + } +} + +static void +tls_shutdown(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + + isc__netievent_tlsdnsshutdown_t *ievent = + isc__nm_get_netievent_tlsdnsshutdown(sock->mgr, sock); + isc__nm_maybe_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); +} + +void +isc__nm_async_tlsdnsshutdown(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_tlsdnsshutdown_t *ievent = + (isc__netievent_tlsdnsshutdown_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + int rv; + int err; + isc_result_t result; + + UNUSED(worker); + + REQUIRE(VALID_NMSOCK(ievent->sock)); + + if (sock->tls.state != TLS_STATE_IO) { + /* Nothing to do */ + return; + } + + rv = SSL_shutdown(sock->tls.tls); + + if (rv == 1) { + sock->tls.state = TLS_STATE_NONE; + /* FIXME: continue closing the socket */ + return; + } + + if (rv == 0) { + result = tls_cycle(sock); + if (result != ISC_R_SUCCESS) { + tls_error(sock, result); + return; + } + + /* Reschedule closing the socket */ + tls_shutdown(sock); + return; + } + + err = SSL_get_error(sock->tls.tls, rv); + + switch (err) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + case SSL_ERROR_WANT_X509_LOOKUP: + result = tls_cycle(sock); + if (result != ISC_R_SUCCESS) { + tls_error(sock, result); + return; + } + + /* Reschedule closing the socket */ + tls_shutdown(sock); + return; + case 0: + UNREACHABLE(); + case SSL_ERROR_ZERO_RETURN: + tls_error(sock, ISC_R_EOF); + break; + default: + tls_error(sock, ISC_R_TLSERROR); + } + return; +} + +void +isc__nm_async_tlsdnsstop(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_tlsdnsstop_t *ievent = + (isc__netievent_tlsdnsstop_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_tlsdns_child(sock); + return; + } + + stop_tlsdns_parent(sock); +} + +void +isc__nm_tlsdns_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result, + bool async) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(result != ISC_R_SUCCESS); + + isc__nmsocket_timer_stop(sock); + isc__nm_stop_reading(sock); + + if (sock->tls.pending_req != NULL) { + isc_result_t failure_result = ISC_R_CANCELED; + isc__nm_uvreq_t *req = sock->tls.pending_req; + sock->tls.pending_req = NULL; + + if (peer_verification_has_failed(sock)) { + /* + * Save error message as 'sock->tls' will get detached. + */ + sock->tls.tls_verify_errmsg = + isc_tls_verify_peer_result_string( + sock->tls.tls); + failure_result = ISC_R_TLSBADPEERCERT; + } + isc__nm_failed_connect_cb(sock, req, failure_result, async); + } + + 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: + call_pending_send_callbacks(sock, result); + 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_tlsdns_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_tlsdnsread_t *ievent = NULL; + + REQUIRE(sock->type == isc_nm_tlsdnssocket); + REQUIRE(sock->statichandle == handle); + + 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_tlsdnsread(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_tlsdnsread(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_tlsdnsread_t *ievent = + (isc__netievent_tlsdnsread_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + isc_result_t result = ISC_R_SUCCESS; + + UNUSED(worker); + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + + if (isc__nmsocket_closing(sock)) { + atomic_store(&sock->reading, true); + isc__nm_failed_read_cb(sock, ISC_R_CANCELED, false); + return; + } + + result = tls_cycle(sock); + if (result != ISC_R_SUCCESS) { + 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_tlsdns_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); + } + + if (sock->recv_cb == NULL) { + /* + * recv_cb has been cleared - there is + * nothing to do + */ + return (ISC_R_CANCELED); + } else if (sock->statichandle == NULL && + sock->tls.state == TLS_STATE_IO && + atomic_load(&sock->connected) && + !atomic_load(&sock->connecting)) + { + /* + * It seems that some unexpected data (a DNS message) has + * arrived while we are wrapping up. + */ + return (ISC_R_CANCELED); + } + + req = isc__nm_get_read_req(sock, NULL); + REQUIRE(VALID_UVREQ(req)); + + /* + * We need to launch isc__nm_resume_processing() after the buffer + * has been consumed, thus we must delay 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_tlsdns_read() was called, it will be satisfied by + * single DNS message in the next call. + */ + sock->recv_read = false; + + /* + * An assertion failure here means that there's an erroneous + * extra nmhandle detach happening in the callback and + * isc__nm_resume_processing() is called while we're + * 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); + + if (isc__nmsocket_closing(sock)) { + tlsdns_set_tls_shutdown(sock->tls.tls); + tlsdns_keep_client_tls_session(sock); + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +tls_cycle_input(isc_nmsocket_t *sock) { + isc_result_t result = ISC_R_SUCCESS; + int err = 0; + int rv = 1; + + if (sock->tls.state == TLS_STATE_IO) { + size_t len; + + for (;;) { + (void)SSL_peek(sock->tls.tls, &(char){ '\0' }, 0); + + int pending = SSL_pending(sock->tls.tls); + if (pending > (int)ISC_NETMGR_TCP_RECVBUF_SIZE) { + pending = (int)ISC_NETMGR_TCP_RECVBUF_SIZE; + } + + if (pending != 0) { + if ((sock->buf_len + pending) > sock->buf_size) + { + isc__nm_alloc_dnsbuf( + sock, sock->buf_len + pending); + } + + len = 0; + rv = SSL_read_ex(sock->tls.tls, + sock->buf + sock->buf_len, + sock->buf_size - sock->buf_len, + &len); + if (rv != 1) { + /* + * Process what's in the buffer so far + */ + result = isc__nm_process_sock_buffer( + sock); + if (result != ISC_R_SUCCESS) { + goto failure; + } + /* + * FIXME: Should we call + * isc__nm_failed_read_cb()? + */ + break; + } + + INSIST((size_t)pending == len); + + sock->buf_len += len; + } + result = isc__nm_process_sock_buffer(sock); + if (result != ISC_R_SUCCESS) { + goto failure; + } + + if (pending == 0) { + break; + } + } + } else if (!SSL_is_init_finished(sock->tls.tls)) { + if (SSL_is_server(sock->tls.tls)) { + rv = SSL_accept(sock->tls.tls); + } else { + rv = SSL_connect(sock->tls.tls); + } + + } else { + rv = 1; + } + + if (rv <= 0) { + err = SSL_get_error(sock->tls.tls, rv); + } + + switch (err) { + case SSL_ERROR_WANT_READ: + if (sock->tls.state == TLS_STATE_NONE && + !SSL_is_init_finished(sock->tls.tls)) + { + sock->tls.state = TLS_STATE_HANDSHAKE; + result = isc__nm_process_sock_buffer(sock); + if (result != ISC_R_SUCCESS) { + goto failure; + } + } + /* else continue reading */ + break; + case SSL_ERROR_WANT_WRITE: + async_tlsdns_cycle(sock); + break; + case SSL_ERROR_WANT_X509_LOOKUP: + /* Continue reading/writing */ + break; + case 0: + /* Everything is ok, continue */ + break; + case SSL_ERROR_ZERO_RETURN: + return (ISC_R_EOF); + default: + return (ISC_R_TLSERROR); + } + + /* Stop state after handshake */ + if (sock->tls.state == TLS_STATE_HANDSHAKE && + SSL_is_init_finished(sock->tls.tls)) + { + const unsigned char *alpn = NULL; + unsigned int alpnlen = 0; + + isc__nmsocket_log_tls_session_reuse(sock, sock->tls.tls); + + isc_tls_get_selected_alpn(sock->tls.tls, &alpn, &alpnlen); + if (alpn != NULL && alpnlen == ISC_TLS_DOT_PROTO_ALPN_ID_LEN && + memcmp(ISC_TLS_DOT_PROTO_ALPN_ID, alpn, + ISC_TLS_DOT_PROTO_ALPN_ID_LEN) == 0) + { + sock->tls.alpn_negotiated = true; + } + + sock->tls.state = TLS_STATE_IO; + + if (SSL_is_server(sock->tls.tls)) { + REQUIRE(sock->recv_handle != NULL); + result = sock->accept_cb(sock->recv_handle, + ISC_R_SUCCESS, + sock->accept_cbarg); + + if (result != ISC_R_SUCCESS) { + isc_nmhandle_detach(&sock->recv_handle); + goto failure; + } + } else { + isc__nm_uvreq_t *req = sock->tls.pending_req; + sock->tls.pending_req = NULL; + + isc__nmsocket_timer_stop(sock); + uv_handle_set_data((uv_handle_t *)&sock->read_timer, + sock); + + atomic_compare_exchange_enforced( + &sock->connecting, &(bool){ true }, false); + isc__nm_connectcb(sock, req, ISC_R_SUCCESS, true); + } + async_tlsdns_cycle(sock); + } +failure: + return (result); +} + +static void +tls_error(isc_nmsocket_t *sock, isc_result_t result) { + switch (sock->tls.state) { + case TLS_STATE_HANDSHAKE: + case TLS_STATE_IO: + if (atomic_load(&sock->connecting)) { + isc__nm_uvreq_t *req = sock->tls.pending_req; + sock->tls.pending_req = NULL; + + isc__nm_failed_connect_cb(sock, req, result, false); + } else { + isc__nm_tlsdns_failed_read_cb(sock, result, false); + } + break; + case TLS_STATE_ERROR: + return; + default: + break; + } + + sock->tls.state = TLS_STATE_ERROR; + sock->tls.pending_error = result; + + isc__nmsocket_shutdown(sock); +} + +static void +call_pending_send_callbacks(isc_nmsocket_t *sock, const isc_result_t result) { + isc__nm_uvreq_t *cbreq = ISC_LIST_HEAD(sock->tls.sendreqs); + while (cbreq != NULL) { + isc__nm_uvreq_t *next = ISC_LIST_NEXT(cbreq, link); + ISC_LIST_UNLINK(sock->tls.sendreqs, cbreq, link); + INSIST(sock == cbreq->handle->sock); + isc__nm_sendcb(sock, cbreq, result, false); + cbreq = next; + } +} + +static void +free_senddata(isc_nmsocket_t *sock, const isc_result_t result) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tls.senddata.base != NULL); + REQUIRE(sock->tls.senddata.length > 0); + + isc_mem_put(sock->mgr->mctx, sock->tls.senddata.base, + sock->tls.senddata.length); + sock->tls.senddata.base = NULL; + sock->tls.senddata.length = 0; + + call_pending_send_callbacks(sock, result); +} + +static void +tls_write_cb(uv_write_t *req, int status) { + isc_result_t result = status != 0 ? isc__nm_uverr2result(status) + : ISC_R_SUCCESS; + isc__nm_uvreq_t *uvreq = (isc__nm_uvreq_t *)req->data; + isc_nmsocket_t *sock = uvreq->sock; + + isc_nm_timer_stop(uvreq->timer); + isc_nm_timer_detach(&uvreq->timer); + + free_senddata(sock, result); + + isc__nm_uvreq_put(&uvreq, sock); + + if (status != 0) { + tls_error(sock, result); + return; + } + + result = tls_cycle(sock); + if (result != ISC_R_SUCCESS) { + tls_error(sock, result); + return; + } +} + +static isc_result_t +tls_cycle_output(isc_nmsocket_t *sock) { + isc_result_t result = ISC_R_SUCCESS; + int pending; + + while ((pending = BIO_pending(sock->tls.app_rbio)) > 0) { + isc__nm_uvreq_t *req = NULL; + size_t bytes; + int rv; + int r; + + if (sock->tls.senddata.base != NULL || + sock->tls.senddata.length > 0) + { + break; + } + + if (pending > (int)ISC_NETMGR_TCP_RECVBUF_SIZE) { + pending = (int)ISC_NETMGR_TCP_RECVBUF_SIZE; + } + + sock->tls.senddata.base = isc_mem_get(sock->mgr->mctx, pending); + sock->tls.senddata.length = pending; + + /* It's a bit misnomer here, but it does the right thing */ + req = isc__nm_get_read_req(sock, NULL); + req->uvbuf.base = (char *)sock->tls.senddata.base; + req->uvbuf.len = sock->tls.senddata.length; + + rv = BIO_read_ex(sock->tls.app_rbio, req->uvbuf.base, + req->uvbuf.len, &bytes); + + RUNTIME_CHECK(rv == 1); + INSIST((size_t)pending == bytes); + + r = uv_try_write(&sock->uv_handle.stream, &req->uvbuf, 1); + + if (r == pending) { + /* Wrote everything, restart */ + isc__nm_uvreq_put(&req, sock); + free_senddata(sock, ISC_R_SUCCESS); + continue; + } + + if (r > 0) { + /* Partial write, send rest asynchronously */ + memmove(req->uvbuf.base, req->uvbuf.base + r, + req->uvbuf.len - r); + req->uvbuf.len = req->uvbuf.len - r; + } else if (r == UV_ENOSYS || r == UV_EAGAIN) { + /* uv_try_write is not supported, send + * asynchronously */ + } else { + result = isc__nm_uverr2result(r); + isc__nm_uvreq_put(&req, sock); + free_senddata(sock, result); + break; + } + + r = uv_write(&req->uv_req.write, &sock->uv_handle.stream, + &req->uvbuf, 1, tls_write_cb); + if (r < 0) { + result = isc__nm_uverr2result(r); + isc__nm_uvreq_put(&req, sock); + free_senddata(sock, result); + break; + } + + 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); + } + + break; + } + + return (result); +} + +static isc_result_t +tls_pop_error(isc_nmsocket_t *sock) { + isc_result_t result; + + if (sock->tls.state != TLS_STATE_ERROR) { + return (ISC_R_SUCCESS); + } + + if (sock->tls.pending_error == ISC_R_SUCCESS) { + return (ISC_R_TLSERROR); + } + + result = sock->tls.pending_error; + sock->tls.pending_error = ISC_R_SUCCESS; + + return (result); +} + +static isc_result_t +tls_cycle(isc_nmsocket_t *sock) { + isc_result_t result; + + /* + * Clear the TLS error queue so that SSL_get_error() and SSL I/O + * routine calls will not get affected by prior error statuses. + * + * See here: + * https://www.openssl.org/docs/man3.0/man3/SSL_get_error.html + * + * In particular, it mentions the following: + * + * The current thread's error queue must be empty before the + * TLS/SSL I/O operation is attempted, or SSL_get_error() will not + * work reliably. + * + * As we use the result of SSL_get_error() to decide on I/O + * operations, we need to ensure that it works reliably by + * cleaning the error queue. + * + * The sum of details: https://stackoverflow.com/a/37980911 + */ + ERR_clear_error(); + + if (isc__nmsocket_closing(sock)) { + return (ISC_R_CANCELED); + } + + result = tls_pop_error(sock); + if (result != ISC_R_SUCCESS) { + goto done; + } + + if (sock->tls.cycle) { + return (ISC_R_SUCCESS); + } + + sock->tls.cycle = true; + result = tls_cycle_input(sock); + if (result != ISC_R_SUCCESS) { + goto done; + } + + result = tls_cycle_output(sock); + if (result != ISC_R_SUCCESS) { + goto done; + } +done: + sock->tls.cycle = false; + + return (result); +} + +static void +async_tlsdns_cycle(isc_nmsocket_t *sock) { + isc__netievent_tlsdnscycle_t *ievent = NULL; + + REQUIRE(VALID_NMSOCK(sock)); + + /* Socket was closed midflight by isc__nm_tlsdns_shutdown() */ + if (isc__nmsocket_closing(sock)) { + return; + } + + ievent = isc__nm_get_netievent_tlsdnscycle(sock->mgr, sock); + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); +} + +void +isc__nm_async_tlsdnscycle(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_tlsdnscycle_t *ievent = + (isc__netievent_tlsdnscycle_t *)ev0; + isc_result_t result; + isc_nmsocket_t *sock; + + UNUSED(worker); + + REQUIRE(VALID_NMSOCK(ievent->sock)); + REQUIRE(ievent->sock->tid == isc_nm_tid()); + + sock = ievent->sock; + + result = tls_cycle(sock); + if (result != ISC_R_SUCCESS) { + tls_error(sock, result); + } +} + +void +isc__nm_tlsdns_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); + size_t len; + isc_result_t result; + int rv; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + REQUIRE(atomic_load(&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, STATID_RECVFAIL); + } + + isc__nm_failed_read_cb(sock, isc__nm_uverr2result(nread), true); + + goto free; + } + + if (!atomic_load(&sock->client)) { + sock->read_timeout = atomic_load(&sock->mgr->idle); + } + + /* + * The input has to be fed into BIO + */ + rv = BIO_write_ex(sock->tls.app_wbio, buf->base, (size_t)nread, &len); + + if (rv <= 0 || (size_t)nread != len) { + isc__nm_failed_read_cb(sock, ISC_R_TLSERROR, true); + goto free; + } + + result = tls_cycle(sock); + if (result != ISC_R_SUCCESS) { + isc__nm_failed_read_cb(sock, result, true); + } +free: + async_tlsdns_cycle(sock); + + 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 tlsdnsaccept event and pass it using the async + * channel. + */ + + isc__netievent_tlsdnsaccept_t *ievent = + isc__nm_get_netievent_tlsdnsaccept(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_tlsdnsaccept(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_tlsdnsaccept_t *ievent = + (isc__netievent_tlsdnsaccept_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_tlsdns_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; + + 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_tlsdnssocket, + &ssock->iface); + csock->tid = ssock->tid; + csock->extrahandlesize = ssock->extrahandlesize; + isc__nmsocket_attach(ssock, &csock->server); + csock->accept_cb = ssock->accept_cb; + csock->accept_cbarg = ssock->accept_cbarg; + csock->recv_cb = ssock->recv_cb; + csock->recv_cbarg = ssock->recv_cbarg; + csock->quota = quota; + atomic_init(&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; + } + + csock->tls.state = TLS_STATE_NONE; + + csock->tls.tls = isc_tls_create(ssock->tls.ctx); + RUNTIME_CHECK(csock->tls.tls != NULL); + + r = BIO_new_bio_pair(&csock->tls.ssl_wbio, ISC_NETMGR_TCP_RECVBUF_SIZE, + &csock->tls.app_rbio, ISC_NETMGR_TCP_RECVBUF_SIZE); + RUNTIME_CHECK(r == 1); + + r = BIO_new_bio_pair(&csock->tls.ssl_rbio, ISC_NETMGR_TCP_RECVBUF_SIZE, + &csock->tls.app_wbio, ISC_NETMGR_TCP_RECVBUF_SIZE); + RUNTIME_CHECK(r == 1); + +#if HAVE_SSL_SET0_RBIO && HAVE_SSL_SET0_WBIO + /* + * Note that if the rbio and wbio are the same then + * SSL_set0_rbio() and SSL_set0_wbio() each take ownership of + * one reference. Therefore it may be necessary to increment the + * number of references available using BIO_up_ref(3) before + * calling the set0 functions. + */ + SSL_set0_rbio(csock->tls.tls, csock->tls.ssl_rbio); + SSL_set0_wbio(csock->tls.tls, csock->tls.ssl_wbio); +#else + SSL_set_bio(csock->tls.tls, csock->tls.ssl_rbio, csock->tls.ssl_wbio); +#endif + + SSL_set_accept_state(csock->tls.tls); + + /* FIXME: Set SSL_MODE_RELEASE_BUFFERS */ + + atomic_store(&csock->accepting, false); + + isc__nm_incstats(csock, 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()->tlsdns_close_direct(). + * + * The handle will be either detached on acceptcb failure or in + * the readcb. + */ + csock->recv_handle = isc__nmhandle_get(csock, NULL, &local); + + /* + * 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)); + + result = isc__nm_process_sock_buffer(csock); + if (result != ISC_R_SUCCESS) { + goto failure; + } + + /* + * 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_tlsdns_send(isc_nmhandle_t *handle, isc_region_t *region, + isc_nm_cb_t cb, void *cbarg) { + isc__netievent_tlsdnssend_t *ievent = NULL; + isc__nm_uvreq_t *uvreq = NULL; + isc_nmsocket_t *sock = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + + sock = handle->sock; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->type == isc_nm_tlsdnssocket); + + 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_tlsdnssend(sock->mgr, sock, uvreq); + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); + return; +} + +/* + * Handle 'tcpsend' async event - send a packet on the socket + */ +void +isc__nm_async_tlsdnssend(isc__networker_t *worker, isc__netievent_t *ev0) { + isc_result_t result; + isc__netievent_tlsdnssend_t *ievent = + (isc__netievent_tlsdnssend_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + isc__nm_uvreq_t *uvreq = ievent->req; + + UNUSED(worker); + + REQUIRE(sock->type == isc_nm_tlsdnssocket); + REQUIRE(sock->tid == isc_nm_tid()); + + if (sock->write_timeout == 0) { + sock->write_timeout = + (atomic_load(&sock->keepalive) + ? atomic_load(&sock->mgr->keepalive) + : atomic_load(&sock->mgr->idle)); + } + + result = tlsdns_send_direct(sock, uvreq); + if (result != ISC_R_SUCCESS) { + isc__nm_incstats(sock, STATID_SENDFAIL); + isc__nm_failed_send_cb(sock, uvreq, result); + } +} + +static void +tlsdns_send_enqueue(isc_nmsocket_t *sock, isc__nm_uvreq_t *req) { + isc__netievent_tlsdnssend_t *ievent = + isc__nm_get_netievent_tlsdnssend(sock->mgr, sock, req); + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); +} + +static isc_result_t +tlsdns_send_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req) { + isc_result_t result; + int err = 0; + int rv; + size_t bytes = 0; + size_t sendlen; + isc__networker_t *worker = NULL; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(VALID_UVREQ(req)); + REQUIRE(sock->tid == isc_nm_tid()); + REQUIRE(sock->type == isc_nm_tlsdnssocket); + + result = tls_pop_error(sock); + if (result != ISC_R_SUCCESS) { + return (result); + } + + if (isc__nmsocket_closing(sock)) { + return (ISC_R_CANCELED); + } + + /* Writes won't succeed until handshake end */ + if (!SSL_is_init_finished(sock->tls.tls)) { + goto requeue; + } + + /* + * Try to send any pending data before trying to call SSL_write_ex(). + * Otherwise, it could fail with SSL_ERROR_WANT_WRITE error. + * + * It is important to stress that we want to avoid this happening + * due to how SSL_write_ex() works - mainly to avoid partial + * writes. + * + * Although the documentation for these functions is vague, it is + * not stated that partial writes are not possible. On the + * contrary, one can deduce that it is possible and recovering + * from this situation is complicated and unreasonably hard to + * implement to the point when it is better to avoid this + * situation altogether. + * + * In particular, the following can be found in the documentation: + * + * "The write functions will only return with success when the + * complete contents of buf of length num has been written. This + * default behaviour can be changed with the + * SSL_MODE_ENABLE_PARTIAL_WRITE option of + * SSL_CTX_set_mode(3). When this flag is set, the write functions + * will also return with success when a partial write has been + * successfully completed. In this case, the write function + * operation is considered completed. The bytes are sent, and a + * new write call with a new buffer (with the already sent bytes + * removed) must be started. A partial write is performed with the + * size of a message block, which is 16kB." + + * That is, it is said that success is returned only when the + * complete chunk of data is written (encrypted), but it does not + * mention that partial writes are not possible (the behaviour can + * be changed using SSL_MODE_ENABLE_PARTIAL_WRITE). Another + * important aspect of this passage is that a partial write of up + * to 16 kilobytes can happen, and the call still can + * fail. Needless to say, this amount of data may include more + * than one DNS message. + * + * One could expect that SSL_write_ex() should return the number + * of bytes written, but no, that is not guaranteed (emphasis is + * mine): "*On success* SSL_write_ex() will store the number of + * bytes written in *written." + * + * Moreover, we can find the following guidance on how to handle + * the SSL_ERROR_WANT_WRITE error in the "Warnings" section of the + * documentation: + + * "When a write function call has to be repeated because + * SSL_get_error(3) returned SSL_ERROR_WANT_READ or + * SSL_ERROR_WANT_WRITE, it must be repeated with the same + * arguments. The data that was passed might have been partially + * processed. When SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER was set + * using SSL_CTX_set_mode(3) the pointer can be different, but the + * data and length should still be the same." + * + * That is, when a call to SSL_write_ex() fails with + * SSL_ERROR_WANT_WRITE, we must attempt to make the next call to + * the function exactly with the same arguments. Of course, the + * code is structured in such a way that we cannot guarantee that + * (and keeping track of that would be unreasonably complicated to + * implement). The best we can do to avoid this error is to get + * (and send) the outgoing data from the SSL buffer ASAP before + * processing the subsequent write request. We can achieve that by + * calling tls_cycle() and rescheduling the write request for + * being processed later. + */ + if (BIO_pending(sock->tls.app_rbio) > 0) { + /* Handle any pending data and requeue the write request. */ + goto cycle; + } + + /* + * There's no SSL_writev(), so we need to use a local buffer to + * assemble the whole message + */ + worker = &sock->mgr->workers[sock->tid]; + sendlen = req->uvbuf.len + sizeof(uint16_t); + memmove(worker->sendbuf, req->tcplen, sizeof(uint16_t)); + memmove(worker->sendbuf + sizeof(uint16_t), req->uvbuf.base, + req->uvbuf.len); + + rv = SSL_write_ex(sock->tls.tls, worker->sendbuf, sendlen, &bytes); + if (rv > 0) { + INSIST(sendlen == bytes); + + ISC_LIST_APPEND(sock->tls.sendreqs, req, link); + async_tlsdns_cycle(sock); + return (ISC_R_SUCCESS); + } + + /* Nothing was written, maybe enqueue? */ + err = SSL_get_error(sock->tls.tls, rv); + + switch (err) { + case SSL_ERROR_WANT_WRITE: + case SSL_ERROR_WANT_READ: + break; + case 0: + UNREACHABLE(); + default: + return (ISC_R_TLSERROR); + } + +cycle: + result = tls_cycle(sock); + if (result != ISC_R_SUCCESS) { + return (result); + } + +requeue: + tlsdns_send_enqueue(sock, req); + + return (result); +} + +static void +tlsdns_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, STATID_CLOSE); + + atomic_store(&sock->listening, false); + + BIO_free_all(sock->tls.app_rbio); + BIO_free_all(sock->tls.app_wbio); + + if (sock->tls.ctx != NULL) { + isc_tlsctx_free(&sock->tls.ctx); + } + + isc__nmsocket_detach(&sock); +} + +static void +tlsdns_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, STATID_CLOSE); + + if (sock->server != NULL) { + isc__nmsocket_detach(&sock->server); + } + + atomic_store(&sock->connected, false); + + if (sock->tls.tls != NULL) { + /* + * Let's shutdown the TLS session properly so that the session + * will remain resumable, if required. + */ + tlsdns_set_tls_shutdown(sock->tls.tls); + tlsdns_keep_client_tls_session(sock); + isc_tls_free(&sock->tls.tls); + } + + BIO_free_all(sock->tls.app_rbio); + BIO_free_all(sock->tls.app_wbio); + + if (sock->tls.ctx != NULL) { + isc_tlsctx_free(&sock->tls.ctx); + } + + isc__nmsocket_prep_destroy(sock); +} + +static void +tlsdns_close_cb(uv_handle_t *handle) { + isc_nmsocket_t *sock = uv_handle_get_data(handle); + uv_handle_set_data(handle, NULL); + + tlsdns_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); + + REQUIRE(VALID_NMSOCK(sock)); + + if (sock->parent) { + uv_close(&sock->uv_handle.handle, tlsdns_stop_cb); + } else if (uv_is_closing(&sock->uv_handle.handle)) { + tlsdns_close_sock(sock); + } else { + uv_close(&sock->uv_handle.handle, tlsdns_close_cb); + } +} + +static void +stop_tlsdns_child(isc_nmsocket_t *sock) { + REQUIRE(sock->type == isc_nm_tlsdnssocket); + REQUIRE(sock->tid == isc_nm_tid()); + + if (!atomic_compare_exchange_strong(&sock->closing, &(bool){ false }, + true)) + { + return; + } + + tlsdns_close_direct(sock); + + atomic_fetch_sub(&sock->parent->rchildren, 1); + + isc_barrier_wait(&sock->parent->stoplistening); +} + +static void +stop_tlsdns_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_tlsdnslistener); + + 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_tlsdns_child(csock); + + atomic_store(&sock->closed, true); + isc__nmsocket_prep_destroy(sock); +} + +static void +tlsdns_close_direct(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + REQUIRE(atomic_load(&sock->closing)); + + REQUIRE(sock->tls.pending_req == NULL); + + 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_tlsdns_close(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->type == isc_nm_tlsdnssocket); + REQUIRE(!isc__nmsocket_active(sock)); + + if (!atomic_compare_exchange_strong(&sock->closing, &(bool){ false }, + true)) + { + return; + } + + if (sock->tid == isc_nm_tid()) { + tlsdns_close_direct(sock); + } else { + /* + * We need to create an event and pass it using async + * channel + */ + isc__netievent_tlsdnsclose_t *ievent = + isc__nm_get_netievent_tlsdnsclose(sock->mgr, sock); + + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); + } +} + +void +isc__nm_async_tlsdnsclose(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_tlsdnsclose_t *ievent = + (isc__netievent_tlsdnsclose_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + + UNUSED(worker); + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + + tlsdns_close_direct(sock); +} + +static void +tlsdns_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_tlsdns_shutdown(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + REQUIRE(sock->type == isc_nm_tlsdnssocket); + + /* + * 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->tls.tls) { + /* Shutdown any active TLS connections */ + tlsdns_set_tls_shutdown(sock->tls.tls); + } + + if (atomic_load(&sock->accepting)) { + return; + } + + /* TLS handshake hasn't been completed yet */ + if (atomic_load(&sock->connecting)) { + isc_nmsocket_t *tsock = NULL; + + /* + * TCP connection has been established, now waiting on + * TLS handshake to complete + */ + if (sock->tls.pending_req != NULL) { + isc_result_t result = ISC_R_CANCELED; + isc__nm_uvreq_t *req = sock->tls.pending_req; + sock->tls.pending_req = NULL; + + if (peer_verification_has_failed(sock)) { + /* + * Save error message as 'sock->tls' will get + * detached. + */ + sock->tls.tls_verify_errmsg = + isc_tls_verify_peer_result_string( + sock->tls.tls); + result = ISC_R_TLSBADPEERCERT; + } + isc__nm_failed_connect_cb(sock, req, result, false); + return; + } + + /* The TCP connection hasn't been established yet */ + isc__nmsocket_attach(sock, &tsock); + uv_close(&sock->uv_handle.handle, tlsdns_close_connect_cb); + return; + } + + if (sock->statichandle != NULL) { + if (isc__nm_closing(sock)) { + isc__nm_failed_read_cb(sock, ISC_R_SHUTTINGDOWN, false); + } else { + 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_tlsdns_cancelread(isc_nmhandle_t *handle) { + isc_nmsocket_t *sock = NULL; + isc__netievent_tlsdnscancel_t *ievent = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + + sock = handle->sock; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->type == isc_nm_tlsdnssocket); + + ievent = isc__nm_get_netievent_tlsdnscancel(sock->mgr, sock, handle); + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); +} + +void +isc__nm_async_tlsdnscancel(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_tlsdnscancel_t *ievent = + (isc__netievent_tlsdnscancel_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); +} + +/* Zone transfers/updates over TLS are allowed only when "dot" ALPN + * was negotiated. + * + * Per the XoT spec, we must also check that the TLS version is >= + * 1.3. The check could be added here. However, we still need to + * support platforms where no cryptographic library with TLSv1.3 + * support is available. As a result of this we cannot be too strict + * regarding the minimal TLS protocol version in order to make it + * possible to do encrypted zone transfers over TLSv1.2, as it would + * not be right to leave users on these platforms without means for + * encrypted zone transfers using BIND only. + * + * The ones requiring strict compatibility with the specification + * could disable TLSv1.2 in the configuration file. + */ +isc_result_t +isc__nm_tlsdns_xfr_checkperm(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->type == isc_nm_tlsdnssocket); + + if (!sock->tls.alpn_negotiated) { + return (ISC_R_DOTALPNERROR); + } + + return (ISC_R_SUCCESS); +} + +const char * +isc__nm_tlsdns_verify_tls_peer_result_string(const isc_nmhandle_t *handle) { + isc_nmsocket_t *sock = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + REQUIRE(handle->sock->type == isc_nm_tlsdnssocket); + + sock = handle->sock; + if (sock->tls.tls == NULL) { + return (sock->tls.tls_verify_errmsg); + } + + return (isc_tls_verify_peer_result_string(sock->tls.tls)); +} + +void +isc__nm_async_tlsdns_set_tlsctx(isc_nmsocket_t *listener, isc_tlsctx_t *tlsctx, + const int tid) { + REQUIRE(tid >= 0); + + isc_tlsctx_free(&listener->children[tid].tls.ctx); + isc_tlsctx_attach(tlsctx, &listener->children[tid].tls.ctx); +} + +void +isc__nm_tlsdns_cleanup_data(isc_nmsocket_t *sock) { + if (sock->type == isc_nm_tlsdnslistener || + sock->type == isc_nm_tlsdnssocket) + { + if (sock->tls.client_sess_cache != NULL) { + INSIST(atomic_load(&sock->client)); + INSIST(sock->type == isc_nm_tlsdnssocket); + isc_tlsctx_client_session_cache_detach( + &sock->tls.client_sess_cache); + } + if (sock->tls.ctx != NULL) { + INSIST(ISC_LIST_EMPTY(sock->tls.sendreqs)); + isc_tlsctx_free(&sock->tls.ctx); + } + } +} + +static void +tlsdns_keep_client_tls_session(isc_nmsocket_t *sock) { + /* + * Ensure that the isc_tls_t is being accessed from + * within the worker thread the socket is bound to. + */ + REQUIRE(sock->tid == isc_nm_tid()); + if (sock->tls.client_sess_cache != NULL && + sock->tls.client_session_saved == false) + { + INSIST(atomic_load(&sock->client)); + isc_tlsctx_client_session_cache_keep_sockaddr( + sock->tls.client_sess_cache, &sock->peer, + sock->tls.tls); + sock->tls.client_session_saved = true; + } +} diff --git a/lib/isc/netmgr/tlsstream.c b/lib/isc/netmgr/tlsstream.c new file mode 100644 index 0000000..7b49071 --- /dev/null +++ b/lib/isc/netmgr/tlsstream.c @@ -0,0 +1,1348 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 <libgen.h> +#include <unistd.h> +#include <uv.h> + +#include <openssl/err.h> +#include <openssl/ssl.h> + +#include <isc/atomic.h> +#include <isc/buffer.h> +#include <isc/condition.h> +#include <isc/log.h> +#include <isc/magic.h> +#include <isc/mem.h> +#include <isc/netmgr.h> +#include <isc/once.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 "../openssl_shim.h" +#include "netmgr-int.h" +#include "uv-compat.h" + +#define TLS_BUF_SIZE (UINT16_MAX) + +static isc_result_t +tls_error_to_result(const int tls_err, const int tls_state, isc_tls_t *tls) { + switch (tls_err) { + case SSL_ERROR_ZERO_RETURN: + return (ISC_R_EOF); + case SSL_ERROR_SSL: + if (tls != NULL && tls_state < TLS_IO && + SSL_get_verify_result(tls) != X509_V_OK) + { + return (ISC_R_TLSBADPEERCERT); + } + return (ISC_R_TLSERROR); + default: + return (ISC_R_UNEXPECTED); + } +} + +static void +tls_failed_read_cb(isc_nmsocket_t *sock, const isc_result_t result); + +static void +tls_do_bio(isc_nmsocket_t *sock, isc_region_t *received_data, + isc__nm_uvreq_t *send_data, bool finish); + +static void +tls_readcb(isc_nmhandle_t *handle, isc_result_t result, isc_region_t *region, + void *cbarg); + +static void +tls_close_direct(isc_nmsocket_t *sock); + +static void +async_tls_do_bio(isc_nmsocket_t *sock); + +static void +tls_init_listener_tlsctx(isc_nmsocket_t *listener, isc_tlsctx_t *ctx); + +static void +tls_cleanup_listener_tlsctx(isc_nmsocket_t *listener); + +static isc_tlsctx_t * +tls_get_listener_tlsctx(isc_nmsocket_t *listener, const int tid); + +static void +tls_keep_client_tls_session(isc_nmsocket_t *sock); + +static void +tls_try_shutdown(isc_tls_t *tls, const bool quite); + +/* + * The socket is closing, outerhandle has been detached, listener is + * inactive, or the netmgr is closing: any operation on it should abort + * with ISC_R_CANCELED. + */ +static bool +inactive(isc_nmsocket_t *sock) { + return (!isc__nmsocket_active(sock) || atomic_load(&sock->closing) || + sock->outerhandle == NULL || + !isc__nmsocket_active(sock->outerhandle->sock) || + atomic_load(&sock->outerhandle->sock->closing) || + (sock->listener != NULL && + !isc__nmsocket_active(sock->listener)) || + isc__nm_closing(sock)); +} + +static void +tls_call_connect_cb(isc_nmsocket_t *sock, isc_nmhandle_t *handle, + const isc_result_t result) { + if (sock->connect_cb == NULL) { + return; + } + sock->connect_cb(handle, result, sock->connect_cbarg); + if (result != ISC_R_SUCCESS) { + isc__nmsocket_clearcb(handle->sock); + } +} + +static void +tls_senddone(isc_nmhandle_t *handle, isc_result_t eresult, void *cbarg) { + isc_nmsocket_tls_send_req_t *send_req = + (isc_nmsocket_tls_send_req_t *)cbarg; + isc_nmsocket_t *tlssock = NULL; + bool finish = send_req->finish; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + REQUIRE(VALID_NMSOCK(send_req->tlssock)); + + tlssock = send_req->tlssock; + send_req->tlssock = NULL; + + if (finish) { + tls_try_shutdown(tlssock->tlsstream.tls, true); + } + + if (send_req->cb != NULL) { + INSIST(VALID_NMHANDLE(tlssock->statichandle)); + send_req->cb(send_req->handle, eresult, send_req->cbarg); + isc_nmhandle_detach(&send_req->handle); + /* The last handle has been just detached: close the underlying + * socket. */ + if (tlssock->statichandle == NULL) { + finish = true; + } + } + + /* We are tying to avoid a memory allocation for small write + * requests. See the mirroring code in the tls_send_outgoing() + * function. */ + if (send_req->data.length > sizeof(send_req->smallbuf)) { + isc_mem_put(handle->sock->mgr->mctx, send_req->data.base, + send_req->data.length); + } else { + INSIST(&send_req->smallbuf[0] == send_req->data.base); + } + isc_mem_put(handle->sock->mgr->mctx, send_req, sizeof(*send_req)); + tlssock->tlsstream.nsending--; + + if (finish && eresult == ISC_R_SUCCESS) { + tlssock->tlsstream.reading = false; + isc_nm_cancelread(handle); + } else if (eresult == ISC_R_SUCCESS) { + tls_do_bio(tlssock, NULL, NULL, false); + } else if (eresult != ISC_R_SUCCESS && + tlssock->tlsstream.state <= TLS_HANDSHAKE && + !tlssock->tlsstream.server) + { + /* + * We are still waiting for the handshake to complete, but + * it isn't going to happen. Call the connect callback, + * passing the error code there. + * + * (Note: tls_failed_read_cb() calls the connect + * rather than the read callback in this case. + * XXX: clarify?) + */ + tls_failed_read_cb(tlssock, eresult); + } + + isc__nmsocket_detach(&tlssock); +} + +static void +tls_failed_read_cb(isc_nmsocket_t *sock, const isc_result_t result) { + bool destroy = true; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(result != ISC_R_SUCCESS); + + if (!sock->tlsstream.server && + (sock->tlsstream.state == TLS_INIT || + sock->tlsstream.state == TLS_HANDSHAKE) && + sock->connect_cb != NULL) + { + isc_nmhandle_t *handle = NULL; + INSIST(sock->statichandle == NULL); + handle = isc__nmhandle_get(sock, &sock->peer, &sock->iface); + tls_call_connect_cb(sock, handle, result); + isc__nmsocket_clearcb(sock); + isc_nmhandle_detach(&handle); + } else if (sock->recv_cb != NULL && sock->statichandle != NULL && + (sock->recv_read || result == ISC_R_TIMEDOUT)) + { + isc__nm_uvreq_t *req = NULL; + INSIST(VALID_NMHANDLE(sock->statichandle)); + sock->recv_read = false; + req = isc__nm_uvreq_get(sock->mgr, sock); + req->cb.recv = sock->recv_cb; + req->cbarg = sock->recv_cbarg; + isc_nmhandle_attach(sock->statichandle, &req->handle); + if (result != ISC_R_TIMEDOUT) { + isc__nmsocket_clearcb(sock); + } + isc__nm_readcb(sock, req, result); + if (result == ISC_R_TIMEDOUT && + (sock->outerhandle == NULL || + isc__nmsocket_timer_running(sock->outerhandle->sock))) + { + destroy = false; + } + } + + if (destroy) { + isc__nmsocket_prep_destroy(sock); + } +} + +static void +async_tls_do_bio(isc_nmsocket_t *sock) { + isc__netievent_tlsdobio_t *ievent = + isc__nm_get_netievent_tlsdobio(sock->mgr, sock); + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); +} + +static int +tls_send_outgoing(isc_nmsocket_t *sock, bool finish, isc_nmhandle_t *tlshandle, + isc_nm_cb_t cb, void *cbarg) { + isc_nmsocket_tls_send_req_t *send_req = NULL; + int pending; + int rv; + size_t len = 0; + + if (inactive(sock)) { + if (cb != NULL) { + INSIST(VALID_NMHANDLE(tlshandle)); + cb(tlshandle, ISC_R_CANCELED, cbarg); + } + return (0); + } + + if (finish) { + tls_try_shutdown(sock->tlsstream.tls, false); + tls_keep_client_tls_session(sock); + } + + pending = BIO_pending(sock->tlsstream.bio_out); + if (pending <= 0) { + return (pending); + } + + /* TODO Should we keep track of these requests in a list? */ + if ((unsigned int)pending > TLS_BUF_SIZE) { + pending = TLS_BUF_SIZE; + } + + send_req = isc_mem_get(sock->mgr->mctx, sizeof(*send_req)); + *send_req = (isc_nmsocket_tls_send_req_t){ .finish = finish, + .data.length = pending }; + + /* Let's try to avoid a memory allocation for small write requests */ + if ((size_t)pending > sizeof(send_req->smallbuf)) { + send_req->data.base = isc_mem_get(sock->mgr->mctx, pending); + } else { + send_req->data.base = &send_req->smallbuf[0]; + } + + isc__nmsocket_attach(sock, &send_req->tlssock); + if (cb != NULL) { + send_req->cb = cb; + send_req->cbarg = cbarg; + isc_nmhandle_attach(tlshandle, &send_req->handle); + } + + rv = BIO_read_ex(sock->tlsstream.bio_out, send_req->data.base, pending, + &len); + /* There's something pending, read must succeed */ + RUNTIME_CHECK(rv == 1); + + INSIST(VALID_NMHANDLE(sock->outerhandle)); + + sock->tlsstream.nsending++; + isc_nm_send(sock->outerhandle, &send_req->data, tls_senddone, send_req); + + return (pending); +} + +static int +tls_process_outgoing(isc_nmsocket_t *sock, bool finish, + isc__nm_uvreq_t *send_data) { + int pending; + + bool received_shutdown = ((SSL_get_shutdown(sock->tlsstream.tls) & + SSL_RECEIVED_SHUTDOWN) != 0); + bool sent_shutdown = ((SSL_get_shutdown(sock->tlsstream.tls) & + SSL_SENT_SHUTDOWN) != 0); + + if (received_shutdown && !sent_shutdown) { + finish = true; + } + + /* Data from TLS to network */ + if (send_data != NULL) { + pending = tls_send_outgoing(sock, finish, send_data->handle, + send_data->cb.send, + send_data->cbarg); + } else { + pending = tls_send_outgoing(sock, finish, NULL, NULL, NULL); + } + + return (pending); +} + +static int +tls_try_handshake(isc_nmsocket_t *sock, isc_result_t *presult) { + int rv = 0; + isc_nmhandle_t *tlshandle = NULL; + + REQUIRE(sock->tlsstream.state == TLS_HANDSHAKE); + + if (SSL_is_init_finished(sock->tlsstream.tls) == 1) { + return (0); + } + + rv = SSL_do_handshake(sock->tlsstream.tls); + if (rv == 1) { + isc_result_t result = ISC_R_SUCCESS; + INSIST(SSL_is_init_finished(sock->tlsstream.tls) == 1); + INSIST(sock->statichandle == NULL); + isc__nmsocket_log_tls_session_reuse(sock, sock->tlsstream.tls); + tlshandle = isc__nmhandle_get(sock, &sock->peer, &sock->iface); + + if (isc__nm_closing(sock)) { + result = ISC_R_SHUTTINGDOWN; + } + + if (sock->tlsstream.server) { + if (isc__nmsocket_closing(sock->listener)) { + result = ISC_R_CANCELED; + } else if (result == ISC_R_SUCCESS) { + result = sock->listener->accept_cb( + tlshandle, result, + sock->listener->accept_cbarg); + } + } else { + tls_call_connect_cb(sock, tlshandle, result); + } + isc_nmhandle_detach(&tlshandle); + sock->tlsstream.state = TLS_IO; + + if (presult != NULL) { + *presult = result; + } + } + + return (rv); +} + +static bool +tls_try_to_close_unused_socket(isc_nmsocket_t *sock) { + if (sock->tlsstream.state > TLS_HANDSHAKE && + sock->statichandle == NULL && sock->tlsstream.nsending == 0) + { + /* + * It seems that no action on the socket has been + * scheduled on some point after the handshake, let's + * close the connection. + */ + isc__nmsocket_prep_destroy(sock); + return (true); + } + + return (false); +} + +static void +tls_do_bio(isc_nmsocket_t *sock, isc_region_t *received_data, + isc__nm_uvreq_t *send_data, bool finish) { + isc_result_t result = ISC_R_SUCCESS; + int pending, tls_status = SSL_ERROR_NONE; + int rv = 0; + size_t len = 0; + int saved_errno = 0; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + + /* We will resume read if TLS layer wants us to */ + if (sock->tlsstream.reading && sock->outerhandle) { + REQUIRE(VALID_NMHANDLE(sock->outerhandle)); + isc_nm_pauseread(sock->outerhandle); + } + + /* + * Clear the TLS error queue so that SSL_get_error() and SSL I/O + * routine calls will not get affected by prior error statuses. + * + * See here: + * https://www.openssl.org/docs/man3.0/man3/SSL_get_error.html + * + * In particular, it mentions the following: + * + * The current thread's error queue must be empty before the + * TLS/SSL I/O operation is attempted, or SSL_get_error() will not + * work reliably. + * + * As we use the result of SSL_get_error() to decide on I/O + * operations, we need to ensure that it works reliably by + * cleaning the error queue. + * + * The sum of details: https://stackoverflow.com/a/37980911 + */ + ERR_clear_error(); + + if (sock->tlsstream.state == TLS_INIT) { + INSIST(received_data == NULL && send_data == NULL); + if (sock->tlsstream.server) { + SSL_set_accept_state(sock->tlsstream.tls); + } else { + SSL_set_connect_state(sock->tlsstream.tls); + } + sock->tlsstream.state = TLS_HANDSHAKE; + rv = tls_try_handshake(sock, NULL); + INSIST(SSL_is_init_finished(sock->tlsstream.tls) == 0); + } else if (sock->tlsstream.state == TLS_CLOSED) { + return; + } else { /* initialised and doing I/O */ + if (received_data != NULL) { + INSIST(send_data == NULL); + rv = BIO_write_ex(sock->tlsstream.bio_in, + received_data->base, + received_data->length, &len); + if (rv <= 0 || len != received_data->length) { + result = ISC_R_TLSERROR; +#if defined(NETMGR_TRACE) && defined(NETMGR_TRACE_VERBOSE) + saved_errno = errno; +#endif + goto error; + } + + /* + * Only after doing the IO we can check whether SSL + * handshake is done. + */ + if (sock->tlsstream.state == TLS_HANDSHAKE) { + isc_result_t hs_result = ISC_R_UNSET; + rv = tls_try_handshake(sock, &hs_result); + if (sock->tlsstream.state == TLS_IO && + hs_result != ISC_R_SUCCESS) + { + /* + * The accept callback has been called + * unsuccessfully. Let's try to shut + * down the TLS connection gracefully. + */ + INSIST(SSL_is_init_finished( + sock->tlsstream.tls) == + 1); + finish = true; + } + } + } else if (send_data != NULL) { + INSIST(received_data == NULL); + INSIST(sock->tlsstream.state > TLS_HANDSHAKE); + bool received_shutdown = + ((SSL_get_shutdown(sock->tlsstream.tls) & + SSL_RECEIVED_SHUTDOWN) != 0); + bool sent_shutdown = + ((SSL_get_shutdown(sock->tlsstream.tls) & + SSL_SENT_SHUTDOWN) != 0); + rv = SSL_write_ex(sock->tlsstream.tls, + send_data->uvbuf.base, + send_data->uvbuf.len, &len); + if (rv != 1 || len != send_data->uvbuf.len) { + result = received_shutdown || sent_shutdown + ? ISC_R_CANCELED + : ISC_R_TLSERROR; + send_data->cb.send(send_data->handle, result, + send_data->cbarg); + send_data = NULL; + return; + } + } + + /* Decrypt and pass data from network to client */ + if (sock->tlsstream.state >= TLS_IO && sock->recv_cb != NULL && + !atomic_load(&sock->readpaused) && + sock->statichandle != NULL && !finish) + { + uint8_t recv_buf[TLS_BUF_SIZE]; + INSIST(sock->tlsstream.state > TLS_HANDSHAKE); + while ((rv = SSL_read_ex(sock->tlsstream.tls, recv_buf, + TLS_BUF_SIZE, &len)) == 1) + { + isc_region_t region; + region = (isc_region_t){ .base = &recv_buf[0], + .length = len }; + + INSIST(VALID_NMHANDLE(sock->statichandle)); + sock->recv_cb(sock->statichandle, ISC_R_SUCCESS, + ®ion, sock->recv_cbarg); + /* The handle could have been detached in + * sock->recv_cb, making the sock->statichandle + * nullified (it happens in netmgr.c). If it is + * the case, then it means that we are not + * interested in keeping the connection alive + * anymore. Let's shut down the SSL session, + * send what we have in the SSL buffers, + * and close the connection. + */ + if (sock->statichandle == NULL) { + finish = true; + break; + } else if (sock->recv_cb == NULL) { + /* + * The 'sock->recv_cb' might have been + * nullified during the call to + * 'sock->recv_cb'. That could happen, + * indirectly when wrapping up. + * + * In this case, let's close the TLS + * connection. + */ + finish = true; + break; + } else if (atomic_load(&sock->readpaused)) { + /* + * Reading has been paused from withing + * the context of read callback - stop + * processing incoming data. + */ + break; + } + } + } + } + errno = 0; + tls_status = SSL_get_error(sock->tlsstream.tls, rv); + saved_errno = errno; + + /* See "BUGS" section at: + * https://www.openssl.org/docs/man1.1.1/man3/SSL_get_error.html + * + * It is mentioned there that when TLS status equals + * SSL_ERROR_SYSCALL AND errno == 0 it means that underlying + * transport layer returned EOF prematurely. However, we are + * managing the transport ourselves, so we should just resume + * reading from the TCP socket. + * + * It seems that this case has been handled properly on modern + * versions of OpenSSL. That being said, the situation goes in + * line with the manual: it is briefly mentioned there that + * SSL_ERROR_SYSCALL might be returned not only in a case of + * low-level errors (like system call failures). + */ + if (tls_status == SSL_ERROR_SYSCALL && saved_errno == 0 && + received_data == NULL && send_data == NULL && finish == false) + { + tls_status = SSL_ERROR_WANT_READ; + } + + pending = tls_process_outgoing(sock, finish, send_data); + if (pending > 0 && tls_status != SSL_ERROR_SSL) { + /* We'll continue in tls_senddone */ + return; + } + + switch (tls_status) { + case SSL_ERROR_NONE: + case SSL_ERROR_ZERO_RETURN: + (void)tls_try_to_close_unused_socket(sock); + return; + case SSL_ERROR_WANT_WRITE: + if (sock->tlsstream.nsending == 0) { + /* + * Launch tls_do_bio asynchronously. If we're sending + * already the send callback will call it. + */ + async_tls_do_bio(sock); + } + return; + case SSL_ERROR_WANT_READ: + if (tls_try_to_close_unused_socket(sock) || + sock->outerhandle == NULL || atomic_load(&sock->readpaused)) + { + return; + } + + INSIST(VALID_NMHANDLE(sock->outerhandle)); + + if (sock->tlsstream.reading) { + isc_nm_resumeread(sock->outerhandle); + } else if (sock->tlsstream.state == TLS_HANDSHAKE) { + sock->tlsstream.reading = true; + isc_nm_read(sock->outerhandle, tls_readcb, sock); + } + return; + default: + result = tls_error_to_result(tls_status, sock->tlsstream.state, + sock->tlsstream.tls); + break; + } + +error: +#if defined(NETMGR_TRACE) && defined(NETMGR_TRACE_VERBOSE) + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, ISC_LOGMODULE_NETMGR, + ISC_LOG_NOTICE, + "SSL error in BIO: %d %s (errno: %d). Arguments: " + "received_data: %p, " + "send_data: %p, finish: %s", + tls_status, isc_result_totext(result), saved_errno, + received_data, send_data, finish ? "true" : "false"); +#endif + tls_failed_read_cb(sock, result); +} + +static void +tls_readcb(isc_nmhandle_t *handle, isc_result_t result, isc_region_t *region, + void *cbarg) { + isc_nmsocket_t *tlssock = (isc_nmsocket_t *)cbarg; + + REQUIRE(VALID_NMSOCK(tlssock)); + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(tlssock->tid == isc_nm_tid()); + + if (result != ISC_R_SUCCESS) { + tls_failed_read_cb(tlssock, result); + return; + } + + tls_do_bio(tlssock, region, NULL, false); +} + +static isc_result_t +initialize_tls(isc_nmsocket_t *sock, bool server) { + REQUIRE(sock->tid == isc_nm_tid()); + + sock->tlsstream.bio_in = BIO_new(BIO_s_mem()); + if (sock->tlsstream.bio_in == NULL) { + isc_tls_free(&sock->tlsstream.tls); + return (ISC_R_TLSERROR); + } + sock->tlsstream.bio_out = BIO_new(BIO_s_mem()); + if (sock->tlsstream.bio_out == NULL) { + BIO_free_all(sock->tlsstream.bio_in); + sock->tlsstream.bio_in = NULL; + isc_tls_free(&sock->tlsstream.tls); + return (ISC_R_TLSERROR); + } + + if (BIO_set_mem_eof_return(sock->tlsstream.bio_in, EOF) != 1 || + BIO_set_mem_eof_return(sock->tlsstream.bio_out, EOF) != 1) + { + goto error; + } + + SSL_set_bio(sock->tlsstream.tls, sock->tlsstream.bio_in, + sock->tlsstream.bio_out); + sock->tlsstream.server = server; + sock->tlsstream.nsending = 0; + sock->tlsstream.state = TLS_INIT; + return (ISC_R_SUCCESS); +error: + isc_tls_free(&sock->tlsstream.tls); + sock->tlsstream.bio_out = sock->tlsstream.bio_in = NULL; + return (ISC_R_TLSERROR); +} + +static isc_result_t +tlslisten_acceptcb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) { + isc_nmsocket_t *tlslistensock = (isc_nmsocket_t *)cbarg; + isc_nmsocket_t *tlssock = NULL; + isc_tlsctx_t *tlsctx = NULL; + int tid; + + /* If accept() was unsuccessful we can't do anything */ + if (result != ISC_R_SUCCESS) { + return (result); + } + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + REQUIRE(VALID_NMSOCK(tlslistensock)); + REQUIRE(tlslistensock->type == isc_nm_tlslistener); + + if (isc__nmsocket_closing(handle->sock) || + isc__nmsocket_closing(tlslistensock) || + !atomic_load(&tlslistensock->listening)) + { + return (ISC_R_CANCELED); + } + + /* + * We need to create a 'wrapper' tlssocket for this connection. + */ + tlssock = isc_mem_get(handle->sock->mgr->mctx, sizeof(*tlssock)); + isc__nmsocket_init(tlssock, handle->sock->mgr, isc_nm_tlssocket, + &handle->sock->iface); + + tid = isc_nm_tid(); + /* We need to initialize SSL now to reference SSL_CTX properly */ + tlsctx = tls_get_listener_tlsctx(tlslistensock, tid); + RUNTIME_CHECK(tlsctx != NULL); + isc_tlsctx_attach(tlsctx, &tlssock->tlsstream.ctx); + tlssock->tlsstream.tls = isc_tls_create(tlssock->tlsstream.ctx); + if (tlssock->tlsstream.tls == NULL) { + atomic_store(&tlssock->closed, true); + isc_tlsctx_free(&tlssock->tlsstream.ctx); + isc__nmsocket_detach(&tlssock); + return (ISC_R_TLSERROR); + } + + tlssock->extrahandlesize = tlslistensock->extrahandlesize; + isc__nmsocket_attach(tlslistensock, &tlssock->listener); + isc_nmhandle_attach(handle, &tlssock->outerhandle); + tlssock->peer = handle->sock->peer; + tlssock->read_timeout = atomic_load(&handle->sock->mgr->init); + tlssock->tid = tid; + + /* + * Hold a reference to tlssock in the TCP socket: it will + * detached in isc__nm_tls_cleanup_data(). + */ + handle->sock->tlsstream.tlssocket = tlssock; + + result = initialize_tls(tlssock, true); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + /* TODO: catch failure code, detach tlssock, and log the error */ + + tls_do_bio(tlssock, NULL, NULL, false); + return (result); +} + +isc_result_t +isc_nm_listentls(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, + SSL_CTX *sslctx, isc_nmsocket_t **sockp) { + isc_result_t result; + isc_nmsocket_t *tlssock = NULL; + isc_nmsocket_t *tsock = NULL; + + REQUIRE(VALID_NM(mgr)); + if (atomic_load(&mgr->closing)) { + return (ISC_R_SHUTTINGDOWN); + } + + tlssock = isc_mem_get(mgr->mctx, sizeof(*tlssock)); + + isc__nmsocket_init(tlssock, mgr, isc_nm_tlslistener, iface); + tlssock->result = ISC_R_UNSET; + tlssock->accept_cb = accept_cb; + tlssock->accept_cbarg = accept_cbarg; + tlssock->extrahandlesize = extrahandlesize; + tls_init_listener_tlsctx(tlssock, sslctx); + tlssock->tlsstream.tls = NULL; + + /* + * tlssock will be a TLS 'wrapper' around an unencrypted stream. + * We set tlssock->outer to a socket listening for a TCP connection. + */ + result = isc_nm_listentcp(mgr, iface, tlslisten_acceptcb, tlssock, + extrahandlesize, backlog, quota, + &tlssock->outer); + if (result != ISC_R_SUCCESS) { + atomic_store(&tlssock->closed, true); + isc__nmsocket_detach(&tlssock); + return (result); + } + + /* wait for listen result */ + isc__nmsocket_attach(tlssock->outer, &tsock); + tlssock->result = result; + atomic_store(&tlssock->active, true); + INSIST(tlssock->outer->tlsstream.tlslistener == NULL); + isc__nmsocket_attach(tlssock, &tlssock->outer->tlsstream.tlslistener); + isc__nmsocket_detach(&tsock); + INSIST(result != ISC_R_UNSET); + tlssock->nchildren = tlssock->outer->nchildren; + + isc__nmsocket_barrier_init(tlssock); + atomic_init(&tlssock->rchildren, tlssock->nchildren); + + if (result == ISC_R_SUCCESS) { + atomic_store(&tlssock->listening, true); + *sockp = tlssock; + } + + return (result); +} + +void +isc__nm_async_tlssend(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_tlssend_t *ievent = (isc__netievent_tlssend_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + isc__nm_uvreq_t *req = ievent->req; + + REQUIRE(VALID_UVREQ(req)); + REQUIRE(sock->tid == isc_nm_tid()); + + UNUSED(worker); + + ievent->req = NULL; + + if (inactive(sock)) { + req->cb.send(req->handle, ISC_R_CANCELED, req->cbarg); + goto done; + } + + tls_do_bio(sock, NULL, req, false); +done: + isc__nm_uvreq_put(&req, sock); + return; +} + +void +isc__nm_tls_send(isc_nmhandle_t *handle, const isc_region_t *region, + isc_nm_cb_t cb, void *cbarg) { + isc__netievent_tlssend_t *ievent = NULL; + isc__nm_uvreq_t *uvreq = NULL; + isc_nmsocket_t *sock = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + + sock = handle->sock; + + REQUIRE(sock->type == isc_nm_tlssocket); + + uvreq = isc__nm_uvreq_get(sock->mgr, sock); + isc_nmhandle_attach(handle, &uvreq->handle); + uvreq->cb.send = cb; + uvreq->cbarg = cbarg; + uvreq->uvbuf.base = (char *)region->base; + uvreq->uvbuf.len = region->length; + + /* + * We need to create an event and pass it using async channel + */ + ievent = isc__nm_get_netievent_tlssend(sock->mgr, sock, uvreq); + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); +} + +void +isc__nm_async_tlsstartread(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_tlsstartread_t *ievent = + (isc__netievent_tlsstartread_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + + REQUIRE(sock->tid == isc_nm_tid()); + + UNUSED(worker); + + tls_do_bio(sock, NULL, NULL, false); +} + +void +isc__nm_tls_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg) { + isc__netievent_tlsstartread_t *ievent = NULL; + isc_nmsocket_t *sock = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + + sock = handle->sock; + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->statichandle == handle); + REQUIRE(sock->tid == isc_nm_tid()); + REQUIRE(sock->recv_cb == NULL); + + if (inactive(sock)) { + cb(handle, ISC_R_CANCELED, NULL, cbarg); + return; + } + + sock->recv_cb = cb; + sock->recv_cbarg = cbarg; + sock->recv_read = true; + + ievent = isc__nm_get_netievent_tlsstartread(sock->mgr, sock); + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); +} + +void +isc__nm_tls_pauseread(isc_nmhandle_t *handle) { + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + + if (atomic_compare_exchange_strong(&handle->sock->readpaused, + &(bool){ false }, true)) + { + if (handle->sock->outerhandle != NULL) { + isc_nm_pauseread(handle->sock->outerhandle); + } + } +} + +void +isc__nm_tls_resumeread(isc_nmhandle_t *handle) { + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + + if (!atomic_compare_exchange_strong(&handle->sock->readpaused, + &(bool){ true }, false)) + { + if (inactive(handle->sock)) { + return; + } + + async_tls_do_bio(handle->sock); + } +} + +static void +tls_close_direct(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + /* + * At this point we're certain that there are no + * external references, we can close everything. + */ + if (sock->outerhandle != NULL) { + isc_nm_pauseread(sock->outerhandle); + isc__nmsocket_clearcb(sock->outerhandle->sock); + isc_nmhandle_detach(&sock->outerhandle); + } + + if (sock->listener != NULL) { + isc__nmsocket_detach(&sock->listener); + } + + /* Further cleanup performed in isc__nm_tls_cleanup_data() */ + atomic_store(&sock->closed, true); + atomic_store(&sock->active, false); + sock->tlsstream.state = TLS_CLOSED; +} + +void +isc__nm_tls_close(isc_nmsocket_t *sock) { + isc__netievent_tlsclose_t *ievent = NULL; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->type == isc_nm_tlssocket); + + if (!atomic_compare_exchange_strong(&sock->closing, &(bool){ false }, + true)) + { + return; + } + + ievent = isc__nm_get_netievent_tlsclose(sock->mgr, sock); + isc__nm_maybe_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); +} + +void +isc__nm_async_tlsclose(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_tlsclose_t *ievent = (isc__netievent_tlsclose_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + + REQUIRE(ievent->sock->tid == isc_nm_tid()); + + UNUSED(worker); + + tls_close_direct(sock); +} + +void +isc__nm_tls_stoplistening(isc_nmsocket_t *sock) { + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->type == isc_nm_tlslistener); + + isc__nmsocket_stop(sock); +} + +static void +tcp_connected(isc_nmhandle_t *handle, isc_result_t result, void *cbarg); + +void +isc_nm_tlsconnect(isc_nm_t *mgr, isc_sockaddr_t *local, isc_sockaddr_t *peer, + isc_nm_cb_t cb, void *cbarg, isc_tlsctx_t *ctx, + isc_tlsctx_client_session_cache_t *client_sess_cache, + unsigned int timeout, size_t extrahandlesize) { + isc_nmsocket_t *nsock = NULL; +#if defined(NETMGR_TRACE) && defined(NETMGR_TRACE_VERBOSE) + fprintf(stderr, "TLS: isc_nm_tlsconnect(): in net thread: %s\n", + isc__nm_in_netthread() ? "yes" : "no"); +#endif /* NETMGR_TRACE */ + + REQUIRE(VALID_NM(mgr)); + + if (atomic_load(&mgr->closing)) { + cb(NULL, ISC_R_SHUTTINGDOWN, cbarg); + return; + } + + nsock = isc_mem_get(mgr->mctx, sizeof(*nsock)); + isc__nmsocket_init(nsock, mgr, isc_nm_tlssocket, local); + nsock->extrahandlesize = extrahandlesize; + nsock->result = ISC_R_UNSET; + nsock->connect_cb = cb; + nsock->connect_cbarg = cbarg; + nsock->connect_timeout = timeout; + isc_tlsctx_attach(ctx, &nsock->tlsstream.ctx); + atomic_init(&nsock->client, true); + if (client_sess_cache != NULL) { + INSIST(isc_tlsctx_client_session_cache_getctx( + client_sess_cache) == ctx); + isc_tlsctx_client_session_cache_attach( + client_sess_cache, &nsock->tlsstream.client_sess_cache); + } + + isc_nm_tcpconnect(mgr, local, peer, tcp_connected, nsock, + nsock->connect_timeout, 0); +} + +static void +tcp_connected(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) { + isc_nmsocket_t *tlssock = (isc_nmsocket_t *)cbarg; + isc_nmhandle_t *tlshandle = NULL; + + REQUIRE(VALID_NMSOCK(tlssock)); + + tlssock->tid = isc_nm_tid(); + if (result != ISC_R_SUCCESS) { + goto error; + } + + INSIST(VALID_NMHANDLE(handle)); + + tlssock->iface = handle->sock->iface; + tlssock->peer = handle->sock->peer; + if (isc__nm_closing(tlssock)) { + result = ISC_R_SHUTTINGDOWN; + goto error; + } + + /* + * We need to initialize SSL now to reference SSL_CTX properly. + */ + tlssock->tlsstream.tls = isc_tls_create(tlssock->tlsstream.ctx); + if (tlssock->tlsstream.tls == NULL) { + result = ISC_R_TLSERROR; + goto error; + } + + result = initialize_tls(tlssock, false); + if (result != ISC_R_SUCCESS) { + goto error; + } + tlssock->peer = isc_nmhandle_peeraddr(handle); + isc_nmhandle_attach(handle, &tlssock->outerhandle); + atomic_store(&tlssock->active, true); + + if (tlssock->tlsstream.client_sess_cache != NULL) { + isc_tlsctx_client_session_cache_reuse_sockaddr( + tlssock->tlsstream.client_sess_cache, &tlssock->peer, + tlssock->tlsstream.tls); + } + + /* + * Hold a reference to tlssock in the TCP socket: it will + * detached in isc__nm_tls_cleanup_data(). + */ + handle->sock->tlsstream.tlssocket = tlssock; + + tls_do_bio(tlssock, NULL, NULL, false); + return; +error: + tlshandle = isc__nmhandle_get(tlssock, NULL, NULL); + atomic_store(&tlssock->closed, true); + tls_call_connect_cb(tlssock, tlshandle, result); + isc_nmhandle_detach(&tlshandle); + isc__nmsocket_detach(&tlssock); +} + +static void +tls_cancelread(isc_nmsocket_t *sock) { + if (!inactive(sock) && sock->tlsstream.state == TLS_IO) { + tls_do_bio(sock, NULL, NULL, true); + } else if (sock->outerhandle != NULL) { + sock->tlsstream.reading = false; + isc_nm_cancelread(sock->outerhandle); + } +} + +void +isc__nm_tls_cancelread(isc_nmhandle_t *handle) { + isc_nmsocket_t *sock = NULL; + isc__netievent_tlscancel_t *ievent = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + + sock = handle->sock; + + REQUIRE(sock->type == isc_nm_tlssocket); + + if (sock->tid == isc_nm_tid()) { + tls_cancelread(sock); + } else { + ievent = isc__nm_get_netievent_tlscancel(sock->mgr, sock, + handle); + isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid], + (isc__netievent_t *)ievent); + } +} + +void +isc__nm_async_tlscancel(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_tlscancel_t *ievent = (isc__netievent_tlscancel_t *)ev0; + isc_nmsocket_t *sock = ievent->sock; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(worker->id == sock->tid); + REQUIRE(sock->tid == isc_nm_tid()); + + UNUSED(worker); + tls_cancelread(sock); +} + +void +isc__nm_async_tlsdobio(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_tlsdobio_t *ievent = (isc__netievent_tlsdobio_t *)ev0; + + UNUSED(worker); + + tls_do_bio(ievent->sock, NULL, NULL, false); +} + +void +isc__nm_tls_cleanup_data(isc_nmsocket_t *sock) { + if (sock->type == isc_nm_tcplistener && + sock->tlsstream.tlslistener != NULL) + { + isc__nmsocket_detach(&sock->tlsstream.tlslistener); + } else if (sock->type == isc_nm_tlslistener) { + tls_cleanup_listener_tlsctx(sock); + } else if (sock->type == isc_nm_tlssocket) { + if (sock->tlsstream.tls != NULL) { + /* + * Let's shut down the TLS session properly so that + * the session will remain resumable, if required. + */ + tls_try_shutdown(sock->tlsstream.tls, true); + tls_keep_client_tls_session(sock); + isc_tls_free(&sock->tlsstream.tls); + /* These are destroyed when we free SSL */ + sock->tlsstream.bio_out = NULL; + sock->tlsstream.bio_in = NULL; + } + if (sock->tlsstream.ctx != NULL) { + isc_tlsctx_free(&sock->tlsstream.ctx); + } + if (sock->tlsstream.client_sess_cache != NULL) { + INSIST(atomic_load(&sock->client)); + isc_tlsctx_client_session_cache_detach( + &sock->tlsstream.client_sess_cache); + } + } else if (sock->type == isc_nm_tcpsocket && + sock->tlsstream.tlssocket != NULL) + { + /* + * The TLS socket can't be destroyed until its underlying TCP + * socket is, to avoid possible use-after-free errors. + */ + isc__nmsocket_detach(&sock->tlsstream.tlssocket); + } +} + +void +isc__nm_tls_cleartimeout(isc_nmhandle_t *handle) { + isc_nmsocket_t *sock = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + REQUIRE(handle->sock->type == isc_nm_tlssocket); + + sock = handle->sock; + if (sock->outerhandle != NULL) { + INSIST(VALID_NMHANDLE(sock->outerhandle)); + isc_nmhandle_cleartimeout(sock->outerhandle); + } +} + +void +isc__nm_tls_settimeout(isc_nmhandle_t *handle, uint32_t timeout) { + isc_nmsocket_t *sock = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + REQUIRE(handle->sock->type == isc_nm_tlssocket); + + sock = handle->sock; + if (sock->outerhandle != NULL) { + INSIST(VALID_NMHANDLE(sock->outerhandle)); + isc_nmhandle_settimeout(sock->outerhandle, timeout); + } +} + +void +isc__nmhandle_tls_keepalive(isc_nmhandle_t *handle, bool value) { + isc_nmsocket_t *sock = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + REQUIRE(handle->sock->type == isc_nm_tlssocket); + + sock = handle->sock; + if (sock->outerhandle != NULL) { + INSIST(VALID_NMHANDLE(sock->outerhandle)); + + isc_nmhandle_keepalive(sock->outerhandle, value); + } +} + +void +isc__nmhandle_tls_setwritetimeout(isc_nmhandle_t *handle, + uint64_t write_timeout) { + isc_nmsocket_t *sock = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + REQUIRE(handle->sock->type == isc_nm_tlssocket); + + sock = handle->sock; + if (sock->outerhandle != NULL) { + INSIST(VALID_NMHANDLE(sock->outerhandle)); + + isc_nmhandle_setwritetimeout(sock->outerhandle, write_timeout); + } +} + +const char * +isc__nm_tls_verify_tls_peer_result_string(const isc_nmhandle_t *handle) { + isc_nmsocket_t *sock = NULL; + + REQUIRE(VALID_NMHANDLE(handle)); + REQUIRE(VALID_NMSOCK(handle->sock)); + REQUIRE(handle->sock->type == isc_nm_tlssocket); + + sock = handle->sock; + if (sock->tlsstream.tls == NULL) { + return (NULL); + } + + return (isc_tls_verify_peer_result_string(sock->tlsstream.tls)); +} + +static void +tls_init_listener_tlsctx(isc_nmsocket_t *listener, isc_tlsctx_t *ctx) { + size_t nworkers; + + REQUIRE(VALID_NM(listener->mgr)); + REQUIRE(ctx != NULL); + + nworkers = (size_t)listener->mgr->nworkers; + INSIST(nworkers > 0); + + listener->tlsstream.listener_tls_ctx = isc_mem_get( + listener->mgr->mctx, sizeof(isc_tlsctx_t *) * nworkers); + listener->tlsstream.n_listener_tls_ctx = nworkers; + for (size_t i = 0; i < nworkers; i++) { + listener->tlsstream.listener_tls_ctx[i] = NULL; + isc_tlsctx_attach(ctx, + &listener->tlsstream.listener_tls_ctx[i]); + } +} + +static void +tls_cleanup_listener_tlsctx(isc_nmsocket_t *listener) { + REQUIRE(VALID_NM(listener->mgr)); + + if (listener->tlsstream.listener_tls_ctx == NULL) { + return; + } + + for (size_t i = 0; i < listener->tlsstream.n_listener_tls_ctx; i++) { + isc_tlsctx_free(&listener->tlsstream.listener_tls_ctx[i]); + } + isc_mem_put(listener->mgr->mctx, listener->tlsstream.listener_tls_ctx, + sizeof(isc_tlsctx_t *) * + listener->tlsstream.n_listener_tls_ctx); + listener->tlsstream.n_listener_tls_ctx = 0; +} + +static isc_tlsctx_t * +tls_get_listener_tlsctx(isc_nmsocket_t *listener, const int tid) { + REQUIRE(VALID_NM(listener->mgr)); + REQUIRE(tid >= 0); + + if (listener->tlsstream.listener_tls_ctx == NULL) { + return (NULL); + } + + return (listener->tlsstream.listener_tls_ctx[tid]); +} + +void +isc__nm_async_tls_set_tlsctx(isc_nmsocket_t *listener, isc_tlsctx_t *tlsctx, + const int tid) { + REQUIRE(tid >= 0); + + isc_tlsctx_free(&listener->tlsstream.listener_tls_ctx[tid]); + isc_tlsctx_attach(tlsctx, &listener->tlsstream.listener_tls_ctx[tid]); +} + +static void +tls_keep_client_tls_session(isc_nmsocket_t *sock) { + /* + * Ensure that the isc_tls_t is being accessed from + * within the worker thread the socket is bound to. + */ + REQUIRE(sock->tid == isc_nm_tid()); + if (sock->tlsstream.client_sess_cache != NULL && + sock->tlsstream.client_session_saved == false) + { + INSIST(atomic_load(&sock->client)); + isc_tlsctx_client_session_cache_keep_sockaddr( + sock->tlsstream.client_sess_cache, &sock->peer, + sock->tlsstream.tls); + sock->tlsstream.client_session_saved = true; + } +} + +static void +tls_try_shutdown(isc_tls_t *tls, const bool force) { + if (force) { + (void)SSL_set_shutdown(tls, SSL_SENT_SHUTDOWN); + } else if ((SSL_get_shutdown(tls) & SSL_SENT_SHUTDOWN) == 0) { + (void)SSL_shutdown(tls); + } +} diff --git a/lib/isc/netmgr/udp.c b/lib/isc/netmgr/udp.c new file mode 100644 index 0000000..1a0ee16 --- /dev/null +++ b/lib/isc/netmgr/udp.c @@ -0,0 +1,1405 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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" + +#ifdef HAVE_NET_ROUTE_H +#include <net/route.h> +#if defined(RTM_VERSION) && defined(RTM_NEWADDR) && defined(RTM_DELADDR) +#define USE_ROUTE_SOCKET 1 +#define ROUTE_SOCKET_PF PF_ROUTE +#define ROUTE_SOCKET_PROTOCOL 0 +#define MSGHDR rt_msghdr +#define MSGTYPE rtm_type +#endif /* if defined(RTM_VERSION) && defined(RTM_NEWADDR) && \ + * defined(RTM_DELADDR) */ +#endif /* ifdef HAVE_NET_ROUTE_H */ + +#if defined(HAVE_LINUX_NETLINK_H) && defined(HAVE_LINUX_RTNETLINK_H) +#include <linux/netlink.h> +#include <linux/rtnetlink.h> +#if defined(RTM_NEWADDR) && defined(RTM_DELADDR) +#define USE_ROUTE_SOCKET 1 +#define USE_NETLINK 1 +#define ROUTE_SOCKET_PF PF_NETLINK +#define ROUTE_SOCKET_PROTOCOL NETLINK_ROUTE +#define MSGHDR nlmsghdr +#define MSGTYPE nlmsg_type +#endif /* if defined(RTM_NEWADDR) && defined(RTM_DELADDR) */ +#endif /* if defined(HAVE_LINUX_NETLINK_H) && defined(HAVE_LINUX_RTNETLINK_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); + (void)isc__nm_socket_v6only(sock, sa_family); + + result = isc__nm_socket_reuse(sock); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + if (mgr->load_balance_sockets) { + result = isc__nm_socket_reuse_lb(sock); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } + + 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; + atomic_init(&csock->reading, true); + csock->recv_cb = sock->recv_cb; + csock->recv_cbarg = sock->recv_cbarg; + csock->extrahandlesize = sock->extrahandlesize; + csock->tid = tid; + + 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); + } + 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); + sock->nchildren = mgr->nworkers; + 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; + + if (!mgr->load_balance_sockets) { + fd = isc__nm_udp_lb_socket(mgr, iface->type.sa.sa_family); + } + + 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()); + } + + if (!mgr->load_balance_sockets) { + isc__nm_closesocket(fd); + } + + 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); +} + +#ifdef USE_ROUTE_SOCKET +static isc_result_t +route_socket(uv_os_sock_t *fdp) { + isc_result_t result; + uv_os_sock_t fd; +#ifdef USE_NETLINK + struct sockaddr_nl sa; + int r; +#endif + + result = isc__nm_socket(ROUTE_SOCKET_PF, SOCK_RAW, + ROUTE_SOCKET_PROTOCOL, &fd); + if (result != ISC_R_SUCCESS) { + return (result); + } + +#ifdef USE_NETLINK + sa.nl_family = PF_NETLINK; + sa.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR; + r = bind(fd, (struct sockaddr *)&sa, sizeof(sa)); + if (r < 0) { + isc__nm_closesocket(fd); + return (isc_errno_toresult(r)); + } +#endif + + *fdp = fd; + return (ISC_R_SUCCESS); +} + +static isc_result_t +route_connect_direct(isc_nmsocket_t *sock) { + isc__networker_t *worker = NULL; + isc_result_t result = ISC_R_UNSET; + 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); + + if (isc__nm_closing(sock)) { + result = ISC_R_SHUTTINGDOWN; + goto error; + } + + r = uv_udp_open(&sock->uv_handle.udp, sock->fd); + if (r != 0) { + goto done; + } + + isc__nm_set_network_buffers(sock->mgr, &sock->uv_handle.handle); + + atomic_store(&sock->connecting, false); + 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); +} + +/* + * Asynchronous 'udpconnect' call handler: open a new UDP socket and + * call the 'open' callback with a handle. + */ +void +isc__nm_async_routeconnect(isc__networker_t *worker, isc__netievent_t *ev0) { + isc__netievent_routeconnect_t *ievent = + (isc__netievent_routeconnect_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 = route_connect_direct(sock); + 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); +} +#endif /* USE_ROUTE_SOCKET */ + +isc_result_t +isc_nm_routeconnect(isc_nm_t *mgr, isc_nm_cb_t cb, void *cbarg, + size_t extrahandlesize) { +#ifdef USE_ROUTE_SOCKET + isc_result_t result = ISC_R_SUCCESS; + isc_nmsocket_t *sock = NULL; + isc__netievent_udpconnect_t *event = NULL; + isc__nm_uvreq_t *req = NULL; + + REQUIRE(VALID_NM(mgr)); + + sock = isc_mem_get(mgr->mctx, sizeof(*sock)); + isc__nmsocket_init(sock, mgr, isc_nm_udpsocket, NULL); + + sock->connect_cb = cb; + sock->connect_cbarg = cbarg; + sock->extrahandlesize = extrahandlesize; + sock->result = ISC_R_UNSET; + atomic_init(&sock->client, true); + sock->route_sock = true; + + req = isc__nm_uvreq_get(mgr, sock); + req->cb.connect = cb; + req->cbarg = cbarg; + req->handle = isc__nmhandle_get(sock, NULL, NULL); + + result = route_socket(&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); + } + + event = isc__nm_get_netievent_routeconnect(mgr, sock, req); + + if (isc__nm_in_netthread()) { + atomic_store(&sock->active, true); + sock->tid = isc_nm_tid(); + isc__nm_async_routeconnect(&mgr->workers[sock->tid], + (isc__netievent_t *)event); + isc__nm_put_netievent_routeconnect(mgr, event); + } else { + atomic_init(&sock->active, false); + sock->tid = 0; + 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); + + return (sock->result); +#else /* USE_ROUTE_SOCKET */ + UNUSED(mgr); + UNUSED(cb); + UNUSED(cbarg); + UNUSED(extrahandlesize); + return (ISC_R_NOTIMPLEMENTED); +#endif /* USE_ROUTE_SOCKET */ +} + +/* + * 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()); + + (void)isc__nm_socket_min_mtu(sock->fd, sa_family); + +#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, STATID_OPENFAIL); + goto done; + } + isc__nm_incstats(sock, STATID_OPEN); + + if (sa_family == AF_INET6) { + uv_bind_flags |= UV_UDP_IPV6ONLY; + } + + 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, 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, 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; + } + } + + isc__nm_set_network_buffers(sock->mgr, &sock->uv_handle.handle); + + r = uv_udp_recv_start(&sock->uv_handle.udp, isc__nm_alloc_cb, + udp_recv_cb); + if (r != 0) { + isc__nm_incstats(sock, 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_result_t result; + isc_sockaddr_t sockaddr, *sa = NULL; + + REQUIRE(VALID_NMSOCK(sock)); + REQUIRE(sock->tid == isc_nm_tid()); + REQUIRE(atomic_load(&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 there was a networking error. + */ + if (nrecv < 0) { + isc__nm_failed_read_cb(sock, isc__nm_uverr2result(nrecv), + false); + 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 (!sock->route_sock) { + result = isc_sockaddr_fromsockaddr(&sockaddr, addr); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + sa = &sockaddr; + } + + req = isc__nm_get_read_req(sock, sa); + + /* + * 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 (isc__nm_in_netthread()) { + ntid = isc_nm_tid(); + } else { + ntid = sock->tid; + } + rsock = &sock->parent->children[ntid]; + } + +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, 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, 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 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); + + if (isc__nm_closing(sock)) { + result = ISC_R_SHUTTINGDOWN; + goto error; + } + + r = uv_udp_open(&sock->uv_handle.udp, sock->fd); + if (r != 0) { + isc__nm_incstats(sock, STATID_OPENFAIL); + goto done; + } + isc__nm_incstats(sock, 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, STATID_BINDFAIL); + goto done; + } + + isc__nm_set_network_buffers(sock->mgr, &sock->uv_handle.handle); + + /* + * 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 && --req->connect_tries > 0); + if (r != 0) { + isc__nm_incstats(sock, STATID_CONNECTFAIL); + goto done; + } + isc__nm_incstats(sock, STATID_CONNECT); + + atomic_store(&sock->connecting, false); + 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); +} + +/* + * 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); + + (void)isc__nm_socket_min_mtu(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__nmsocket_timer_stop(sock); + 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__nm_closing(sock)) { + result = ISC_R_SHUTTINGDOWN; + } else if (isc__nmsocket_closing(sock)) { + result = ISC_R_CANCELED; + } else { + result = isc__nm_start_reading(sock); + } + + if (result != ISC_R_SUCCESS) { + atomic_store(&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->recv_read); + + sock->recv_cb = cb; + sock->recv_cbarg = cbarg; + sock->recv_read = true; + + if (!atomic_load(&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, 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, 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) { + if (isc__nm_closing(sock)) { + isc__nm_failed_read_cb(sock, ISC_R_SHUTTINGDOWN, false); + } else { + 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..b7c0f7b --- /dev/null +++ b/lib/isc/netmgr/uv-compat.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 "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); + err = connect(handle->io_watcher.fd, addr, addrlen); + } while (err == -1 && errno == EINTR); + + if (err) { +#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) */ + } + + 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); + } + + 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..9f16ea8 --- /dev/null +++ b/lib/isc/netmgr/uverr2result.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. + */ + +#include <stdbool.h> +#include <uv.h> + +#include <isc/result.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); + case UV_ENOPROTOOPT: + case UV_EPROTONOSUPPORT: + return (ISC_R_INVALIDPROTO); + default: + if (dolog) { + UNEXPECTED_ERROR("unable to convert libuv error code " + "in %s (%s:%d) to isc_result: %d: %s", + func, file, line, 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..b8dbfaa --- /dev/null +++ b/lib/isc/openssl_shim.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. + */ + +#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_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_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 + +#if !HAVE_SSL_CTX_UP_REF +int +SSL_CTX_up_ref(SSL_CTX *ctx) { + return (CRYPTO_add(&ctx->references, 1, CRYPTO_LOCK_SSL_CTX) > 0); +} +#endif /* !HAVE_SSL_CTX_UP_REF */ + +#if !HAVE_X509_STORE_UP_REF + +int +X509_STORE_up_ref(X509_STORE *store) { + return (CRYPTO_add(&store->references, 1, CRYPTO_LOCK_X509_STORE) > 0); +} + +#endif /* !HAVE_OPENSSL_CLEANUP */ + +#if !HAVE_SSL_CTX_SET1_CERT_STORE + +void +SSL_CTX_set1_cert_store(SSL_CTX *ctx, X509_STORE *store) { + (void)X509_STORE_up_ref(store); + + SSL_CTX_set_cert_store(ctx, store); +} + +#endif /* !HAVE_SSL_CTX_SET1_CERT_STORE */ diff --git a/lib/isc/openssl_shim.h b/lib/isc/openssl_shim.h new file mode 100644 index 0000000..c0abd14 --- /dev/null +++ b/lib/isc/openssl_shim.h @@ -0,0 +1,137 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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_PKEY_NEW_RAW_PRIVATE_KEY +#define EVP_PKEY_new_raw_private_key(type, e, key, keylen) \ + EVP_PKEY_new_mac_key(type, e, key, (int)(keylen)) +#endif /* if !HAVE_EVP_PKEY_NEW_RAW_PRIVATE_KEY */ + +#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 +#define EVP_MD_CTX_new EVP_MD_CTX_create +#endif /* if !HAVE_EVP_MD_CTX_NEW */ + +#if !HAVE_EVP_MD_CTX_FREE +#define EVP_MD_CTX_free EVP_MD_CTX_destroy +#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_EVP_MD_CTX_GET0_MD +#define EVP_MD_CTX_get0_md EVP_MD_CTX_md +#endif /* if !HAVE_EVP_MD_CTX_GET0_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 + +#if !HAVE_SSL_CTX_UP_REF +int +SSL_CTX_up_ref(SSL_CTX *store); +#endif /* !HAVE_SSL_CTX_UP_REF */ + +#if !HAVE_X509_STORE_UP_REF +int +X509_STORE_up_ref(X509_STORE *v); +#endif /* !HAVE_OPENSSL_CLEANUP */ + +#if !HAVE_SSL_CTX_SET1_CERT_STORE +void +SSL_CTX_set1_cert_store(SSL_CTX *ctx, X509_STORE *store); +#endif /* !HAVE_SSL_CTX_SET1_CERT_STORE */ diff --git a/lib/isc/os.c b/lib/isc/os.c new file mode 100644 index 0000000..0ba0fab --- /dev/null +++ b/lib/isc/os.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 <inttypes.h> +#include <sys/stat.h> + +#include <isc/os.h> +#include <isc/types.h> +#include <isc/util.h> + +#include "os_p.h" + +static unsigned int isc__os_ncpus = 0; +static unsigned long isc__os_cacheline = ISC_OS_CACHELINE_SIZE; +static mode_t isc__os_umask = 0; + +#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) */ + +static void +ncpus_initialize(void) { +#if defined(HAVE_SYSCONF) + isc__os_ncpus = sysconf_ncpus(); +#endif /* if defined(HAVE_SYSCONF) */ +#if defined(HAVE_SYS_SYSCTL_H) && defined(HAVE_SYSCTLBYNAME) + if (isc__os_ncpus <= 0) { + isc__os_ncpus = sysctl_ncpus(); + } +#endif /* if defined(HAVE_SYS_SYSCTL_H) && defined(HAVE_SYSCTLBYNAME) */ + if (isc__os_ncpus == 0) { + isc__os_ncpus = 1; + } +} + +static void +umask_initialize(void) { + isc__os_umask = umask(0); + (void)umask(isc__os_umask); +} + +unsigned int +isc_os_ncpus(void) { + return (isc__os_ncpus); +} + +unsigned long +isc_os_cacheline(void) { + return (isc__os_cacheline); +} + +mode_t +isc_os_umask(void) { + return (isc__os_umask); +} + +void +isc__os_initialize(void) { + umask_initialize(); + ncpus_initialize(); +#if defined(HAVE_SYSCONF) && defined(_SC_LEVEL1_DCACHE_LINESIZE) + long s = sysconf(_SC_LEVEL1_DCACHE_LINESIZE); + if (s > 0 && (unsigned long)s > isc__os_cacheline) { + isc__os_cacheline = s; + } +#endif +} + +void +isc__os_shutdown(void) { + /* empty, but defined for completeness */; +} diff --git a/lib/isc/os_p.h b/lib/isc/os_p.h new file mode 100644 index 0000000..c39bc23 --- /dev/null +++ b/lib/isc/os_p.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. + */ + +#pragma once + +#include <stdio.h> + +#include <isc/os.h> + +/*! \file */ + +void +isc__os_initialize(void); + +void +isc__os_shutdown(void); 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/picohttpparser.c b/lib/isc/picohttpparser.c new file mode 100644 index 0000000..d82fd01 --- /dev/null +++ b/lib/isc/picohttpparser.c @@ -0,0 +1,727 @@ +/* + * Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase, + * Shigeo Mitsunari + * + * SPDX-License-Identifier: MIT + * + * The software is licensed under either the MIT License (below) or the Perl + * license. + * + * 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 <assert.h> +#include <stddef.h> +#include <string.h> +#ifdef __SSE4_2__ +#ifdef _MSC_VER +#include <nmmintrin.h> +#else +#include <x86intrin.h> +#endif +#endif +#include "picohttpparser.h" + +#if __GNUC__ >= 3 +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) +#else +#define likely(x) (x) +#define unlikely(x) (x) +#endif + +#ifdef _MSC_VER +#define ALIGNED(n) _declspec(align(n)) +#else +#define ALIGNED(n) __attribute__((aligned(n))) +#endif + +#define IS_PRINTABLE_ASCII(c) ((unsigned char)(c)-040u < 0137u) + +#define CHECK_EOF() \ + if (buf == buf_end) { \ + *ret = -2; \ + return NULL; \ + } + +#define EXPECT_CHAR_NO_CHECK(ch) \ + if (*buf++ != ch) { \ + *ret = -1; \ + return NULL; \ + } + +#define EXPECT_CHAR(ch) \ + CHECK_EOF(); \ + EXPECT_CHAR_NO_CHECK(ch); + +#define ADVANCE_TOKEN(tok, toklen) \ + do { \ + const char *tok_start = buf; \ + static const char ALIGNED(16) \ + ranges2[16] = "\000\040\177\177"; \ + int found2; \ + buf = findchar_fast(buf, buf_end, ranges2, 4, &found2); \ + if (!found2) { \ + CHECK_EOF(); \ + } \ + while (1) { \ + if (*buf == ' ') { \ + break; \ + } else if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { \ + if ((unsigned char)*buf < '\040' || \ + *buf == '\177') \ + { \ + *ret = -1; \ + return NULL; \ + } \ + } \ + ++buf; \ + CHECK_EOF(); \ + } \ + tok = tok_start; \ + toklen = buf - tok_start; \ + } while (0) + +static const char *token_char_map = + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\1\0\1\1\1\1\1\0\0\1\1\0\1\1\0\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0" + "\0\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\1\1" + "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\1\0\1\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; + +static const char * +findchar_fast(const char *buf, const char *buf_end, const char *ranges, + size_t ranges_size, int *found) { + *found = 0; +#if __SSE4_2__ + if (likely(buf_end - buf >= 16)) { + __m128i ranges16 = _mm_loadu_si128((const __m128i *)ranges); + + size_t left = (buf_end - buf) & ~15; + do { + __m128i b16 = _mm_loadu_si128((const __m128i *)buf); + int r = _mm_cmpestri(ranges16, ranges_size, b16, 16, + _SIDD_LEAST_SIGNIFICANT | + _SIDD_CMP_RANGES | + _SIDD_UBYTE_OPS); + if (unlikely(r != 16)) { + buf += r; + *found = 1; + break; + } + buf += 16; + left -= 16; + } while (likely(left != 0)); + } +#else + /* suppress unused parameter warning */ + (void)buf_end; + (void)ranges; + (void)ranges_size; +#endif + return buf; +} + +static const char * +get_token_to_eol(const char *buf, const char *buf_end, const char **token, + size_t *token_len, int *ret) { + const char *token_start = buf; + +#ifdef __SSE4_2__ + static const char ALIGNED(16) + ranges1[16] = "\0\010" /* allow HT */ + "\012\037" /* allow SP and up to but not including + DEL */ + "\177\177"; /* allow chars w. MSB set */ + int found; + buf = findchar_fast(buf, buf_end, ranges1, 6, &found); + if (found) + goto FOUND_CTL; +#else + /* find non-printable char within the next 8 bytes, this is the hottest + * code; manually inlined */ + while (likely(buf_end - buf >= 8)) { +#define DOIT() \ + do { \ + if (unlikely(!IS_PRINTABLE_ASCII(*buf))) \ + goto NonPrintable; \ + ++buf; \ + } while (0) + DOIT(); + DOIT(); + DOIT(); + DOIT(); + DOIT(); + DOIT(); + DOIT(); + DOIT(); +#undef DOIT + continue; + NonPrintable: + if ((likely((unsigned char)*buf < '\040') && + likely(*buf != '\011')) || + unlikely(*buf == '\177')) + { + goto FOUND_CTL; + } + ++buf; + } +#endif + for (;; ++buf) { + CHECK_EOF(); + if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { + if ((likely((unsigned char)*buf < '\040') && + likely(*buf != '\011')) || + unlikely(*buf == '\177')) + { + goto FOUND_CTL; + } + } + } +FOUND_CTL: + if (likely(*buf == '\015')) { + ++buf; + EXPECT_CHAR('\012'); + *token_len = buf - 2 - token_start; + } else if (*buf == '\012') { + *token_len = buf - token_start; + ++buf; + } else { + *ret = -1; + return NULL; + } + *token = token_start; + + return buf; +} + +static const char * +is_complete(const char *buf, const char *buf_end, size_t last_len, int *ret) { + int ret_cnt = 0; + buf = last_len < 3 ? buf : buf + last_len - 3; + + while (1) { + CHECK_EOF(); + if (*buf == '\015') { + ++buf; + CHECK_EOF(); + EXPECT_CHAR('\012'); + ++ret_cnt; + } else if (*buf == '\012') { + ++buf; + ++ret_cnt; + } else { + ++buf; + ret_cnt = 0; + } + if (ret_cnt == 2) { + return buf; + } + } + + *ret = -2; + return NULL; +} + +#define PARSE_INT(valp_, mul_) \ + if (*buf < '0' || '9' < *buf) { \ + buf++; \ + *ret = -1; \ + return NULL; \ + } \ + *(valp_) = (mul_) * (*buf++ - '0'); + +#define PARSE_INT_3(valp_) \ + do { \ + int res_ = 0; \ + PARSE_INT(&res_, 100) \ + *valp_ = res_; \ + PARSE_INT(&res_, 10) \ + *valp_ += res_; \ + PARSE_INT(&res_, 1) \ + *valp_ += res_; \ + } while (0) + +/* returned pointer is always within [buf, buf_end), or null */ +static const char * +parse_token(const char *buf, const char *buf_end, const char **token, + size_t *token_len, char next_char, int *ret) { + /* We use pcmpestri to detect non-token characters. This instruction can + * take no more than eight character ranges (8*2*8=128 bits that is the + * size of a SSE register). Due to this restriction, characters `|` and + * `~` are handled in the slow loop. */ + static const char ALIGNED(16) ranges[] = "\x00 " /* control chars and up + to SP */ + "\"\"" /* 0x22 */ + "()" /* 0x28,0x29 */ + ",," /* 0x2c */ + "//" /* 0x2f */ + ":@" /* 0x3a-0x40 */ + "[]" /* 0x5b-0x5d */ + "{\xff"; /* 0x7b-0xff */ + const char *buf_start = buf; + int found; + buf = findchar_fast(buf, buf_end, ranges, sizeof(ranges) - 1, &found); + if (!found) { + CHECK_EOF(); + } + while (1) { + if (*buf == next_char) { + break; + } else if (!token_char_map[(unsigned char)*buf]) { + *ret = -1; + return NULL; + } + ++buf; + CHECK_EOF(); + } + *token = buf_start; + *token_len = buf - buf_start; + return buf; +} + +/* returned pointer is always within [buf, buf_end), or null */ +static const char * +parse_http_version(const char *buf, const char *buf_end, int *minor_version, + int *ret) { + /* we want at least [HTTP/1.<two chars>] to try to parse */ + if (buf_end - buf < 9) { + *ret = -2; + return NULL; + } + EXPECT_CHAR_NO_CHECK('H'); + EXPECT_CHAR_NO_CHECK('T'); + EXPECT_CHAR_NO_CHECK('T'); + EXPECT_CHAR_NO_CHECK('P'); + EXPECT_CHAR_NO_CHECK('/'); + EXPECT_CHAR_NO_CHECK('1'); + EXPECT_CHAR_NO_CHECK('.'); + PARSE_INT(minor_version, 1); + return buf; +} + +static const char * +parse_headers(const char *buf, const char *buf_end, struct phr_header *headers, + size_t *num_headers, size_t max_headers, int *ret) { + for (;; ++*num_headers) { + CHECK_EOF(); + if (*buf == '\015') { + ++buf; + EXPECT_CHAR('\012'); + break; + } else if (*buf == '\012') { + ++buf; + break; + } + if (*num_headers == max_headers) { + *ret = -1; + return NULL; + } + if (!(*num_headers != 0 && (*buf == ' ' || *buf == '\t'))) { + /* parsing name, but do not discard SP before colon, see + * http://www.mozilla.org/security/announce/2006/mfsa2006-33.html + */ + if ((buf = parse_token(buf, buf_end, + &headers[*num_headers].name, + &headers[*num_headers].name_len, + ':', ret)) == NULL) + { + return NULL; + } + if (headers[*num_headers].name_len == 0) { + *ret = -1; + return NULL; + } + ++buf; + for (;; ++buf) { + CHECK_EOF(); + if (!(*buf == ' ' || *buf == '\t')) { + break; + } + } + } else { + headers[*num_headers].name = NULL; + headers[*num_headers].name_len = 0; + } + const char *value; + size_t value_len; + if ((buf = get_token_to_eol(buf, buf_end, &value, &value_len, + ret)) == NULL) + { + return NULL; + } + /* remove trailing SPs and HTABs */ + const char *value_end = value + value_len; + for (; value_end != value; --value_end) { + const char c = *(value_end - 1); + if (!(c == ' ' || c == '\t')) { + break; + } + } + headers[*num_headers].value = value; + headers[*num_headers].value_len = value_end - value; + } + return buf; +} + +static const char * +parse_request(const char *buf, const char *buf_end, const char **method, + size_t *method_len, const char **path, size_t *path_len, + int *minor_version, struct phr_header *headers, + size_t *num_headers, size_t max_headers, int *ret) { + /* skip first empty line (some clients add CRLF after POST content) */ + CHECK_EOF(); + if (*buf == '\015') { + ++buf; + EXPECT_CHAR('\012'); + } else if (*buf == '\012') { + ++buf; + } + + /* parse request line */ + if ((buf = parse_token(buf, buf_end, method, method_len, ' ', ret)) == + NULL) + { + return NULL; + } + do { + ++buf; + CHECK_EOF(); + } while (*buf == ' '); + ADVANCE_TOKEN(*path, *path_len); + do { + ++buf; + CHECK_EOF(); + } while (*buf == ' '); + if (*method_len == 0 || *path_len == 0) { + *ret = -1; + return NULL; + } + if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == + NULL) + { + return NULL; + } + if (*buf == '\015') { + ++buf; + EXPECT_CHAR('\012'); + } else if (*buf == '\012') { + ++buf; + } else { + *ret = -1; + return NULL; + } + + return parse_headers(buf, buf_end, headers, num_headers, max_headers, + ret); +} + +int +phr_parse_request(const char *buf_start, size_t len, const char **method, + size_t *method_len, const char **path, size_t *path_len, + int *minor_version, struct phr_header *headers, + size_t *num_headers, size_t last_len) { + const char *buf = buf_start, *buf_end = buf_start + len; + size_t max_headers = *num_headers; + int r = -1; + + *method = NULL; + *method_len = 0; + *path = NULL; + *path_len = 0; + *minor_version = -1; + *num_headers = 0; + + /* if last_len != 0, check if the request is complete (a fast + countermeasure againt slowloris */ + if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { + return r; + } + + if ((buf = parse_request(buf, buf_end, method, method_len, path, + path_len, minor_version, headers, num_headers, + max_headers, &r)) == NULL) + { + return r; + } + + return (int)(buf - buf_start); +} + +static const char * +parse_response(const char *buf, const char *buf_end, int *minor_version, + int *status, const char **msg, size_t *msg_len, + struct phr_header *headers, size_t *num_headers, + size_t max_headers, int *ret) { + /* parse "HTTP/1.x" */ + if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == + NULL) + { + return NULL; + } + /* skip space */ + if (*buf != ' ') { + *ret = -1; + return NULL; + } + do { + ++buf; + CHECK_EOF(); + } while (*buf == ' '); + /* parse status code, we want at least [:digit:][:digit:][:digit:]<other + * char> to try to parse */ + if (buf_end - buf < 4) { + *ret = -2; + return NULL; + } + PARSE_INT_3(status); + + /* get message including preceding space */ + if ((buf = get_token_to_eol(buf, buf_end, msg, msg_len, ret)) == NULL) { + return NULL; + } + if (*msg_len == 0) { + /* ok */ + } else if (**msg == ' ') { + /* Remove preceding space. Successful return from + * `get_token_to_eol` guarantees that we would hit something + * other than SP before running past the end of the given + * buffer. */ + do { + ++*msg; + --*msg_len; + } while (**msg == ' '); + } else { + /* garbage found after status code */ + *ret = -1; + return NULL; + } + + return parse_headers(buf, buf_end, headers, num_headers, max_headers, + ret); +} + +int +phr_parse_response(const char *buf_start, size_t len, int *minor_version, + int *status, const char **msg, size_t *msg_len, + struct phr_header *headers, size_t *num_headers, + size_t last_len) { + const char *buf = buf_start, *buf_end = buf + len; + size_t max_headers = *num_headers; + int r; + + *minor_version = -1; + *status = 0; + *msg = NULL; + *msg_len = 0; + *num_headers = 0; + + /* if last_len != 0, check if the response is complete (a fast + countermeasure against slowloris */ + if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { + return r; + } + + if ((buf = parse_response(buf, buf_end, minor_version, status, msg, + msg_len, headers, num_headers, max_headers, + &r)) == NULL) + { + return r; + } + + return (int)(buf - buf_start); +} + +int +phr_parse_headers(const char *buf_start, size_t len, struct phr_header *headers, + size_t *num_headers, size_t last_len) { + const char *buf = buf_start, *buf_end = buf + len; + size_t max_headers = *num_headers; + int r; + + *num_headers = 0; + + /* if last_len != 0, check if the response is complete (a fast + countermeasure against slowloris */ + if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { + return r; + } + + if ((buf = parse_headers(buf, buf_end, headers, num_headers, + max_headers, &r)) == NULL) + { + return r; + } + + return (int)(buf - buf_start); +} + +enum { + CHUNKED_IN_CHUNK_SIZE, + CHUNKED_IN_CHUNK_EXT, + CHUNKED_IN_CHUNK_DATA, + CHUNKED_IN_CHUNK_CRLF, + CHUNKED_IN_TRAILERS_LINE_HEAD, + CHUNKED_IN_TRAILERS_LINE_MIDDLE +}; + +static int +decode_hex(int ch) { + if ('0' <= ch && ch <= '9') { + return ch - '0'; + } else if ('A' <= ch && ch <= 'F') { + return ch - 'A' + 0xa; + } else if ('a' <= ch && ch <= 'f') { + return ch - 'a' + 0xa; + } else { + return -1; + } +} + +ssize_t +phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, + size_t *_bufsz) { + size_t dst = 0, src = 0, bufsz = *_bufsz; + ssize_t ret = -2; /* incomplete */ + + while (1) { + switch (decoder->_state) { + case CHUNKED_IN_CHUNK_SIZE: + for (;; ++src) { + int v; + if (src == bufsz) + goto Exit; + if ((v = decode_hex(buf[src])) == -1) { + if (decoder->_hex_count == 0) { + ret = -1; + goto Exit; + } + break; + } + if (decoder->_hex_count == sizeof(size_t) * 2) { + ret = -1; + goto Exit; + } + decoder->bytes_left_in_chunk = + decoder->bytes_left_in_chunk * 16 + v; + ++decoder->_hex_count; + } + decoder->_hex_count = 0; + decoder->_state = CHUNKED_IN_CHUNK_EXT; + /* fallthru */ + case CHUNKED_IN_CHUNK_EXT: + /* RFC 7230 A.2 "Line folding in chunk extensions is + * disallowed" */ + for (;; ++src) { + if (src == bufsz) + goto Exit; + if (buf[src] == '\012') + break; + } + ++src; + if (decoder->bytes_left_in_chunk == 0) { + if (decoder->consume_trailer) { + decoder->_state = + CHUNKED_IN_TRAILERS_LINE_HEAD; + break; + } else { + goto Complete; + } + } + decoder->_state = CHUNKED_IN_CHUNK_DATA; + /* fallthru */ + case CHUNKED_IN_CHUNK_DATA: { + size_t avail = bufsz - src; + if (avail < decoder->bytes_left_in_chunk) { + if (dst != src) + memmove(buf + dst, buf + src, avail); + src += avail; + dst += avail; + decoder->bytes_left_in_chunk -= avail; + goto Exit; + } + if (dst != src) + memmove(buf + dst, buf + src, + decoder->bytes_left_in_chunk); + src += decoder->bytes_left_in_chunk; + dst += decoder->bytes_left_in_chunk; + decoder->bytes_left_in_chunk = 0; + decoder->_state = CHUNKED_IN_CHUNK_CRLF; + } + /* fallthru */ + case CHUNKED_IN_CHUNK_CRLF: + for (;; ++src) { + if (src == bufsz) + goto Exit; + if (buf[src] != '\015') + break; + } + if (buf[src] != '\012') { + ret = -1; + goto Exit; + } + ++src; + decoder->_state = CHUNKED_IN_CHUNK_SIZE; + break; + case CHUNKED_IN_TRAILERS_LINE_HEAD: + for (;; ++src) { + if (src == bufsz) + goto Exit; + if (buf[src] != '\015') + break; + } + if (buf[src++] == '\012') + goto Complete; + decoder->_state = CHUNKED_IN_TRAILERS_LINE_MIDDLE; + /* fallthru */ + case CHUNKED_IN_TRAILERS_LINE_MIDDLE: + for (;; ++src) { + if (src == bufsz) + goto Exit; + if (buf[src] == '\012') + break; + } + ++src; + decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD; + break; + default: + assert(!"decoder is corrupt"); + } + } + +Complete: + ret = bufsz - src; +Exit: + if (dst != src) + memmove(buf + dst, buf + src, bufsz - src); + *_bufsz = dst; + return ret; +} + +int +phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder) { + return decoder->_state == CHUNKED_IN_CHUNK_DATA; +} + +#undef CHECK_EOF +#undef EXPECT_CHAR +#undef ADVANCE_TOKEN diff --git a/lib/isc/picohttpparser.h b/lib/isc/picohttpparser.h new file mode 100644 index 0000000..0657cb2 --- /dev/null +++ b/lib/isc/picohttpparser.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase, + * Shigeo Mitsunari + * + * SPDX-License-Identifier: MIT + * + * The software is licensed under either the MIT License (below) or the Perl + * license. + * + * 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. + */ + +#ifndef picohttpparser_h +#define picohttpparser_h + +#include <sys/types.h> + +#ifdef _MSC_VER +#define ssize_t intptr_t +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* contains name and value of a header (name == NULL if is a continuing line + * of a multiline header */ +struct phr_header { + const char *name; + size_t name_len; + const char *value; + size_t value_len; +}; + +/* returns number of bytes consumed if successful, -2 if request is partial, + * -1 if failed */ +int +phr_parse_request(const char *buf, size_t len, const char **method, + size_t *method_len, const char **path, size_t *path_len, + int *minor_version, struct phr_header *headers, + size_t *num_headers, size_t last_len); + +/* ditto */ +int +phr_parse_response(const char *_buf, size_t len, int *minor_version, + int *status, const char **msg, size_t *msg_len, + struct phr_header *headers, size_t *num_headers, + size_t last_len); + +/* ditto */ +int +phr_parse_headers(const char *buf, size_t len, struct phr_header *headers, + size_t *num_headers, size_t last_len); + +/* should be zero-filled before start */ +struct phr_chunked_decoder { + size_t bytes_left_in_chunk; /* number of bytes left in current chunk */ + char consume_trailer; /* if trailing headers should be consumed */ + char _hex_count; + char _state; +}; + +/* the function rewrites the buffer given as (buf, bufsz) removing the chunked- + * encoding headers. When the function returns without an error, bufsz is + * updated to the length of the decoded data available. Applications should + * repeatedly call the function while it returns -2 (incomplete) every time + * supplying newly arrived data. If the end of the chunked-encoded data is + * found, the function returns a non-negative number indicating the number of + * octets left undecoded, that starts from the offset returned by `*bufsz`. + * Returns -1 on error. + */ +ssize_t +phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, + size_t *bufsz); + +/* returns if the chunked decoder is in middle of chunked data */ +int +phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder); + +#ifdef __cplusplus +} +#endif + +#endif 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/quota.c b/lib/isc/quota.c new file mode 100644 index 0000000..5465db5 --- /dev/null +++ b/lib/isc/quota.c @@ -0,0 +1,202 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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); + ISC_LINK_INIT(quota, link); + 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) { + uint_fast32_t used; + + /* + * 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; + } + } + + used = atomic_fetch_sub_release("a->used, 1); + INSIST(used > 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..54c0383 --- /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; + } + + if (parent->r == node) { + parent->r = child; + } else { + INSIST(parent->l == node); + parent->l = child; + } + + isc_mem_put(radix->mctx, node, sizeof(*node)); + radix->num_active_node--; +} diff --git a/lib/isc/random.c b/lib/isc/random.c new file mode 100644 index 0000000..7eead66 --- /dev/null +++ b/lib/isc/random.c @@ -0,0 +1,206 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/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. + */ + +/* + * 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/>. + */ + +/* + * 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. + */ +static 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); +} + +static 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..84a848a --- /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, + true); + 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/resource.c b/lib/isc/resource.c new file mode 100644 index 0000000..d83762c --- /dev/null +++ b/lib/isc/resource.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. + */ + +#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/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/result.c b/lib/isc/result.c new file mode 100644 index 0000000..edbc8fd --- /dev/null +++ b/lib/isc/result.c @@ -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. + */ + +/*! \file */ + +#include <stddef.h> +#include <stdlib.h> + +#include <isc/once.h> +#include <isc/util.h> + +static const char *description[ISC_R_NRESULTS] = { + [ISC_R_SUCCESS] = "success", + [ISC_R_NOMEMORY] = "out of memory", + [ISC_R_TIMEDOUT] = "timed out", + [ISC_R_NOTHREADS] = "no available threads", + [ISC_R_ADDRNOTAVAIL] = "address not available", + [ISC_R_ADDRINUSE] = "address in use", + [ISC_R_NOPERM] = "permission denied", + [ISC_R_NOCONN] = "no pending connections", + [ISC_R_NETUNREACH] = "network unreachable", + [ISC_R_HOSTUNREACH] = "host unreachable", + [ISC_R_NETDOWN] = "network down", + [ISC_R_HOSTDOWN] = "host down", + [ISC_R_CONNREFUSED] = "connection refused", + [ISC_R_NORESOURCES] = "not enough free resources", + [ISC_R_EOF] = "end of file", + [ISC_R_BOUND] = "socket already bound", + [ISC_R_RELOAD] = "reload", + [ISC_R_LOCKBUSY] = "lock busy", + [ISC_R_EXISTS] = "already exists", + [ISC_R_NOSPACE] = "ran out of space", + [ISC_R_CANCELED] = "operation canceled", + [ISC_R_NOTBOUND] = "socket is not bound", + [ISC_R_SHUTTINGDOWN] = "shutting down", + [ISC_R_NOTFOUND] = "not found", + [ISC_R_UNEXPECTEDEND] = "unexpected end of input", + [ISC_R_FAILURE] = "failure", + [ISC_R_IOERROR] = "I/O error", + [ISC_R_NOTIMPLEMENTED] = "not implemented", + [ISC_R_UNBALANCED] = "unbalanced parentheses", + [ISC_R_NOMORE] = "no more", + [ISC_R_INVALIDFILE] = "invalid file", + [ISC_R_BADBASE64] = "bad base64 encoding", + [ISC_R_UNEXPECTEDTOKEN] = "unexpected token", + [ISC_R_QUOTA] = "quota reached", + [ISC_R_UNEXPECTED] = "unexpected error", + [ISC_R_ALREADYRUNNING] = "already running", + [ISC_R_IGNORE] = "ignore", + [ISC_R_MASKNONCONTIG] = "address mask not contiguous", + [ISC_R_FILENOTFOUND] = "file not found", + [ISC_R_FILEEXISTS] = "file already exists", + [ISC_R_NOTCONNECTED] = "socket is not connected", + [ISC_R_RANGE] = "out of range", + [ISC_R_NOENTROPY] = "out of entropy", + [ISC_R_MULTICAST] = "invalid use of multicast address", + [ISC_R_NOTFILE] = "not a file", + [ISC_R_NOTDIRECTORY] = "not a directory", + [ISC_R_EMPTY] = "queue is empty", + [ISC_R_FAMILYMISMATCH] = "address family mismatch", + [ISC_R_FAMILYNOSUPPORT] = "address family not supported", + [ISC_R_BADHEX] = "bad hex encoding", + [ISC_R_TOOMANYOPENFILES] = "too many open files", + [ISC_R_NOTBLOCKING] = "not blocking", + [ISC_R_UNBALANCEDQUOTES] = "unbalanced quotes", + [ISC_R_INPROGRESS] = "operation in progress", + [ISC_R_CONNECTIONRESET] = "connection reset", + [ISC_R_SOFTQUOTA] = "soft quota reached", + [ISC_R_BADNUMBER] = "not a valid number", + [ISC_R_DISABLED] = "disabled", + [ISC_R_MAXSIZE] = "max size", + [ISC_R_BADADDRESSFORM] = "invalid address format", + [ISC_R_BADBASE32] = "bad base32 encoding", + [ISC_R_UNSET] = "unset", + [ISC_R_MULTIPLE] = "multiple", + [ISC_R_WOULDBLOCK] = "would block", + [ISC_R_COMPLETE] = "complete", + [ISC_R_CRYPTOFAILURE] = "crypto failure", + [ISC_R_DISCQUOTA] = "disc quota", + [ISC_R_DISCFULL] = "disc full", + [ISC_R_DEFAULT] = "default", + [ISC_R_IPV4PREFIX] = "IPv4 prefix", + [ISC_R_TLSERROR] = "TLS error", + [ISC_R_TLSBADPEERCERT] = "TLS peer certificate verification failed", + [ISC_R_HTTP2ALPNERROR] = "ALPN for HTTP/2 failed", + [ISC_R_DOTALPNERROR] = "ALPN for DoT failed", + [ISC_R_INVALIDPROTO] = "invalid protocol", + + [DNS_R_LABELTOOLONG] = "label too long", + [DNS_R_BADESCAPE] = "bad escape", + [DNS_R_EMPTYLABEL] = "empty label", + [DNS_R_BADDOTTEDQUAD] = "bad dotted quad", + [DNS_R_INVALIDNS] = "invalid NS owner name (wildcard)", + [DNS_R_UNKNOWN] = "unknown class/type", + [DNS_R_BADLABELTYPE] = "bad label type", + [DNS_R_BADPOINTER] = "bad compression pointer", + [DNS_R_TOOMANYHOPS] = "too many hops", + [DNS_R_DISALLOWED] = "disallowed (by application policy)", + [DNS_R_EXTRATOKEN] = "extra input text", + [DNS_R_EXTRADATA] = "extra input data", + [DNS_R_TEXTTOOLONG] = "text too long", + [DNS_R_NOTZONETOP] = "not at top of zone", + [DNS_R_SYNTAX] = "syntax error", + [DNS_R_BADCKSUM] = "bad checksum", + [DNS_R_BADAAAA] = "bad IPv6 address", + [DNS_R_NOOWNER] = "no owner", + [DNS_R_NOTTL] = "no ttl", + [DNS_R_BADCLASS] = "bad class", + [DNS_R_NAMETOOLONG] = "name too long", + [DNS_R_PARTIALMATCH] = "partial match", + [DNS_R_NEWORIGIN] = "new origin", + [DNS_R_UNCHANGED] = "unchanged", + [DNS_R_BADTTL] = "bad ttl", + [DNS_R_NOREDATA] = "more data needed/to be rendered", + [DNS_R_CONTINUE] = "continue", + [DNS_R_DELEGATION] = "delegation", + [DNS_R_GLUE] = "glue", + [DNS_R_DNAME] = "dname", + [DNS_R_CNAME] = "cname", + [DNS_R_BADDB] = "bad database", + [DNS_R_ZONECUT] = "zonecut", + [DNS_R_BADZONE] = "bad zone", + [DNS_R_MOREDATA] = "more data", + [DNS_R_UPTODATE] = "up to date", + [DNS_R_TSIGVERIFYFAILURE] = "tsig verify failure", + [DNS_R_TSIGERRORSET] = "tsig indicates error", + [DNS_R_SIGINVALID] = "RRSIG failed to verify", + [DNS_R_SIGEXPIRED] = "RRSIG has expired", + [DNS_R_SIGFUTURE] = "RRSIG validity period has not begun", + [DNS_R_KEYUNAUTHORIZED] = "key is unauthorized to sign data", + [DNS_R_INVALIDTIME] = "invalid time", + [DNS_R_EXPECTEDTSIG] = "expected a TSIG or SIG(0)", + [DNS_R_UNEXPECTEDTSIG] = "did not expect a TSIG or SIG(0)", + [DNS_R_INVALIDTKEY] = "TKEY is unacceptable", + [DNS_R_HINT] = "hint", + [DNS_R_DROP] = "drop", + [DNS_R_NOTLOADED] = "zone not loaded", + [DNS_R_NCACHENXDOMAIN] = "ncache nxdomain", + [DNS_R_NCACHENXRRSET] = "ncache nxrrset", + [DNS_R_WAIT] = "wait", + [DNS_R_NOTVERIFIEDYET] = "not verified yet", + [DNS_R_NOIDENTITY] = "no identity", + [DNS_R_NOJOURNAL] = "no journal", + [DNS_R_ALIAS] = "alias", + [DNS_R_USETCP] = "use TCP", + [DNS_R_NOVALIDSIG] = "no valid RRSIG", + [DNS_R_NOVALIDNSEC] = "no valid NSEC", + [DNS_R_NOTINSECURE] = "insecurity proof failed", + [DNS_R_UNKNOWNSERVICE] = "unknown service", + [DNS_R_RECOVERABLE] = "recoverable error occurred", + [DNS_R_UNKNOWNOPT] = "unknown opt attribute record", + [DNS_R_UNEXPECTEDID] = "unexpected message id", + [DNS_R_SEENINCLUDE] = "seen include file", + [DNS_R_NOTEXACT] = "not exact", + [DNS_R_BLACKHOLED] = "address blackholed", + [DNS_R_BADALG] = "bad algorithm", + [DNS_R_METATYPE] = "invalid use of a meta type", + [DNS_R_CNAMEANDOTHER] = "CNAME and other data", + [DNS_R_SINGLETON] = "multiple RRs of singleton type", + [DNS_R_HINTNXRRSET] = "hint nxrrset", + [DNS_R_NOMASTERFILE] = "no master file configured", + [DNS_R_UNKNOWNPROTO] = "unknown protocol", + [DNS_R_CLOCKSKEW] = "clocks are unsynchronized", + [DNS_R_BADIXFR] = "IXFR failed", + [DNS_R_NOTAUTHORITATIVE] = "not authoritative", + [DNS_R_NOVALIDKEY] = "no valid KEY", + [DNS_R_OBSOLETE] = "obsolete", + [DNS_R_FROZEN] = "already frozen", + [DNS_R_UNKNOWNFLAG] = "unknown flag", + [DNS_R_EXPECTEDRESPONSE] = "expected a response", + [DNS_R_NOVALIDDS] = "no valid DS", + [DNS_R_NSISADDRESS] = "NS is an address", + [DNS_R_REMOTEFORMERR] = "received FORMERR", + [DNS_R_TRUNCATEDTCP] = "truncated TCP response", + [DNS_R_LAME] = "lame server detected", + [DNS_R_UNEXPECTEDRCODE] = "unexpected RCODE", + [DNS_R_UNEXPECTEDOPCODE] = "unexpected OPCODE", + [DNS_R_CHASEDSSERVERS] = "chase DS servers", + [DNS_R_EMPTYNAME] = "empty name", + [DNS_R_EMPTYWILD] = "empty wild", + [DNS_R_BADBITMAP] = "bad bitmap", + [DNS_R_FROMWILDCARD] = "from wildcard", + [DNS_R_BADOWNERNAME] = "bad owner name (check-names)", + [DNS_R_BADNAME] = "bad name (check-names)", + [DNS_R_DYNAMIC] = "dynamic zone", + [DNS_R_UNKNOWNCOMMAND] = "unknown command", + [DNS_R_MUSTBESECURE] = "must-be-secure", + [DNS_R_COVERINGNSEC] = "covering NSEC record returned", + [DNS_R_MXISADDRESS] = "MX is an address", + [DNS_R_DUPLICATE] = "duplicate query", + [DNS_R_INVALIDNSEC3] = "invalid NSEC3 owner name (wildcard)", + [DNS_R_NOTPRIMARY] = "not primary", + [DNS_R_BROKENCHAIN] = "broken trust chain", + [DNS_R_EXPIRED] = "expired", + [DNS_R_NOTDYNAMIC] = "not dynamic", + [DNS_R_BADEUI] = "bad EUI", + [DNS_R_NTACOVERED] = "covered by negative trust anchor", + [DNS_R_BADCDS] = "bad CDS", + [DNS_R_BADCDNSKEY] = "bad CDNSKEY", + [DNS_R_OPTERR] = "malformed OPT option", + [DNS_R_BADDNSTAP] = "malformed DNSTAP data", + [DNS_R_BADTSIG] = "TSIG in wrong location", + [DNS_R_BADSIG0] = "SIG(0) in wrong location", + [DNS_R_TOOMANYRECORDS] = "too many records", + [DNS_R_VERIFYFAILURE] = "verify failure", + [DNS_R_ATZONETOP] = "at top of zone", + [DNS_R_NOKEYMATCH] = "no matching key found", + [DNS_R_TOOMANYKEYS] = "too many keys matching", + [DNS_R_KEYNOTACTIVE] = "key is not actively signing", + [DNS_R_NSEC3ITERRANGE] = "NSEC3 iterations out of range", + [DNS_R_NSEC3SALTRANGE] = "NSEC3 salt length too high", + [DNS_R_NSEC3BADALG] = "cannot use NSEC3 with key algorithm", + [DNS_R_NSEC3RESALT] = "NSEC3 resalt", + [DNS_R_INCONSISTENTRR] = "inconsistent resource record", + [DNS_R_NOALPN] = "no ALPN", + + [DST_R_UNSUPPORTEDALG] = "algorithm is unsupported", + [DST_R_CRYPTOFAILURE] = "crypto failure", + [DST_R_NOCRYPTO] = "built with no crypto support", + [DST_R_NULLKEY] = "illegal operation for a null key", + [DST_R_INVALIDPUBLICKEY] = "public key is invalid", + [DST_R_INVALIDPRIVATEKEY] = "private key is invalid", + [DST_R_WRITEERROR] = "error occurred writing key to disk", + [DST_R_INVALIDPARAM] = "invalid algorithm specific parameter", + [DST_R_SIGNFAILURE] = "sign failure", + [DST_R_VERIFYFAILURE] = "verify failure", + [DST_R_NOTPUBLICKEY] = "not a public key", + [DST_R_NOTPRIVATEKEY] = "not a private key", + [DST_R_KEYCANNOTCOMPUTESECRET] = "not a key that can compute a secret", + [DST_R_COMPUTESECRETFAILURE] = "failure computing a shared secret", + [DST_R_NORANDOMNESS] = "no randomness available", + [DST_R_BADKEYTYPE] = "bad key type", + [DST_R_NOENGINE] = "no engine", + [DST_R_EXTERNALKEY] = "illegal operation for an external key", + + [DNS_R_NOERROR] = "NOERROR", + [DNS_R_FORMERR] = "FORMERR", + [DNS_R_SERVFAIL] = "SERVFAIL", + [DNS_R_NXDOMAIN] = "NXDOMAIN", + [DNS_R_NOTIMP] = "NOTIMP", + [DNS_R_REFUSED] = "REFUSED", + [DNS_R_YXDOMAIN] = "YXDOMAIN", + [DNS_R_YXRRSET] = "YXRRSET", + [DNS_R_NXRRSET] = "NXRRSET", + [DNS_R_NOTAUTH] = "NOTAUTH", + [DNS_R_NOTZONE] = "NOTZONE", + [DNS_R_RCODE11] = "<rcode 11>", + [DNS_R_RCODE12] = "<rcode 12>", + [DNS_R_RCODE13] = "<rcode 13>", + [DNS_R_RCODE14] = "<rcode 14>", + [DNS_R_RCODE15] = "<rcode 15>", + [DNS_R_BADVERS] = "BADVERS", + [DNS_R_BADCOOKIE] = "BADCOOKIE", + + [ISCCC_R_UNKNOWNVERSION] = "unknown version", + [ISCCC_R_SYNTAX] = "syntax error", + [ISCCC_R_BADAUTH] = "bad auth", + [ISCCC_R_EXPIRED] = "expired", + [ISCCC_R_CLOCKSKEW] = "clock skew", + [ISCCC_R_DUPLICATE] = "duplicate", + [ISCCC_R_MAXDEPTH] = "max depth", +}; + +static const char *identifier[ISC_R_NRESULTS] = { + [ISC_R_SUCCESS] = "ISC_R_SUCCESS", + [ISC_R_NOMEMORY] = "ISC_R_NOMEMORY", + [ISC_R_TIMEDOUT] = "ISC_R_TIMEDOUT", + [ISC_R_NOTHREADS] = "ISC_R_NOTHREADS", + [ISC_R_ADDRNOTAVAIL] = "ISC_R_ADDRNOTAVAIL", + [ISC_R_ADDRINUSE] = "ISC_R_ADDRINUSE", + [ISC_R_NOPERM] = "ISC_R_NOPERM", + [ISC_R_NOCONN] = "ISC_R_NOCONN", + [ISC_R_NETUNREACH] = "ISC_R_NETUNREACH", + [ISC_R_HOSTUNREACH] = "ISC_R_HOSTUNREACH", + [ISC_R_NETDOWN] = "ISC_R_NETDOWN", + [ISC_R_HOSTDOWN] = "ISC_R_HOSTDOWN", + [ISC_R_CONNREFUSED] = "ISC_R_CONNREFUSED", + [ISC_R_NORESOURCES] = "ISC_R_NORESOURCES", + [ISC_R_EOF] = "ISC_R_EOF", + [ISC_R_BOUND] = "ISC_R_BOUND", + [ISC_R_RELOAD] = "ISC_R_RELOAD", + [ISC_R_LOCKBUSY] = "ISC_R_LOCKBUSY", + [ISC_R_EXISTS] = "ISC_R_EXISTS", + [ISC_R_NOSPACE] = "ISC_R_NOSPACE", + [ISC_R_CANCELED] = "ISC_R_CANCELED", + [ISC_R_NOTBOUND] = "ISC_R_NOTBOUND", + [ISC_R_SHUTTINGDOWN] = "ISC_R_SHUTTINGDOWN", + [ISC_R_NOTFOUND] = "ISC_R_NOTFOUND", + [ISC_R_UNEXPECTEDEND] = "ISC_R_UNEXPECTEDEND", + [ISC_R_FAILURE] = "ISC_R_FAILURE", + [ISC_R_IOERROR] = "ISC_R_IOERROR", + [ISC_R_NOTIMPLEMENTED] = "ISC_R_NOTIMPLEMENTED", + [ISC_R_UNBALANCED] = "ISC_R_UNBALANCED", + [ISC_R_NOMORE] = "ISC_R_NOMORE", + [ISC_R_INVALIDFILE] = "ISC_R_INVALIDFILE", + [ISC_R_BADBASE64] = "ISC_R_BADBASE64", + [ISC_R_UNEXPECTEDTOKEN] = "ISC_R_UNEXPECTEDTOKEN", + [ISC_R_QUOTA] = "ISC_R_QUOTA", + [ISC_R_UNEXPECTED] = "ISC_R_UNEXPECTED", + [ISC_R_ALREADYRUNNING] = "ISC_R_ALREADYRUNNING", + [ISC_R_IGNORE] = "ISC_R_IGNORE", + [ISC_R_MASKNONCONTIG] = "ISC_R_MASKNONCONTIG", + [ISC_R_FILENOTFOUND] = "ISC_R_FILENOTFOUND", + [ISC_R_FILEEXISTS] = "ISC_R_FILEEXISTS", + [ISC_R_NOTCONNECTED] = "ISC_R_NOTCONNECTED", + [ISC_R_RANGE] = "ISC_R_RANGE", + [ISC_R_NOENTROPY] = "ISC_R_NOENTROPY", + [ISC_R_MULTICAST] = "ISC_R_MULTICAST", + [ISC_R_NOTFILE] = "ISC_R_NOTFILE", + [ISC_R_NOTDIRECTORY] = "ISC_R_NOTDIRECTORY", + [ISC_R_EMPTY] = "ISC_R_EMPTY", + [ISC_R_FAMILYMISMATCH] = "ISC_R_FAMILYMISMATCH", + [ISC_R_FAMILYNOSUPPORT] = "ISC_R_FAMILYNOSUPPORT", + [ISC_R_BADHEX] = "ISC_R_BADHEX", + [ISC_R_TOOMANYOPENFILES] = "ISC_R_TOOMANYOPENFILES", + [ISC_R_NOTBLOCKING] = "ISC_R_NOTBLOCKING", + [ISC_R_UNBALANCEDQUOTES] = "ISC_R_UNBALANCEDQUOTES", + [ISC_R_INPROGRESS] = "ISC_R_INPROGRESS", + [ISC_R_CONNECTIONRESET] = "ISC_R_CONNECTIONRESET", + [ISC_R_SOFTQUOTA] = "ISC_R_SOFTQUOTA", + [ISC_R_BADNUMBER] = "ISC_R_BADNUMBER", + [ISC_R_DISABLED] = "ISC_R_DISABLED", + [ISC_R_MAXSIZE] = "ISC_R_MAXSIZE", + [ISC_R_BADADDRESSFORM] = "ISC_R_BADADDRESSFORM", + [ISC_R_BADBASE32] = "ISC_R_BADBASE32", + [ISC_R_UNSET] = "ISC_R_UNSET", + [ISC_R_MULTIPLE] = "ISC_R_MULTIPLE", + [ISC_R_WOULDBLOCK] = "ISC_R_WOULDBLOCK", + [ISC_R_COMPLETE] = "ISC_R_COMPLETE", + [ISC_R_CRYPTOFAILURE] = "ISC_R_CRYPTOFAILURE", + [ISC_R_DISCQUOTA] = "ISC_R_DISCQUOTA", + [ISC_R_DISCFULL] = "ISC_R_DISCFULL", + [ISC_R_DEFAULT] = "ISC_R_DEFAULT", + [ISC_R_IPV4PREFIX] = "ISC_R_IPV4PREFIX", + [ISC_R_TLSERROR] = "ISC_R_TLSERROR", + [ISC_R_TLSBADPEERCERT] = "ISC_R_TLSBADPEERCERT", + [ISC_R_HTTP2ALPNERROR] = "ISC_R_HTTP2ALPNERROR", + [ISC_R_DOTALPNERROR] = "ISC_R_DOTALPNERROR", + [DNS_R_LABELTOOLONG] = "DNS_R_LABELTOOLONG", + [DNS_R_BADESCAPE] = "DNS_R_BADESCAPE", + [DNS_R_EMPTYLABEL] = "DNS_R_EMPTYLABEL", + [DNS_R_BADDOTTEDQUAD] = "DNS_R_BADDOTTEDQUAD", + [DNS_R_INVALIDNS] = "DNS_R_INVALIDNS", + [DNS_R_UNKNOWN] = "DNS_R_UNKNOWN", + [DNS_R_BADLABELTYPE] = "DNS_R_BADLABELTYPE", + [DNS_R_BADPOINTER] = "DNS_R_BADPOINTER", + [DNS_R_TOOMANYHOPS] = "DNS_R_TOOMANYHOPS", + [DNS_R_DISALLOWED] = "DNS_R_DISALLOWED", + [DNS_R_EXTRATOKEN] = "DNS_R_EXTRATOKEN", + [DNS_R_EXTRADATA] = "DNS_R_EXTRADATA", + [DNS_R_TEXTTOOLONG] = "DNS_R_TEXTTOOLONG", + [DNS_R_NOTZONETOP] = "DNS_R_NOTZONETOP", + [DNS_R_SYNTAX] = "DNS_R_SYNTAX", + [DNS_R_BADCKSUM] = "DNS_R_BADCKSUM", + [DNS_R_BADAAAA] = "DNS_R_BADAAAA", + [DNS_R_NOOWNER] = "DNS_R_NOOWNER", + [DNS_R_NOTTL] = "DNS_R_NOTTL", + [DNS_R_BADCLASS] = "DNS_R_BADCLASS", + [DNS_R_NAMETOOLONG] = "DNS_R_NAMETOOLONG", + [DNS_R_PARTIALMATCH] = "DNS_R_PARTIALMATCH", + [DNS_R_NEWORIGIN] = "DNS_R_NEWORIGIN", + [DNS_R_UNCHANGED] = "DNS_R_UNCHANGED", + [DNS_R_BADTTL] = "DNS_R_BADTTL", + [DNS_R_NOREDATA] = "DNS_R_NOREDATA", + [DNS_R_CONTINUE] = "DNS_R_CONTINUE", + [DNS_R_DELEGATION] = "DNS_R_DELEGATION", + [DNS_R_GLUE] = "DNS_R_GLUE", + [DNS_R_DNAME] = "DNS_R_DNAME", + [DNS_R_CNAME] = "DNS_R_CNAME", + [DNS_R_BADDB] = "DNS_R_BADDB", + [DNS_R_ZONECUT] = "DNS_R_ZONECUT", + [DNS_R_BADZONE] = "DNS_R_BADZONE", + [DNS_R_MOREDATA] = "DNS_R_MOREDATA", + [DNS_R_UPTODATE] = "DNS_R_UPTODATE", + [DNS_R_TSIGVERIFYFAILURE] = "DNS_R_TSIGVERIFYFAILURE", + [DNS_R_TSIGERRORSET] = "DNS_R_TSIGERRORSET", + [DNS_R_SIGINVALID] = "DNS_R_SIGINVALID", + [DNS_R_SIGEXPIRED] = "DNS_R_SIGEXPIRED", + [DNS_R_SIGFUTURE] = "DNS_R_SIGFUTURE", + [DNS_R_KEYUNAUTHORIZED] = "DNS_R_KEYUNAUTHORIZED", + [DNS_R_INVALIDTIME] = "DNS_R_INVALIDTIME", + [DNS_R_EXPECTEDTSIG] = "DNS_R_EXPECTEDTSIG", + [DNS_R_UNEXPECTEDTSIG] = "DNS_R_UNEXPECTEDTSIG", + [DNS_R_INVALIDTKEY] = "DNS_R_INVALIDTKEY", + [DNS_R_HINT] = "DNS_R_HINT", + [DNS_R_DROP] = "DNS_R_DROP", + [DNS_R_NOTLOADED] = "DNS_R_NOTLOADED", + [DNS_R_NCACHENXDOMAIN] = "DNS_R_NCACHENXDOMAIN", + [DNS_R_NCACHENXRRSET] = "DNS_R_NCACHENXRRSET", + [DNS_R_WAIT] = "DNS_R_WAIT", + [DNS_R_NOTVERIFIEDYET] = "DNS_R_NOTVERIFIEDYET", + [DNS_R_NOIDENTITY] = "DNS_R_NOIDENTITY", + [DNS_R_NOJOURNAL] = "DNS_R_NOJOURNAL", + [DNS_R_ALIAS] = "DNS_R_ALIAS", + [DNS_R_USETCP] = "DNS_R_USETCP", + [DNS_R_NOVALIDSIG] = "DNS_R_NOVALIDSIG", + [DNS_R_NOVALIDNSEC] = "DNS_R_NOVALIDNSEC", + [DNS_R_NOTINSECURE] = "DNS_R_NOTINSECURE", + [DNS_R_UNKNOWNSERVICE] = "DNS_R_UNKNOWNSERVICE", + [DNS_R_RECOVERABLE] = "DNS_R_RECOVERABLE", + [DNS_R_UNKNOWNOPT] = "DNS_R_UNKNOWNOPT", + [DNS_R_UNEXPECTEDID] = "DNS_R_UNEXPECTEDID", + [DNS_R_SEENINCLUDE] = "DNS_R_SEENINCLUDE", + [DNS_R_NOTEXACT] = "DNS_R_NOTEXACT", + [DNS_R_BLACKHOLED] = "DNS_R_BLACKHOLED", + [DNS_R_BADALG] = "DNS_R_BADALG", + [DNS_R_METATYPE] = "DNS_R_METATYPE", + [DNS_R_CNAMEANDOTHER] = "DNS_R_CNAMEANDOTHER", + [DNS_R_SINGLETON] = "DNS_R_SINGLETON", + [DNS_R_HINTNXRRSET] = "DNS_R_HINTNXRRSET", + [DNS_R_NOMASTERFILE] = "DNS_R_NOMASTERFILE", + [DNS_R_UNKNOWNPROTO] = "DNS_R_UNKNOWNPROTO", + [DNS_R_CLOCKSKEW] = "DNS_R_CLOCKSKEW", + [DNS_R_BADIXFR] = "DNS_R_BADIXFR", + [DNS_R_NOTAUTHORITATIVE] = "DNS_R_NOTAUTHORITATIVE", + [DNS_R_NOVALIDKEY] = "DNS_R_NOVALIDKEY", + [DNS_R_OBSOLETE] = "DNS_R_OBSOLETE", + [DNS_R_FROZEN] = "DNS_R_FROZEN", + [DNS_R_UNKNOWNFLAG] = "DNS_R_UNKNOWNFLAG", + [DNS_R_EXPECTEDRESPONSE] = "DNS_R_EXPECTEDRESPONSE", + [DNS_R_NOVALIDDS] = "DNS_R_NOVALIDDS", + [DNS_R_NSISADDRESS] = "DNS_R_NSISADDRESS", + [DNS_R_REMOTEFORMERR] = "DNS_R_REMOTEFORMERR", + [DNS_R_TRUNCATEDTCP] = "DNS_R_TRUNCATEDTCP", + [DNS_R_LAME] = "DNS_R_LAME", + [DNS_R_UNEXPECTEDRCODE] = "DNS_R_UNEXPECTEDRCODE", + [DNS_R_UNEXPECTEDOPCODE] = "DNS_R_UNEXPECTEDOPCODE", + [DNS_R_CHASEDSSERVERS] = "DNS_R_CHASEDSSERVERS", + [DNS_R_EMPTYNAME] = "DNS_R_EMPTYNAME", + [DNS_R_EMPTYWILD] = "DNS_R_EMPTYWILD", + [DNS_R_BADBITMAP] = "DNS_R_BADBITMAP", + [DNS_R_FROMWILDCARD] = "DNS_R_FROMWILDCARD", + [DNS_R_BADOWNERNAME] = "DNS_R_BADOWNERNAME", + [DNS_R_BADNAME] = "DNS_R_BADNAME", + [DNS_R_DYNAMIC] = "DNS_R_DYNAMIC", + [DNS_R_UNKNOWNCOMMAND] = "DNS_R_UNKNOWNCOMMAND", + [DNS_R_MUSTBESECURE] = "DNS_R_MUSTBESECURE", + [DNS_R_COVERINGNSEC] = "DNS_R_COVERINGNSEC", + [DNS_R_MXISADDRESS] = "DNS_R_MXISADDRESS", + [DNS_R_DUPLICATE] = "DNS_R_DUPLICATE", + [DNS_R_INVALIDNSEC3] = "DNS_R_INVALIDNSEC3", + [DNS_R_NOTPRIMARY] = "DNS_R_NOTPRIMARY", + [DNS_R_BROKENCHAIN] = "DNS_R_BROKENCHAIN", + [DNS_R_EXPIRED] = "DNS_R_EXPIRED", + [DNS_R_NOTDYNAMIC] = "DNS_R_NOTDYNAMIC", + [DNS_R_BADEUI] = "DNS_R_BADEUI", + [DNS_R_NTACOVERED] = "DNS_R_NTACOVERED", + [DNS_R_BADCDS] = "DNS_R_BADCDS", + [DNS_R_BADCDNSKEY] = "DNS_R_BADCDNSKEY", + [DNS_R_OPTERR] = "DNS_R_OPTERR", + [DNS_R_BADDNSTAP] = "DNS_R_BADDNSTAP", + [DNS_R_BADTSIG] = "DNS_R_BADTSIG", + [DNS_R_BADSIG0] = "DNS_R_BADSIG0", + [DNS_R_TOOMANYRECORDS] = "DNS_R_TOOMANYRECORDS", + [DNS_R_VERIFYFAILURE] = "DNS_R_VERIFYFAILURE", + [DNS_R_ATZONETOP] = "DNS_R_ATZONETOP", + [DNS_R_NOKEYMATCH] = "DNS_R_NOKEYMATCH", + [DNS_R_TOOMANYKEYS] = "DNS_R_TOOMANYKEYS", + [DNS_R_KEYNOTACTIVE] = "DNS_R_KEYNOTACTIVE", + [DNS_R_NSEC3ITERRANGE] = "DNS_R_NSEC3ITERRANGE", + [DNS_R_NSEC3SALTRANGE] = "DNS_R_NSEC3SALTRANGE", + [DNS_R_NSEC3BADALG] = "DNS_R_NSEC3BADALG", + [DNS_R_NSEC3RESALT] = "DNS_R_NSEC3RESALT", + [DNS_R_INCONSISTENTRR] = "DNS_R_INCONSISTENTRR", + [DNS_R_NOALPN] = "DNS_R_NOALPN", + + [DST_R_UNSUPPORTEDALG] = "DST_R_UNSUPPORTEDALG", + [DST_R_CRYPTOFAILURE] = "DST_R_CRYPTOFAILURE", + [DST_R_NOCRYPTO] = "DST_R_NOCRYPTO", + [DST_R_NULLKEY] = "DST_R_NULLKEY", + [DST_R_INVALIDPUBLICKEY] = "DST_R_INVALIDPUBLICKEY", + [DST_R_INVALIDPRIVATEKEY] = "DST_R_INVALIDPRIVATEKEY", + [DST_R_WRITEERROR] = "DST_R_WRITEERROR", + [DST_R_INVALIDPARAM] = "DST_R_INVALIDPARAM", + [DST_R_SIGNFAILURE] = "DST_R_SIGNFAILURE", + [DST_R_VERIFYFAILURE] = "DST_R_VERIFYFAILURE", + [DST_R_NOTPUBLICKEY] = "DST_R_NOTPUBLICKEY", + [DST_R_NOTPRIVATEKEY] = "DST_R_NOTPRIVATEKEY", + [DST_R_KEYCANNOTCOMPUTESECRET] = "DST_R_KEYCANNOTCOMPUTESECRET", + [DST_R_COMPUTESECRETFAILURE] = "DST_R_COMPUTESECRETFAILURE", + [DST_R_NORANDOMNESS] = "DST_R_NORANDOMNESS", + [DST_R_BADKEYTYPE] = "DST_R_BADKEYTYPE", + [DST_R_NOENGINE] = "DST_R_NOENGINE", + [DST_R_EXTERNALKEY] = "DST_R_EXTERNALKEY", + + [DNS_R_NOERROR] = "DNS_R_NOERROR", + [DNS_R_FORMERR] = "DNS_R_FORMERR", + [DNS_R_SERVFAIL] = "DNS_R_SERVFAIL", + [DNS_R_NXDOMAIN] = "DNS_R_NXDOMAIN", + [DNS_R_NOTIMP] = "DNS_R_NOTIMP", + [DNS_R_REFUSED] = "DNS_R_REFUSED", + [DNS_R_YXDOMAIN] = "DNS_R_YXDOMAIN", + [DNS_R_YXRRSET] = "DNS_R_YXRRSET", + [DNS_R_NXRRSET] = "DNS_R_NXRRSET", + [DNS_R_NOTAUTH] = "DNS_R_NOTAUTH", + [DNS_R_NOTZONE] = "DNS_R_NOTZONE", + [DNS_R_RCODE11] = "DNS_R_RCODE11", + [DNS_R_RCODE12] = "RNS_R_RCODE12", + [DNS_R_RCODE13] = "DNS_R_RCODE13", + [DNS_R_RCODE14] = "DNS_R_RCODE14", + [DNS_R_RCODE15] = "DNS_R_RCODE15", + [DNS_R_BADVERS] = "DNS_R_BADVERS", + [DNS_R_BADCOOKIE] = "DNS_R_BADCOOKIE", + + [ISCCC_R_UNKNOWNVERSION] = "ISCCC_R_UNKNOWNVERSION", + [ISCCC_R_SYNTAX] = "ISCCC_R_SYNTAX", + [ISCCC_R_BADAUTH] = "ISCCC_R_BADAUTH", + [ISCCC_R_EXPIRED] = "ISCCC_R_EXPIRED", + [ISCCC_R_CLOCKSKEW] = "ISCCC_R_CLOCKSKEW", + [ISCCC_R_DUPLICATE] = "ISCCC_R_DUPLICATE", + [ISCCC_R_MAXDEPTH] = "ISCCC_R_MAXDEPTH", +}; + +STATIC_ASSERT((DNS_R_SERVFAIL - DNS_R_NOERROR == 2), + "DNS_R_NOERROR has wrong value"); + +STATIC_ASSERT((DNS_R_BADVERS - DNS_R_NOERROR == 16), + "DNS_R_BADVERS has wrong value"); + +STATIC_ASSERT((ISC_R_NRESULTS < INT32_MAX), "result.h enum too big"); + +const char * +isc_result_totext(isc_result_t result) { + return (description[result]); +} + +const char * +isc_result_toid(isc_result_t result) { + return (identifier[result]); +} diff --git a/lib/isc/rwlock.c b/lib/isc/rwlock.c new file mode 100644 index 0000000..156b469 --- /dev/null +++ b/lib/isc/rwlock.c @@ -0,0 +1,644 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/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("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..038e3ec --- /dev/null +++ b/lib/isc/sockaddr.c @@ -0,0 +1,499 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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> + +#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; + 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); + 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("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("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("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("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) { + 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); +} + +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; + case AF_UNIX: + length = sizeof(isa->type.sunix); + break; + 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..3e4676c --- /dev/null +++ b/lib/isc/stats.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 <inttypes.h> +#include <string.h> + +#include <isc/atomic.h> +#include <isc/buffer.h> +#include <isc/magic.h> +#include <isc/mem.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) + +typedef atomic_int_fast64_t isc__atomic_statcounter_t; + +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/stdio.c b/lib/isc/stdio.c new file mode 100644 index 0000000..12ab678 --- /dev/null +++ b/lib/isc/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/stdtime.c b/lib/isc/stdtime.c new file mode 100644 index 0000000..b7cec81 --- /dev/null +++ b/lib/isc/stdtime.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. + */ + +/*! \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/time.h> +#include <isc/util.h> + +#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) { + FATAL_SYSERROR(errno, "clock_gettime()"); + } + + REQUIRE(ts.tv_sec > 0 && ts.tv_nsec >= 0 && ts.tv_nsec < NS_PER_SEC); + + *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/string.c b/lib/isc/string.c new file mode 100644 index 0000000..09cf5d6 --- /dev/null +++ b/lib/isc/string.c @@ -0,0 +1,143 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 */ + +#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; + 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--; + } + return ((char *)s); +} +#endif + +int +isc_string_strerror_r(int errnum, char *buf, size_t buflen) { + return (strerror_r(errnum, buf, buflen)); +} 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/syslog.c b/lib/isc/syslog.c new file mode 100644 index 0000000..81b99ca --- /dev/null +++ b/lib/isc/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/task.c b/lib/isc/task.c new file mode 100644 index 0000000..439d430 --- /dev/null +++ b/lib/isc/task.c @@ -0,0 +1,1368 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/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_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", + "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; + 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; + + 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_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); + 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_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); +} + +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; + + 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; + } + 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); + 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 = NULL; + + 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"); + } + + atomic_compare_exchange_enforced(&manager->exclusive_req, + &(bool){ true }, false); +} + +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/thread.c b/lib/isc/thread.c new file mode 100644 index 0000000..5b762ed --- /dev/null +++ b/lib/isc/thread.c @@ -0,0 +1,121 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/thread.h> +#include <isc/util.h> + +#include "trampoline_p.h" + +#ifndef THREAD_MINSTACKSIZE +#define THREAD_MINSTACKSIZE (1024U * 1024) +#endif /* ifndef THREAD_MINSTACKSIZE */ + +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_SYSERROR(ret, "pthread_attr_getstacksize()"); + } + + if (stacksize < THREAD_MINSTACKSIZE) { + ret = pthread_attr_setstacksize(&attr, THREAD_MINSTACKSIZE); + if (ret != 0) { + FATAL_SYSERROR(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_SYSERROR(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_SYSERROR(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/time.c b/lib/isc/time.c new file mode 100644 index 0000000..b03f377 --- /dev/null +++ b/lib/isc/time.c @@ -0,0 +1,563 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/print.h> +#include <isc/string.h> +#include <isc/time.h> +#include <isc/tm.h> +#include <isc/util.h> + +#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_SEC); + + i->seconds = seconds; + i->nanoseconds = nanoseconds; +} + +bool +isc_interval_iszero(const isc_interval_t *i) { + REQUIRE(i != NULL); + INSIST(i->nanoseconds < NS_PER_SEC); + + if (i->seconds == 0 && i->nanoseconds == 0) { + return (true); + } + + return (false); +} + +unsigned int +isc_interval_ms(const isc_interval_t *i) { + REQUIRE(i != NULL); + INSIST(i->nanoseconds < NS_PER_SEC); + + return ((i->seconds * MS_PER_SEC) + (i->nanoseconds / NS_PER_MS)); +} + +/*** + *** 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_SEC); + + 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_SEC); + + 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; + + REQUIRE(t != NULL); + + if (clock_gettime(clock, &ts) == -1) { + UNEXPECTED_SYSERROR(errno, "clock_gettime()"); + return (ISC_R_UNEXPECTED); + } + + if (ts.tv_sec < 0 || ts.tv_nsec < 0 || ts.tv_nsec >= NS_PER_SEC) { + 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; + + REQUIRE(t != NULL); + REQUIRE(i != NULL); + INSIST(i->nanoseconds < NS_PER_SEC); + + if (clock_gettime(CLOCKSOURCE, &ts) == -1) { + UNEXPECTED_SYSERROR(errno, "clock_gettime()"); + return (ISC_R_UNEXPECTED); + } + + if (ts.tv_sec < 0 || ts.tv_nsec < 0 || ts.tv_nsec >= NS_PER_SEC) { + 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_SEC) { + t->seconds++; + t->nanoseconds -= NS_PER_SEC; + } + + 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_SEC && t2->nanoseconds < NS_PER_SEC); + + 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_SEC && i->nanoseconds < NS_PER_SEC); + + /* Seconds */ +#if HAVE_BUILTIN_OVERFLOW + if (__builtin_uadd_overflow(t->seconds, i->seconds, &result->seconds)) { + return (ISC_R_RANGE); + } +#else + if (t->seconds > UINT_MAX - i->seconds) { + return (ISC_R_RANGE); + } + result->seconds = t->seconds + i->seconds; +#endif + + /* Nanoseconds */ + result->nanoseconds = t->nanoseconds + i->nanoseconds; + if (result->nanoseconds >= NS_PER_SEC) { + if (result->seconds == UINT_MAX) { + return (ISC_R_RANGE); + } + result->nanoseconds -= NS_PER_SEC; + 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_SEC && i->nanoseconds < NS_PER_SEC); + + /* Seconds */ +#if HAVE_BUILTIN_OVERFLOW + if (__builtin_usub_overflow(t->seconds, i->seconds, &result->seconds)) { + return (ISC_R_RANGE); + } +#else + if (t->seconds < i->seconds) { + return (ISC_R_RANGE); + } + result->seconds = t->seconds - i->seconds; +#endif + + /* 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_SEC + 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_SEC && t2->nanoseconds < NS_PER_SEC); + + i1 = (uint64_t)t1->seconds * NS_PER_SEC + t1->nanoseconds; + i2 = (uint64_t)t2->seconds * NS_PER_SEC + 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_SEC); + + 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_SEC); + + /* + * 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_SEC); + + 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_SEC); + 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_SEC); + 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_SEC); + 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_SEC); + 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_SEC); + 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_SEC); + 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_SEC); + 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_SEC); + 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_SEC); + 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/timer.c b/lib/isc/timer.c new file mode 100644 index 0000000..40ae929 --- /dev/null +++ b/lib/isc/timer.c @@ -0,0 +1,741 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/print.h> +#include <isc/refcount.h> +#include <isc/result.h> +#include <isc/task.h> +#include <isc/thread.h> +#include <isc/time.h> +#include <isc/timer.h> +#include <isc/util.h> + +#include "timer_p.h" + +#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 *manager); + +static inline isc_result_t +schedule(isc_timer_t *timer, isc_time_t *now, bool signal_ok) { + isc_timermgr_t *manager; + isc_time_t due; + + /*! + * 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. + */ + int 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 inline void +deschedule(isc_timer_t *timer) { + isc_timermgr_t *manager; + + /* + * The caller must ensure locking. + */ + + manager = timer->manager; + if (timer->index > 0) { + bool need_wakeup = false; + 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( + "couldn't schedule timer: %s", + isc_result_totext(result)); + } + } + } else { + manager->due = timer->due; + done = true; + } + } +} + +static isc_threadresult_t +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); + + 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..ab9bf9c --- /dev/null +++ b/lib/isc/timer_p.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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/timer.h> + +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. + */ diff --git a/lib/isc/tls.c b/lib/isc/tls.c new file mode 100644 index 0000000..e2cd63e --- /dev/null +++ b/lib/isc/tls.c @@ -0,0 +1,1678 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you 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 <netinet/in.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#if HAVE_LIBNGHTTP2 +#include <nghttp2/nghttp2.h> +#endif /* HAVE_LIBNGHTTP2 */ +#include <arpa/inet.h> + +#include <openssl/bn.h> +#include <openssl/conf.h> +#include <openssl/crypto.h> +#include <openssl/dh.h> +#include <openssl/err.h> +#include <openssl/evp.h> +#include <openssl/opensslv.h> +#include <openssl/rand.h> +#include <openssl/rsa.h> +#include <openssl/x509_vfy.h> +#include <openssl/x509v3.h> + +#include <isc/atomic.h> +#include <isc/ht.h> +#include <isc/log.h> +#include <isc/magic.h> +#include <isc/mutex.h> +#include <isc/mutexblock.h> +#include <isc/once.h> +#include <isc/random.h> +#include <isc/refcount.h> +#include <isc/rwlock.h> +#include <isc/sockaddr.h> +#include <isc/thread.h> +#include <isc/tls.h> +#include <isc/util.h> + +#include "openssl_shim.h" +#include "tls_p.h" + +#define COMMON_SSL_OPTIONS \ + (SSL_OP_NO_COMPRESSION | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION) + +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. + * + * 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) && OPENSSL_API_LEVEL < 30000 + 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("OpenSSL pseudorandom number generator " + "cannot be initialized (see the `PRNG not " + "seeded' message in the OpenSSL FAQ)"); + } + + atomic_compare_exchange_enforced(&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) && OPENSSL_API_LEVEL < 30000 + 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 + + atomic_compare_exchange_enforced(&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)); +} + +void +isc_tlsctx_free(isc_tlsctx_t **ctxp) { + SSL_CTX *ctx = NULL; + REQUIRE(ctxp != NULL && *ctxp != NULL); + + ctx = *ctxp; + *ctxp = NULL; + + SSL_CTX_free(ctx); +} + +void +isc_tlsctx_attach(isc_tlsctx_t *src, isc_tlsctx_t **ptarget) { + REQUIRE(src != NULL); + REQUIRE(ptarget != NULL && *ptarget == NULL); + + RUNTIME_CHECK(SSL_CTX_up_ref(src) == 1); + + *ptarget = src; +} + +#if HAVE_SSL_CTX_SET_KEYLOG_CALLBACK +/* + * Callback invoked by the SSL library whenever a new TLS pre-master secret + * needs to be logged. + */ +static void +sslkeylogfile_append(const SSL *ssl, const char *line) { + UNUSED(ssl); + + isc_log_write(isc_lctx, ISC_LOGCATEGORY_SSLKEYLOG, ISC_LOGMODULE_NETMGR, + ISC_LOG_INFO, "%s", line); +} + +/* + * Enable TLS pre-master secret logging if the SSLKEYLOGFILE environment + * variable is set. This needs to be done on a per-context basis as that is + * how SSL_CTX_set_keylog_callback() works. + */ +static void +sslkeylogfile_init(isc_tlsctx_t *ctx) { + if (getenv("SSLKEYLOGFILE") != NULL) { + SSL_CTX_set_keylog_callback(ctx, sslkeylogfile_append); + } +} +#else /* HAVE_SSL_CTX_SET_KEYLOG_CALLBACK */ +#define sslkeylogfile_init(ctx) +#endif /* HAVE_SSL_CTX_SET_KEYLOG_CALLBACK */ + +isc_result_t +isc_tlsctx_createclient(isc_tlsctx_t **ctxp) { + unsigned long err; + char errbuf[256]; + SSL_CTX *ctx = NULL; + const SSL_METHOD *method = NULL; + + REQUIRE(ctxp != NULL && *ctxp == NULL); + + method = TLS_client_method(); + if (method == NULL) { + goto ssl_error; + } + ctx = SSL_CTX_new(method); + if (ctx == NULL) { + goto ssl_error; + } + + SSL_CTX_set_options(ctx, COMMON_SSL_OPTIONS); + +#if HAVE_SSL_CTX_SET_MIN_PROTO_VERSION + SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); +#else + SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | + SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1); +#endif + + sslkeylogfile_init(ctx); + + *ctxp = ctx; + + return (ISC_R_SUCCESS); + +ssl_error: + err = ERR_get_error(); + ERR_error_string_n(err, errbuf, sizeof(errbuf)); + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, ISC_LOGMODULE_NETMGR, + ISC_LOG_ERROR, "Error initializing TLS context: %s", + errbuf); + + return (ISC_R_TLSERROR); +} + +isc_result_t +isc_tlsctx_load_certificate(isc_tlsctx_t *ctx, const char *keyfile, + const char *certfile) { + int rv; + REQUIRE(ctx != NULL); + REQUIRE(keyfile != NULL); + REQUIRE(certfile != NULL); + + rv = SSL_CTX_use_certificate_chain_file(ctx, certfile); + if (rv != 1) { + return (ISC_R_TLSERROR); + } + rv = SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM); + if (rv != 1) { + return (ISC_R_TLSERROR); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_tlsctx_createserver(const char *keyfile, const char *certfile, + isc_tlsctx_t **ctxp) { + int rv; + unsigned long err; + bool ephemeral = (keyfile == NULL && certfile == NULL); + X509 *cert = NULL; + EVP_PKEY *pkey = NULL; + SSL_CTX *ctx = NULL; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_KEY *eckey = NULL; +#else + EVP_PKEY_CTX *pkey_ctx = NULL; + EVP_PKEY *params_pkey = NULL; +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L */ + char errbuf[256]; + const SSL_METHOD *method = NULL; + + REQUIRE(ctxp != NULL && *ctxp == NULL); + REQUIRE((keyfile == NULL) == (certfile == NULL)); + + method = TLS_server_method(); + if (method == NULL) { + goto ssl_error; + } + ctx = SSL_CTX_new(method); + if (ctx == NULL) { + goto ssl_error; + } + RUNTIME_CHECK(ctx != NULL); + + SSL_CTX_set_options(ctx, COMMON_SSL_OPTIONS); + +#if HAVE_SSL_CTX_SET_MIN_PROTO_VERSION + SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); +#else + SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | + SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1); +#endif + + if (ephemeral) { + const int group_nid = NID_X9_62_prime256v1; + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + eckey = EC_KEY_new_by_curve_name(group_nid); + if (eckey == NULL) { + goto ssl_error; + } + + /* Generate the key. */ + rv = EC_KEY_generate_key(eckey); + if (rv != 1) { + goto ssl_error; + } + pkey = EVP_PKEY_new(); + if (pkey == NULL) { + goto ssl_error; + } + rv = EVP_PKEY_set1_EC_KEY(pkey, eckey); + if (rv != 1) { + goto ssl_error; + } + + /* Use a named curve and uncompressed point conversion form. */ +#if HAVE_EVP_PKEY_GET0_EC_KEY + EC_KEY_set_asn1_flag(EVP_PKEY_get0_EC_KEY(pkey), + OPENSSL_EC_NAMED_CURVE); + EC_KEY_set_conv_form(EVP_PKEY_get0_EC_KEY(pkey), + POINT_CONVERSION_UNCOMPRESSED); +#else + EC_KEY_set_asn1_flag(pkey->pkey.ec, OPENSSL_EC_NAMED_CURVE); + EC_KEY_set_conv_form(pkey->pkey.ec, + POINT_CONVERSION_UNCOMPRESSED); +#endif /* HAVE_EVP_PKEY_GET0_EC_KEY */ + +#if defined(SSL_CTX_set_ecdh_auto) + /* + * Using this macro is required for older versions of OpenSSL to + * automatically enable ECDH support. + * + * On later versions this function is no longer needed and is + * deprecated. + */ + (void)SSL_CTX_set_ecdh_auto(ctx, 1); +#endif /* defined(SSL_CTX_set_ecdh_auto) */ + + /* Cleanup */ + EC_KEY_free(eckey); + eckey = NULL; +#else + /* Generate the key's parameters. */ + pkey_ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL); + if (pkey_ctx == NULL) { + goto ssl_error; + } + rv = EVP_PKEY_paramgen_init(pkey_ctx); + if (rv != 1) { + goto ssl_error; + } + rv = EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pkey_ctx, + group_nid); + if (rv != 1) { + goto ssl_error; + } + rv = EVP_PKEY_paramgen(pkey_ctx, ¶ms_pkey); + if (rv != 1 || params_pkey == NULL) { + goto ssl_error; + } + EVP_PKEY_CTX_free(pkey_ctx); + + /* Generate the key. */ + pkey_ctx = EVP_PKEY_CTX_new(params_pkey, NULL); + if (pkey_ctx == NULL) { + goto ssl_error; + } + rv = EVP_PKEY_keygen_init(pkey_ctx); + if (rv != 1) { + goto ssl_error; + } + rv = EVP_PKEY_keygen(pkey_ctx, &pkey); + if (rv != 1 || pkey == NULL) { + goto ssl_error; + } + + /* Cleanup */ + EVP_PKEY_free(params_pkey); + params_pkey = NULL; + EVP_PKEY_CTX_free(pkey_ctx); + pkey_ctx = NULL; +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L */ + + cert = X509_new(); + if (cert == NULL) { + goto ssl_error; + } + + ASN1_INTEGER_set(X509_get_serialNumber(cert), + (long)isc_random32()); + + /* + * Set the "not before" property 5 minutes into the past to + * accommodate with some possible clock skew across systems. + */ +#if OPENSSL_VERSION_NUMBER < 0x10101000L + X509_gmtime_adj(X509_get_notBefore(cert), -300); +#else + X509_gmtime_adj(X509_getm_notBefore(cert), -300); +#endif + + /* + * We set the vailidy for 10 years. + */ +#if OPENSSL_VERSION_NUMBER < 0x10101000L + X509_gmtime_adj(X509_get_notAfter(cert), 3650 * 24 * 3600); +#else + X509_gmtime_adj(X509_getm_notAfter(cert), 3650 * 24 * 3600); +#endif + + X509_set_pubkey(cert, pkey); + + X509_NAME *name = X509_get_subject_name(cert); + + X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, + (const unsigned char *)"AQ", -1, -1, + 0); + X509_NAME_add_entry_by_txt( + name, "O", MBSTRING_ASC, + (const unsigned char *)"BIND9 ephemeral " + "certificate", + -1, -1, 0); + X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, + (const unsigned char *)"bind9.local", + -1, -1, 0); + + X509_set_issuer_name(cert, name); + X509_sign(cert, pkey, EVP_sha256()); + rv = SSL_CTX_use_certificate(ctx, cert); + if (rv != 1) { + goto ssl_error; + } + rv = SSL_CTX_use_PrivateKey(ctx, pkey); + if (rv != 1) { + goto ssl_error; + } + + X509_free(cert); + EVP_PKEY_free(pkey); + } else { + isc_result_t result; + result = isc_tlsctx_load_certificate(ctx, keyfile, certfile); + if (result != ISC_R_SUCCESS) { + goto ssl_error; + } + } + + sslkeylogfile_init(ctx); + + *ctxp = ctx; + return (ISC_R_SUCCESS); + +ssl_error: + err = ERR_get_error(); + ERR_error_string_n(err, errbuf, sizeof(errbuf)); + isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, ISC_LOGMODULE_NETMGR, + ISC_LOG_ERROR, "Error initializing TLS context: %s", + errbuf); + + if (ctx != NULL) { + SSL_CTX_free(ctx); + } + if (cert != NULL) { + X509_free(cert); + } + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } +#if OPENSSL_VERSION_NUMBER < 0x30000000L + if (eckey != NULL) { + EC_KEY_free(eckey); + } +#else + if (params_pkey != NULL) { + EVP_PKEY_free(params_pkey); + } + if (pkey_ctx != NULL) { + EVP_PKEY_CTX_free(pkey_ctx); + } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L */ + + return (ISC_R_TLSERROR); +} + +static long +get_tls_version_disable_bit(const isc_tls_protocol_version_t tls_ver) { + long bit = 0; + + switch (tls_ver) { + case ISC_TLS_PROTO_VER_1_2: +#ifdef SSL_OP_NO_TLSv1_2 + bit = SSL_OP_NO_TLSv1_2; +#else + bit = 0; +#endif + break; + case ISC_TLS_PROTO_VER_1_3: +#ifdef SSL_OP_NO_TLSv1_3 + bit = SSL_OP_NO_TLSv1_3; +#else + bit = 0; +#endif + break; + default: + UNREACHABLE(); + break; + }; + + return (bit); +} + +bool +isc_tls_protocol_supported(const isc_tls_protocol_version_t tls_ver) { + return (get_tls_version_disable_bit(tls_ver) != 0); +} + +isc_tls_protocol_version_t +isc_tls_protocol_name_to_version(const char *name) { + REQUIRE(name != NULL); + + if (strcasecmp(name, "TLSv1.2") == 0) { + return (ISC_TLS_PROTO_VER_1_2); + } else if (strcasecmp(name, "TLSv1.3") == 0) { + return (ISC_TLS_PROTO_VER_1_3); + } + + return (ISC_TLS_PROTO_VER_UNDEFINED); +} + +void +isc_tlsctx_set_protocols(isc_tlsctx_t *ctx, const uint32_t tls_versions) { + REQUIRE(ctx != NULL); + REQUIRE(tls_versions != 0); + long set_options = 0; + long clear_options = 0; + uint32_t versions = tls_versions; + + /* + * The code below might be initially hard to follow because of the + * double negation that OpenSSL enforces. + * + * Taking into account that OpenSSL provides bits to *disable* + * specific protocol versions, like SSL_OP_NO_TLSv1_2, + * SSL_OP_NO_TLSv1_3, etc., the code has the following logic: + * + * If a protocol version is not specified in the bitmask, get the + * bit that disables it and add it to the set of TLS options to + * set ('set_options'). Otherwise, if a protocol version is set, + * add the bit to the set of options to clear ('clear_options'). + */ + + /* TLS protocol versions are defined as powers of two. */ + for (uint32_t tls_ver = ISC_TLS_PROTO_VER_1_2; + tls_ver < ISC_TLS_PROTO_VER_UNDEFINED; tls_ver <<= 1) + { + if ((tls_versions & tls_ver) == 0) { + set_options |= get_tls_version_disable_bit(tls_ver); + } else { + /* + * Only supported versions should ever be passed to the + * function SSL_CTX_clear_options. For example, in order + * to enable TLS v1.2, we have to clear + * SSL_OP_NO_TLSv1_2. Insist that the configuration file + * was verified properly, so we are not trying to enable + * an unsupported TLS version. + */ + INSIST(isc_tls_protocol_supported(tls_ver)); + clear_options |= get_tls_version_disable_bit(tls_ver); + } + versions &= ~(tls_ver); + } + + /* All versions should be processed at this point, thus the value + * must equal zero. If it is not, then some garbage has been + * passed to the function; this situation is worth + * investigation. */ + INSIST(versions == 0); + + (void)SSL_CTX_set_options(ctx, set_options); + (void)SSL_CTX_clear_options(ctx, clear_options); +} + +bool +isc_tlsctx_load_dhparams(isc_tlsctx_t *ctx, const char *dhparams_file) { + REQUIRE(ctx != NULL); + REQUIRE(dhparams_file != NULL); + REQUIRE(*dhparams_file != '\0'); + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + /* OpenSSL < 3.0 */ + DH *dh = NULL; + FILE *paramfile; + + paramfile = fopen(dhparams_file, "r"); + + if (paramfile) { + int check = 0; + dh = PEM_read_DHparams(paramfile, NULL, NULL, NULL); + fclose(paramfile); + + if (dh == NULL) { + return (false); + } else if (DH_check(dh, &check) != 1 || check != 0) { + DH_free(dh); + return (false); + } + } else { + return (false); + } + + if (SSL_CTX_set_tmp_dh(ctx, dh) != 1) { + DH_free(dh); + return (false); + } + + DH_free(dh); +#else + /* OpenSSL >= 3.0: low level DH APIs are deprecated in OpenSSL 3.0 */ + EVP_PKEY *dh = NULL; + BIO *bio = NULL; + + bio = BIO_new_file(dhparams_file, "r"); + if (bio == NULL) { + return (false); + } + + dh = PEM_read_bio_Parameters(bio, NULL); + if (dh == NULL) { + BIO_free(bio); + return (false); + } + + if (SSL_CTX_set0_tmp_dh_pkey(ctx, dh) != 1) { + BIO_free(bio); + EVP_PKEY_free(dh); + return (false); + } + + /* No need to call EVP_PKEY_free(dh) as the "dh" is owned by the + * SSL context at this point. */ + + BIO_free(bio); +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L */ + + return (true); +} + +bool +isc_tls_cipherlist_valid(const char *cipherlist) { + isc_tlsctx_t *tmp_ctx = NULL; + const SSL_METHOD *method = NULL; + bool result; + REQUIRE(cipherlist != NULL); + + if (*cipherlist == '\0') { + return (false); + } + + method = TLS_server_method(); + if (method == NULL) { + return (false); + } + tmp_ctx = SSL_CTX_new(method); + if (tmp_ctx == NULL) { + return (false); + } + + result = SSL_CTX_set_cipher_list(tmp_ctx, cipherlist) == 1; + + isc_tlsctx_free(&tmp_ctx); + + return (result); +} + +void +isc_tlsctx_set_cipherlist(isc_tlsctx_t *ctx, const char *cipherlist) { + REQUIRE(ctx != NULL); + REQUIRE(cipherlist != NULL); + REQUIRE(*cipherlist != '\0'); + + RUNTIME_CHECK(SSL_CTX_set_cipher_list(ctx, cipherlist) == 1); +} + +void +isc_tlsctx_prefer_server_ciphers(isc_tlsctx_t *ctx, const bool prefer) { + REQUIRE(ctx != NULL); + + if (prefer) { + (void)SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); + } else { + (void)SSL_CTX_clear_options(ctx, + SSL_OP_CIPHER_SERVER_PREFERENCE); + } +} + +void +isc_tlsctx_session_tickets(isc_tlsctx_t *ctx, const bool use) { + REQUIRE(ctx != NULL); + + if (!use) { + (void)SSL_CTX_set_options(ctx, SSL_OP_NO_TICKET); + } else { + (void)SSL_CTX_clear_options(ctx, SSL_OP_NO_TICKET); + } +} + +isc_tls_t * +isc_tls_create(isc_tlsctx_t *ctx) { + isc_tls_t *newctx = NULL; + + REQUIRE(ctx != NULL); + + newctx = SSL_new(ctx); + if (newctx == NULL) { + char errbuf[256]; + unsigned long err = ERR_get_error(); + + ERR_error_string_n(err, errbuf, sizeof(errbuf)); + fprintf(stderr, "%s:SSL_new(%p) -> %s\n", __func__, ctx, + errbuf); + } + + return (newctx); +} + +void +isc_tls_free(isc_tls_t **tlsp) { + isc_tls_t *tls = NULL; + REQUIRE(tlsp != NULL && *tlsp != NULL); + + tls = *tlsp; + *tlsp = NULL; + SSL_free(tls); +} + +const char * +isc_tls_verify_peer_result_string(isc_tls_t *tls) { + REQUIRE(tls != NULL); + + return (X509_verify_cert_error_string(SSL_get_verify_result(tls))); +} + +#if HAVE_LIBNGHTTP2 +#ifndef OPENSSL_NO_NEXTPROTONEG +/* + * NPN TLS extension client callback. + */ +static int +select_next_proto_cb(SSL *ssl, unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, void *arg) { + UNUSED(ssl); + UNUSED(arg); + + if (nghttp2_select_next_protocol(out, outlen, in, inlen) <= 0) { + return (SSL_TLSEXT_ERR_NOACK); + } + return (SSL_TLSEXT_ERR_OK); +} +#endif /* !OPENSSL_NO_NEXTPROTONEG */ + +void +isc_tlsctx_enable_http2client_alpn(isc_tlsctx_t *ctx) { + REQUIRE(ctx != NULL); + +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_CTX_set_next_proto_select_cb(ctx, select_next_proto_cb, NULL); +#endif /* !OPENSSL_NO_NEXTPROTONEG */ + +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + SSL_CTX_set_alpn_protos(ctx, (const unsigned char *)NGHTTP2_PROTO_ALPN, + NGHTTP2_PROTO_ALPN_LEN); +#endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */ +} + +#ifndef OPENSSL_NO_NEXTPROTONEG +static int +next_proto_cb(isc_tls_t *ssl, const unsigned char **data, unsigned int *len, + void *arg) { + UNUSED(ssl); + UNUSED(arg); + + *data = (const unsigned char *)NGHTTP2_PROTO_ALPN; + *len = (unsigned int)NGHTTP2_PROTO_ALPN_LEN; + return (SSL_TLSEXT_ERR_OK); +} +#endif /* !OPENSSL_NO_NEXTPROTONEG */ + +#if OPENSSL_VERSION_NUMBER >= 0x10002000L +static int +alpn_select_proto_cb(SSL *ssl, const unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, void *arg) { + int ret; + + UNUSED(ssl); + UNUSED(arg); + + ret = nghttp2_select_next_protocol((unsigned char **)(uintptr_t)out, + outlen, in, inlen); + + if (ret != 1) { + return (SSL_TLSEXT_ERR_NOACK); + } + + return (SSL_TLSEXT_ERR_OK); +} +#endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */ + +void +isc_tlsctx_enable_http2server_alpn(isc_tlsctx_t *tls) { + REQUIRE(tls != NULL); + +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_CTX_set_next_protos_advertised_cb(tls, next_proto_cb, NULL); +#endif // OPENSSL_NO_NEXTPROTONEG +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + SSL_CTX_set_alpn_select_cb(tls, alpn_select_proto_cb, NULL); +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L +} +#endif /* HAVE_LIBNGHTTP2 */ + +void +isc_tls_get_selected_alpn(isc_tls_t *tls, const unsigned char **alpn, + unsigned int *alpnlen) { + REQUIRE(tls != NULL); + REQUIRE(alpn != NULL); + REQUIRE(alpnlen != NULL); + +#ifndef OPENSSL_NO_NEXTPROTONEG + SSL_get0_next_proto_negotiated(tls, alpn, alpnlen); +#endif +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + if (*alpn == NULL) { + SSL_get0_alpn_selected(tls, alpn, alpnlen); + } +#endif +} + +static bool +protoneg_check_protocol(const uint8_t **pout, uint8_t *pout_len, + const uint8_t *in, size_t in_len, const uint8_t *key, + size_t key_len) { + for (size_t i = 0; i + key_len <= in_len; i += (size_t)(in[i] + 1)) { + if (memcmp(&in[i], key, key_len) == 0) { + *pout = (const uint8_t *)(&in[i + 1]); + *pout_len = in[i]; + return (true); + } + } + return (false); +} + +/* dot prepended by its length (3 bytes) */ +#define DOT_PROTO_ALPN "\x3" ISC_TLS_DOT_PROTO_ALPN_ID +#define DOT_PROTO_ALPN_LEN (sizeof(DOT_PROTO_ALPN) - 1) + +static bool +dot_select_next_protocol(const uint8_t **pout, uint8_t *pout_len, + const uint8_t *in, size_t in_len) { + return (protoneg_check_protocol(pout, pout_len, in, in_len, + (const uint8_t *)DOT_PROTO_ALPN, + DOT_PROTO_ALPN_LEN)); +} + +void +isc_tlsctx_enable_dot_client_alpn(isc_tlsctx_t *ctx) { + REQUIRE(ctx != NULL); + +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + SSL_CTX_set_alpn_protos(ctx, (const uint8_t *)DOT_PROTO_ALPN, + DOT_PROTO_ALPN_LEN); +#endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */ +} + +#if OPENSSL_VERSION_NUMBER >= 0x10002000L +static int +dot_alpn_select_proto_cb(SSL *ssl, const unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) { + bool ret; + + UNUSED(ssl); + UNUSED(arg); + + ret = dot_select_next_protocol(out, outlen, in, inlen); + + if (!ret) { + return (SSL_TLSEXT_ERR_NOACK); + } + + return (SSL_TLSEXT_ERR_OK); +} +#endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */ + +void +isc_tlsctx_enable_dot_server_alpn(isc_tlsctx_t *tls) { + REQUIRE(tls != NULL); + +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + SSL_CTX_set_alpn_select_cb(tls, dot_alpn_select_proto_cb, NULL); +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L +} + +isc_result_t +isc_tlsctx_enable_peer_verification(isc_tlsctx_t *tlsctx, const bool is_server, + isc_tls_cert_store_t *store, + const char *hostname, + bool hostname_ignore_subject) { + int ret = 0; + REQUIRE(tlsctx != NULL); + REQUIRE(store != NULL); + + /* Set the hostname/IP address. */ + if (!is_server && hostname != NULL && *hostname != '\0') { + struct in6_addr sa6; + struct in_addr sa; + X509_VERIFY_PARAM *param = SSL_CTX_get0_param(tlsctx); + unsigned int hostflags = X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS; + + /* It might be an IP address. */ + if (inet_pton(AF_INET6, hostname, &sa6) == 1 || + inet_pton(AF_INET, hostname, &sa) == 1) + { + ret = X509_VERIFY_PARAM_set1_ip_asc(param, hostname); + } else { + /* It seems that it is a host name. Let's set it. */ + ret = X509_VERIFY_PARAM_set1_host(param, hostname, 0); + } + if (ret != 1) { + ERR_clear_error(); + return (ISC_R_FAILURE); + } + +#ifdef X509_CHECK_FLAG_NEVER_CHECK_SUBJECT + /* + * According to the RFC 8310, Section 8.1, Subject field MUST + * NOT be inspected when verifying a hostname when using + * DoT. Only SubjectAltName must be checked instead. That is + * not the case for HTTPS, though. + * + * Unfortunately, some quite old versions of OpenSSL (< 1.1.1) + * might lack the functionality to implement that. It should + * have very little real-world consequences, as most of the + * production-ready certificates issued by real CAs will have + * SubjectAltName set. In such a case, the Subject field is + * ignored. + */ + if (hostname_ignore_subject) { + hostflags |= X509_CHECK_FLAG_NEVER_CHECK_SUBJECT; + } +#else + UNUSED(hostname_ignore_subject); +#endif + X509_VERIFY_PARAM_set_hostflags(param, hostflags); + } + + /* "Attach" the cert store to the context */ + SSL_CTX_set1_cert_store(tlsctx, store); + + /* enable verification */ + if (is_server) { + SSL_CTX_set_verify(tlsctx, + SSL_VERIFY_PEER | + SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + NULL); + } else { + SSL_CTX_set_verify(tlsctx, SSL_VERIFY_PEER, NULL); + } + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_tlsctx_load_client_ca_names(isc_tlsctx_t *ctx, const char *ca_bundle_file) { + STACK_OF(X509_NAME) * cert_names; + REQUIRE(ctx != NULL); + REQUIRE(ca_bundle_file != NULL); + + cert_names = SSL_load_client_CA_file(ca_bundle_file); + if (cert_names == NULL) { + ERR_clear_error(); + return (ISC_R_FAILURE); + } + + SSL_CTX_set_client_CA_list(ctx, cert_names); + + return (ISC_R_SUCCESS); +} + +isc_result_t +isc_tls_cert_store_create(const char *ca_bundle_filename, + isc_tls_cert_store_t **pstore) { + int ret = 0; + isc_tls_cert_store_t *store = NULL; + REQUIRE(pstore != NULL && *pstore == NULL); + + store = X509_STORE_new(); + if (store == NULL) { + goto error; + } + + /* Let's treat empty string as the default (system wide) store */ + if (ca_bundle_filename != NULL && *ca_bundle_filename == '\0') { + ca_bundle_filename = NULL; + } + + if (ca_bundle_filename == NULL) { + ret = X509_STORE_set_default_paths(store); + } else { + ret = X509_STORE_load_locations(store, ca_bundle_filename, + NULL); + } + + if (ret == 0) { + goto error; + } + + *pstore = store; + return (ISC_R_SUCCESS); + +error: + ERR_clear_error(); + if (store != NULL) { + X509_STORE_free(store); + } + return (ISC_R_FAILURE); +} + +void +isc_tls_cert_store_free(isc_tls_cert_store_t **pstore) { + isc_tls_cert_store_t *store; + REQUIRE(pstore != NULL && *pstore != NULL); + + store = *pstore; + + X509_STORE_free(store); + + *pstore = NULL; +} + +#define TLSCTX_CACHE_MAGIC ISC_MAGIC('T', 'l', 'S', 'c') +#define VALID_TLSCTX_CACHE(t) ISC_MAGIC_VALID(t, TLSCTX_CACHE_MAGIC) + +#define TLSCTX_CLIENT_SESSION_CACHE_MAGIC ISC_MAGIC('T', 'l', 'C', 'c') +#define VALID_TLSCTX_CLIENT_SESSION_CACHE(t) \ + ISC_MAGIC_VALID(t, TLSCTX_CLIENT_SESSION_CACHE_MAGIC) + +typedef struct isc_tlsctx_cache_entry { + /* + * We need a TLS context entry for each transport on both IPv4 and + * IPv6 in order to avoid cluttering a context-specific + * session-resumption cache. + */ + isc_tlsctx_t *ctx[isc_tlsctx_cache_count - 1][2]; + isc_tlsctx_client_session_cache_t + *client_sess_cache[isc_tlsctx_cache_count - 1][2]; + /* + * One certificate store is enough for all the contexts defined + * above. We need that for peer validation. + */ + isc_tls_cert_store_t *ca_store; +} isc_tlsctx_cache_entry_t; + +struct isc_tlsctx_cache { + uint32_t magic; + isc_refcount_t references; + isc_mem_t *mctx; + + isc_rwlock_t rwlock; + isc_ht_t *data; +}; + +void +isc_tlsctx_cache_create(isc_mem_t *mctx, isc_tlsctx_cache_t **cachep) { + isc_tlsctx_cache_t *nc; + + REQUIRE(cachep != NULL && *cachep == NULL); + nc = isc_mem_get(mctx, sizeof(*nc)); + + *nc = (isc_tlsctx_cache_t){ .magic = TLSCTX_CACHE_MAGIC }; + isc_refcount_init(&nc->references, 1); + isc_mem_attach(mctx, &nc->mctx); + + isc_ht_init(&nc->data, mctx, 5, ISC_HT_CASE_SENSITIVE); + isc_rwlock_init(&nc->rwlock, 0, 0); + + *cachep = nc; +} + +void +isc_tlsctx_cache_attach(isc_tlsctx_cache_t *source, + isc_tlsctx_cache_t **targetp) { + REQUIRE(VALID_TLSCTX_CACHE(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + isc_refcount_increment(&source->references); + + *targetp = source; +} + +static void +tlsctx_cache_entry_destroy(isc_mem_t *mctx, isc_tlsctx_cache_entry_t *entry) { + size_t i, k; + + for (i = 0; i < (isc_tlsctx_cache_count - 1); i++) { + for (k = 0; k < 2; k++) { + if (entry->ctx[i][k] != NULL) { + isc_tlsctx_free(&entry->ctx[i][k]); + } + + if (entry->client_sess_cache[i][k] != NULL) { + isc_tlsctx_client_session_cache_detach( + &entry->client_sess_cache[i][k]); + } + } + } + if (entry->ca_store != NULL) { + isc_tls_cert_store_free(&entry->ca_store); + } + isc_mem_put(mctx, entry, sizeof(*entry)); +} + +static void +tlsctx_cache_destroy(isc_tlsctx_cache_t *cache) { + isc_ht_iter_t *it = NULL; + isc_result_t result; + + cache->magic = 0; + + isc_refcount_destroy(&cache->references); + + isc_ht_iter_create(cache->data, &it); + for (result = isc_ht_iter_first(it); result == ISC_R_SUCCESS; + result = isc_ht_iter_delcurrent_next(it)) + { + isc_tlsctx_cache_entry_t *entry = NULL; + isc_ht_iter_current(it, (void **)&entry); + tlsctx_cache_entry_destroy(cache->mctx, entry); + } + + isc_ht_iter_destroy(&it); + isc_ht_destroy(&cache->data); + isc_rwlock_destroy(&cache->rwlock); + isc_mem_putanddetach(&cache->mctx, cache, sizeof(*cache)); +} + +void +isc_tlsctx_cache_detach(isc_tlsctx_cache_t **cachep) { + isc_tlsctx_cache_t *cache = NULL; + + REQUIRE(cachep != NULL); + + cache = *cachep; + *cachep = NULL; + + REQUIRE(VALID_TLSCTX_CACHE(cache)); + + if (isc_refcount_decrement(&cache->references) == 1) { + tlsctx_cache_destroy(cache); + } +} + +isc_result_t +isc_tlsctx_cache_add( + isc_tlsctx_cache_t *cache, const char *name, + const isc_tlsctx_cache_transport_t transport, const uint16_t family, + isc_tlsctx_t *ctx, isc_tls_cert_store_t *store, + isc_tlsctx_client_session_cache_t *client_sess_cache, + isc_tlsctx_t **pfound, isc_tls_cert_store_t **pfound_store, + isc_tlsctx_client_session_cache_t **pfound_client_sess_cache) { + isc_result_t result = ISC_R_FAILURE; + size_t name_len, tr_offset; + isc_tlsctx_cache_entry_t *entry = NULL; + bool ipv6; + + REQUIRE(VALID_TLSCTX_CACHE(cache)); + REQUIRE(client_sess_cache == NULL || + VALID_TLSCTX_CLIENT_SESSION_CACHE(client_sess_cache)); + REQUIRE(name != NULL && *name != '\0'); + REQUIRE(transport > isc_tlsctx_cache_none && + transport < isc_tlsctx_cache_count); + REQUIRE(family == AF_INET || family == AF_INET6); + REQUIRE(ctx != NULL); + + tr_offset = (transport - 1); + ipv6 = (family == AF_INET6); + + RWLOCK(&cache->rwlock, isc_rwlocktype_write); + + name_len = strlen(name); + result = isc_ht_find(cache->data, (const uint8_t *)name, name_len, + (void **)&entry); + if (result == ISC_R_SUCCESS && entry->ctx[tr_offset][ipv6] != NULL) { + isc_tlsctx_client_session_cache_t *found_client_sess_cache; + /* The entry exists. */ + if (pfound != NULL) { + INSIST(*pfound == NULL); + *pfound = entry->ctx[tr_offset][ipv6]; + } + + if (pfound_store != NULL && entry->ca_store != NULL) { + INSIST(*pfound_store == NULL); + *pfound_store = entry->ca_store; + } + + found_client_sess_cache = + entry->client_sess_cache[tr_offset][ipv6]; + if (pfound_client_sess_cache != NULL && + found_client_sess_cache != NULL) + { + INSIST(*pfound_client_sess_cache == NULL); + *pfound_client_sess_cache = found_client_sess_cache; + } + result = ISC_R_EXISTS; + } else if (result == ISC_R_SUCCESS && + entry->ctx[tr_offset][ipv6] == NULL) + { + /* + * The hash table entry exists, but is not filled for this + * particular transport/IP type combination. + */ + entry->ctx[tr_offset][ipv6] = ctx; + entry->client_sess_cache[tr_offset][ipv6] = client_sess_cache; + /* + * As the passed certificates store object is supposed + * to be internally managed by the cache object anyway, + * we might destroy the unneeded store object right now. + */ + if (store != NULL && store != entry->ca_store) { + isc_tls_cert_store_free(&store); + } + result = ISC_R_SUCCESS; + } else { + /* + * The hash table entry does not exist, let's create one. + */ + INSIST(result != ISC_R_SUCCESS); + entry = isc_mem_get(cache->mctx, sizeof(*entry)); + /* Oracle/Red Hat Linux, GCC bug #53119 */ + memset(entry, 0, sizeof(*entry)); + entry->ctx[tr_offset][ipv6] = ctx; + entry->client_sess_cache[tr_offset][ipv6] = client_sess_cache; + entry->ca_store = store; + RUNTIME_CHECK(isc_ht_add(cache->data, (const uint8_t *)name, + name_len, + (void *)entry) == ISC_R_SUCCESS); + result = ISC_R_SUCCESS; + } + + RWUNLOCK(&cache->rwlock, isc_rwlocktype_write); + + return (result); +} + +isc_result_t +isc_tlsctx_cache_find( + isc_tlsctx_cache_t *cache, const char *name, + const isc_tlsctx_cache_transport_t transport, const uint16_t family, + isc_tlsctx_t **pctx, isc_tls_cert_store_t **pstore, + isc_tlsctx_client_session_cache_t **pfound_client_sess_cache) { + isc_result_t result = ISC_R_FAILURE; + size_t tr_offset; + isc_tlsctx_cache_entry_t *entry = NULL; + bool ipv6; + + REQUIRE(VALID_TLSCTX_CACHE(cache)); + REQUIRE(name != NULL && *name != '\0'); + REQUIRE(transport > isc_tlsctx_cache_none && + transport < isc_tlsctx_cache_count); + REQUIRE(family == AF_INET || family == AF_INET6); + REQUIRE(pctx != NULL && *pctx == NULL); + + tr_offset = (transport - 1); + ipv6 = (family == AF_INET6); + + RWLOCK(&cache->rwlock, isc_rwlocktype_read); + + result = isc_ht_find(cache->data, (const uint8_t *)name, strlen(name), + (void **)&entry); + + if (result == ISC_R_SUCCESS && pstore != NULL && + entry->ca_store != NULL) + { + *pstore = entry->ca_store; + } + + if (result == ISC_R_SUCCESS && entry->ctx[tr_offset][ipv6] != NULL) { + isc_tlsctx_client_session_cache_t *found_client_sess_cache = + entry->client_sess_cache[tr_offset][ipv6]; + + *pctx = entry->ctx[tr_offset][ipv6]; + + if (pfound_client_sess_cache != NULL && + found_client_sess_cache != NULL) + { + INSIST(*pfound_client_sess_cache == NULL); + *pfound_client_sess_cache = found_client_sess_cache; + } + } else if (result == ISC_R_SUCCESS && + entry->ctx[tr_offset][ipv6] == NULL) + { + result = ISC_R_NOTFOUND; + } else { + INSIST(result != ISC_R_SUCCESS); + } + + RWUNLOCK(&cache->rwlock, isc_rwlocktype_read); + + return (result); +} + +typedef struct client_session_cache_entry client_session_cache_entry_t; + +typedef struct client_session_cache_bucket { + char *bucket_key; + size_t bucket_key_len; + /* Cache entries within the bucket (from the oldest to the newest). */ + ISC_LIST(client_session_cache_entry_t) entries; +} client_session_cache_bucket_t; + +struct client_session_cache_entry { + SSL_SESSION *session; + client_session_cache_bucket_t *bucket; /* "Parent" bucket pointer. */ + ISC_LINK(client_session_cache_entry_t) bucket_link; + ISC_LINK(client_session_cache_entry_t) cache_link; +}; + +struct isc_tlsctx_client_session_cache { + uint32_t magic; + isc_refcount_t references; + isc_mem_t *mctx; + + /* + * We need to keep a reference to the related TLS context in order + * to ensure that it remains valid while the TLS client sessions + * cache object is valid, as every TLS session object + * (SSL_SESSION) is "tied" to a particular context. + */ + isc_tlsctx_t *ctx; + + /* + * The idea is to have one bucket per remote server. Each bucket, + * can maintain multiple TLS sessions to that server, as BIND + * might want to establish multiple TLS connections to the remote + * server at once. + */ + isc_ht_t *buckets; + + /* + * The list of all current entries within the cache maintained in + * LRU-manner, so that the oldest entry might be efficiently + * removed. + */ + ISC_LIST(client_session_cache_entry_t) lru_entries; + /* Number of the entries within the cache. */ + size_t nentries; + /* Maximum number of the entries within the cache. */ + size_t max_entries; + + isc_mutex_t lock; +}; + +void +isc_tlsctx_client_session_cache_create( + isc_mem_t *mctx, isc_tlsctx_t *ctx, const size_t max_entries, + isc_tlsctx_client_session_cache_t **cachep) { + isc_tlsctx_client_session_cache_t *nc; + + REQUIRE(ctx != NULL); + REQUIRE(max_entries > 0); + REQUIRE(cachep != NULL && *cachep == NULL); + + nc = isc_mem_get(mctx, sizeof(*nc)); + + *nc = (isc_tlsctx_client_session_cache_t){ .max_entries = max_entries }; + isc_refcount_init(&nc->references, 1); + isc_mem_attach(mctx, &nc->mctx); + isc_tlsctx_attach(ctx, &nc->ctx); + + isc_ht_init(&nc->buckets, mctx, 5, ISC_HT_CASE_SENSITIVE); + ISC_LIST_INIT(nc->lru_entries); + isc_mutex_init(&nc->lock); + + nc->magic = TLSCTX_CLIENT_SESSION_CACHE_MAGIC; + + *cachep = nc; +} + +void +isc_tlsctx_client_session_cache_attach( + isc_tlsctx_client_session_cache_t *source, + isc_tlsctx_client_session_cache_t **targetp) { + REQUIRE(VALID_TLSCTX_CLIENT_SESSION_CACHE(source)); + REQUIRE(targetp != NULL && *targetp == NULL); + + isc_refcount_increment(&source->references); + + *targetp = source; +} + +static void +client_cache_entry_delete(isc_tlsctx_client_session_cache_t *restrict cache, + client_session_cache_entry_t *restrict entry) { + client_session_cache_bucket_t *restrict bucket = entry->bucket; + + /* Unlink and free the cache entry */ + ISC_LIST_UNLINK(bucket->entries, entry, bucket_link); + ISC_LIST_UNLINK(cache->lru_entries, entry, cache_link); + cache->nentries--; + (void)SSL_SESSION_free(entry->session); + isc_mem_put(cache->mctx, entry, sizeof(*entry)); + + /* The bucket is empty - let's remove it */ + if (ISC_LIST_EMPTY(bucket->entries)) { + RUNTIME_CHECK(isc_ht_delete(cache->buckets, + (const uint8_t *)bucket->bucket_key, + bucket->bucket_key_len) == + ISC_R_SUCCESS); + + isc_mem_free(cache->mctx, bucket->bucket_key); + isc_mem_put(cache->mctx, bucket, sizeof(*bucket)); + } +} + +void +isc_tlsctx_client_session_cache_detach( + isc_tlsctx_client_session_cache_t **cachep) { + isc_tlsctx_client_session_cache_t *cache = NULL; + client_session_cache_entry_t *entry = NULL, *next = NULL; + + REQUIRE(cachep != NULL); + + cache = *cachep; + *cachep = NULL; + + REQUIRE(VALID_TLSCTX_CLIENT_SESSION_CACHE(cache)); + + if (isc_refcount_decrement(&cache->references) != 1) { + return; + } + + cache->magic = 0; + + isc_refcount_destroy(&cache->references); + + entry = ISC_LIST_HEAD(cache->lru_entries); + while (entry != NULL) { + next = ISC_LIST_NEXT(entry, cache_link); + client_cache_entry_delete(cache, entry); + entry = next; + } + + RUNTIME_CHECK(isc_ht_count(cache->buckets) == 0); + isc_ht_destroy(&cache->buckets); + + isc_mutex_destroy(&cache->lock); + isc_tlsctx_free(&cache->ctx); + isc_mem_putanddetach(&cache->mctx, cache, sizeof(*cache)); +} + +static bool +ssl_session_seems_resumable(const SSL_SESSION *sess) { +#ifdef HAVE_SSL_SESSION_IS_RESUMABLE + /* + * If SSL_SESSION_is_resumable() is available, let's use that. It + * is expected to be available on OpenSSL >= 1.1.1 and its modern + * siblings. + */ + return (SSL_SESSION_is_resumable(sess) != 0); +#elif (OPENSSL_VERSION_NUMBER >= 0x10100000L) + /* + * Taking into consideration that OpenSSL 1.1.0 uses opaque + * pointers for SSL_SESSION, we cannot implement a replacement for + * SSL_SESSION_is_resumable() manually. Let's use a sensible + * approximation for that, then: if there is an associated session + * ticket or session ID, then, most likely, the session is + * resumable. + */ + unsigned int session_id_len = 0; + (void)SSL_SESSION_get_id(sess, &session_id_len); + return (SSL_SESSION_has_ticket(sess) || session_id_len > 0); +#else + return (!sess->not_resumable && + (sess->session_id_length > 0 || sess->tlsext_ticklen > 0)); +#endif +} + +void +isc_tlsctx_client_session_cache_keep(isc_tlsctx_client_session_cache_t *cache, + char *remote_peer_name, isc_tls_t *tls) { + size_t name_len; + isc_result_t result; + SSL_SESSION *sess; + client_session_cache_bucket_t *restrict bucket = NULL; + client_session_cache_entry_t *restrict entry = NULL; + + REQUIRE(VALID_TLSCTX_CLIENT_SESSION_CACHE(cache)); + REQUIRE(remote_peer_name != NULL && *remote_peer_name != '\0'); + REQUIRE(tls != NULL); + + sess = SSL_get1_session(tls); + if (sess == NULL) { + ERR_clear_error(); + return; + } else if (!ssl_session_seems_resumable(sess)) { + SSL_SESSION_free(sess); + return; + } + + isc_mutex_lock(&cache->lock); + + name_len = strlen(remote_peer_name); + result = isc_ht_find(cache->buckets, (const uint8_t *)remote_peer_name, + name_len, (void **)&bucket); + + if (result != ISC_R_SUCCESS) { + /* Let's create a new bucket */ + INSIST(bucket == NULL); + bucket = isc_mem_get(cache->mctx, sizeof(*bucket)); + *bucket = (client_session_cache_bucket_t){ + .bucket_key = isc_mem_strdup(cache->mctx, + remote_peer_name), + .bucket_key_len = name_len + }; + ISC_LIST_INIT(bucket->entries); + RUNTIME_CHECK(isc_ht_add(cache->buckets, + (const uint8_t *)remote_peer_name, + name_len, + (void *)bucket) == ISC_R_SUCCESS); + } + + /* Let's add a new cache entry to the new/found bucket */ + entry = isc_mem_get(cache->mctx, sizeof(*entry)); + *entry = (client_session_cache_entry_t){ .session = sess, + .bucket = bucket }; + ISC_LINK_INIT(entry, bucket_link); + ISC_LINK_INIT(entry, cache_link); + + ISC_LIST_APPEND(bucket->entries, entry, bucket_link); + + ISC_LIST_APPEND(cache->lru_entries, entry, cache_link); + cache->nentries++; + + if (cache->nentries > cache->max_entries) { + /* + * Cache overrun. We need to remove the oldest entry from the + * cache + */ + client_session_cache_entry_t *restrict oldest; + INSIST((cache->nentries - 1) == cache->max_entries); + + oldest = ISC_LIST_HEAD(cache->lru_entries); + client_cache_entry_delete(cache, oldest); + } + + isc_mutex_unlock(&cache->lock); +} + +void +isc_tlsctx_client_session_cache_reuse(isc_tlsctx_client_session_cache_t *cache, + char *remote_peer_name, isc_tls_t *tls) { + client_session_cache_bucket_t *restrict bucket = NULL; + client_session_cache_entry_t *restrict entry; + size_t name_len; + isc_result_t result; + + REQUIRE(VALID_TLSCTX_CLIENT_SESSION_CACHE(cache)); + REQUIRE(remote_peer_name != NULL && *remote_peer_name != '\0'); + REQUIRE(tls != NULL); + + isc_mutex_lock(&cache->lock); + + /* Let's find the bucket */ + name_len = strlen(remote_peer_name); + result = isc_ht_find(cache->buckets, (const uint8_t *)remote_peer_name, + name_len, (void **)&bucket); + + if (result != ISC_R_SUCCESS) { + goto exit; + } + + INSIST(bucket != NULL); + + /* + * If the bucket has been found, let's use the newest session from + * the bucket, as it has the highest chance to be successfully + * resumed. + */ + INSIST(!ISC_LIST_EMPTY(bucket->entries)); + entry = ISC_LIST_TAIL(bucket->entries); + RUNTIME_CHECK(SSL_set_session(tls, entry->session) == 1); + client_cache_entry_delete(cache, entry); + +exit: + isc_mutex_unlock(&cache->lock); +} + +void +isc_tlsctx_client_session_cache_keep_sockaddr( + isc_tlsctx_client_session_cache_t *cache, isc_sockaddr_t *remote_peer, + isc_tls_t *tls) { + char peername[ISC_SOCKADDR_FORMATSIZE] = { 0 }; + + REQUIRE(remote_peer != NULL); + + isc_sockaddr_format(remote_peer, peername, sizeof(peername)); + + isc_tlsctx_client_session_cache_keep(cache, peername, tls); +} + +void +isc_tlsctx_client_session_cache_reuse_sockaddr( + isc_tlsctx_client_session_cache_t *cache, isc_sockaddr_t *remote_peer, + isc_tls_t *tls) { + char peername[ISC_SOCKADDR_FORMATSIZE] = { 0 }; + + REQUIRE(remote_peer != NULL); + + isc_sockaddr_format(remote_peer, peername, sizeof(peername)); + + isc_tlsctx_client_session_cache_reuse(cache, peername, tls); +} + +const isc_tlsctx_t * +isc_tlsctx_client_session_cache_getctx( + isc_tlsctx_client_session_cache_t *cache) { + REQUIRE(VALID_TLSCTX_CLIENT_SESSION_CACHE(cache)); + return (cache->ctx); +} + +void +isc_tlsctx_set_random_session_id_context(isc_tlsctx_t *ctx) { + uint8_t session_id_ctx[SSL_MAX_SID_CTX_LENGTH] = { 0 }; + const size_t len = ISC_MIN(20, sizeof(session_id_ctx)); + + REQUIRE(ctx != NULL); + + RUNTIME_CHECK(RAND_bytes(session_id_ctx, len) == 1); + + RUNTIME_CHECK( + SSL_CTX_set_session_id_context(ctx, session_id_ctx, len) == 1); +} 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..58171d9 --- /dev/null +++ b/lib/isc/trampoline.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. + */ + +/*! \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 the + * isc_mem_debugging flags can be changed later causing mismatch between flags + * used for isc_mem_get() and isc_mem_put(). + * + * 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; +thread_local size_t isc_tid_v = SIZE_MAX; +static size_t isc__trampoline_min = 1; +static size_t isc__trampoline_max = 65; + +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); +} + +void +isc__trampoline_initialize(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_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]); +} + +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..616a3dd --- /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(), 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/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); +} |