diff --git a/Makefile.am b/Makefile.am index 64778c9e..8d9c55be 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,205 +1,211 @@ # # Copyright (C) 2010-2025 Red Hat, Inc. All rights reserved. # # Author: Fabio M. Di Nitto # # This software licensed under GPL-2.0+ # MAINTAINERCLEANFILES = Makefile.in aclocal.m4 configure depcomp \ config.guess config.sub missing install-sh \ ltmain.sh compile config.h.in config.h.in~ \ autoscan.log configure.scan test-driver \ m4/libtool.m4 m4/lt~obsolete.m4 m4/ltoptions.m4 \ m4/ltsugar.m4 m4/ltversion.m4 include $(top_srcdir)/build-aux/check.mk AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 SPEC = $(PACKAGE_NAME).spec TARGZFILE = $(PACKAGE_NAME)-$(VERSION).tar.gz EXTRA_DIST = autogen.sh .version \ NOTES_TO_PACKAGE_MAINTAINERS \ $(SPEC).in build-aux SUBDIRS = libnozzle libknet if BUILD_MAN SUBDIRS += man endif dist_doc_DATA = \ COPYING.applications \ COPYING.libraries \ COPYRIGHT \ README.licence \ README all-local: $(SPEC) clean-local: rm -rf $(SPEC) cov* distclean-local: rm -f $(PACKAGE_NAME)-*.tar.* $(PACKAGE_NAME)-*.sha256* tag-* ## make rpm/srpm section. +if BUILD_FOR_SOLARIS +$(SPEC): $(SPEC).in .version config.status + echo "RPM NOT SUPPORTED. skipping" + touch $(SPEC) +else $(SPEC): $(SPEC).in .version config.status rm -f $@-t $@ date="`LC_ALL=C $(UTC_DATE_AT)$(SOURCE_EPOCH) "+%a %b %d %Y"`" && \ gvgver="`cd $(abs_srcdir); build-aux/git-version-gen --fallback $(VERSION) .tarball-version .gitarchivever`" && \ if [ "$$gvgver" = "`echo $$gvgver | sed 's/-/./'`" ];then \ rpmver="$$gvgver" && \ alphatag="" && \ dirty="" && \ numcomm="0"; \ else \ gitver="`echo $$gvgver | sed 's/\(.*\)\./\1-/'`" && \ rpmver=`echo $$gitver | sed 's/-.*//g'` && \ alphatag=`echo $$gvgver | sed 's/[^-]*-\([^-]*\).*/\1/'` && \ numcomm=`echo $$gitver | sed 's/[^-]*-\([^-]*\).*/\1/'` && \ dirty="" && \ if [ "`echo $$gitver | sed 's/^.*-dirty$$//g'`" = "" ];then \ dirty="dirty"; \ fi \ fi && \ if [ -n "$$dirty" ]; then dirty="dirty"; else dirty=""; fi && \ if [ "$$numcomm" = "0" ]; then \ sed \ -e "s#@version@#$$rpmver#g" \ -e "s#%glo.*alpha.*##g" \ -e "s#%glo.*numcomm.*##g" \ -e "s#@dirty@#$$dirty#g" \ -e "s#@date@#$$date#g" \ $(abs_srcdir)/$@.in > $@-t; \ else \ sed \ -e "s#@version@#$$rpmver#g" \ -e "s#@alphatag@#$$alphatag#g" \ -e "s#@numcomm@#$$numcomm#g" \ -e "s#@dirty@#$$dirty#g" \ -e "s#@date@#$$date#g" \ $(abs_srcdir)/$@.in > $@-t; \ fi; \ if [ -z "$$dirty" ]; then sed -i -e "s#%glo.*dirty.*##g" $@-t; fi if BUILD_SCTP sed -i -e "s#@sctp@#bcond_without#g" $@-t else sed -i -e "s#@sctp@#bcond_with#g" $@-t endif if BUILD_CRYPTO_NSS sed -i -e "s#@nss@#bcond_without#g" $@-t else sed -i -e "s#@nss@#bcond_with#g" $@-t endif if BUILD_CRYPTO_OPENSSL sed -i -e "s#@openssl@#bcond_without#g" $@-t else sed -i -e "s#@openssl@#bcond_with#g" $@-t endif if BUILD_COMPRESS_ZLIB sed -i -e "s#@zlib@#bcond_without#g" $@-t else sed -i -e "s#@zlib@#bcond_with#g" $@-t endif if BUILD_COMPRESS_LZ4 sed -i -e "s#@lz4@#bcond_without#g" $@-t else sed -i -e "s#@lz4@#bcond_with#g" $@-t endif if BUILD_COMPRESS_LZO2 sed -i -e "s#@lzo2@#bcond_without#g" $@-t else sed -i -e "s#@lzo2@#bcond_with#g" $@-t endif if BUILD_COMPRESS_LZMA sed -i -e "s#@lzma@#bcond_without#g" $@-t else sed -i -e "s#@lzma@#bcond_with#g" $@-t endif if BUILD_COMPRESS_BZIP2 sed -i -e "s#@bzip2@#bcond_without#g" $@-t else sed -i -e "s#@bzip2@#bcond_with#g" $@-t endif if BUILD_COMPRESS_ZSTD sed -i -e "s#@zstd@#bcond_without#g" $@-t else sed -i -e "s#@zstd@#bcond_with#g" $@-t endif if BUILD_LIBNOZZLE sed -i -e "s#@libnozzle@#bcond_without#g" $@-t else sed -i -e "s#@libnozzle@#bcond_with#g" $@-t endif if BUILD_RUNAUTOGEN sed -i -e "s#@runautogen@#bcond_without#g" $@-t else sed -i -e "s#@runautogen@#bcond_with#g" $@-t endif if OVERRIDE_RPM_DEBUGINFO sed -i -e "s#@overriderpmdebuginfo@#bcond_without#g" $@-t else sed -i -e "s#@overriderpmdebuginfo@#bcond_with#g" $@-t endif if BUILD_RPM_DEBUGINFO sed -i -e "s#@rpmdebuginfo@#bcond_without#g" $@-t else sed -i -e "s#@rpmdebuginfo@#bcond_with#g" $@-t endif if BUILD_MAN sed -i -e "s#@buildman@#bcond_without#g" $@-t else sed -i -e "s#@buildman@#bcond_with#g" $@-t endif if INSTALL_TESTS sed -i -e "s#@installtests@#bcond_without#g" $@-t else sed -i -e "s#@installtests@#bcond_with#g" $@-t endif sed -i -e "s#@defaultadmgroup@#$(DEFAULTADMGROUP)#g" $@-t chmod a-w $@-t mv $@-t $@ rm -f $@-t* +endif $(TARGZFILE): $(MAKE) dist RPMBUILDOPTS = --define "_sourcedir $(abs_builddir)" \ --define "_specdir $(abs_builddir)" \ --define "_builddir $(abs_builddir)" \ --define "_srcrpmdir $(abs_builddir)" \ --define "_rpmdir $(abs_builddir)" srpm: clean $(MAKE) $(SPEC) $(TARGZFILE) rpmbuild $(RPMBUILDOPTS) --nodeps -bs $(SPEC) rpm: clean $(MAKE) $(SPEC) $(TARGZFILE) rpmbuild $(RPMBUILDOPTS) -ba $(SPEC) # release/versioning BUILT_SOURCES = .version .version: echo $(VERSION) > $@-t && mv $@-t $@ dist-hook: gen-ChangeLog echo $(VERSION) > $(distdir)/.tarball-version echo $(SOURCE_EPOCH) > $(distdir)/source_epoch gen_start_date = 2000-01-01 .PHONY: gen-ChangeLog gen-ChangeLog: if test -d $(abs_srcdir)/.git; then \ LC_ALL=C $(top_srcdir)/build-aux/gitlog-to-changelog \ --since=$(gen_start_date) > $(distdir)/cl-t; \ rm -f $(distdir)/ChangeLog; \ mv $(distdir)/cl-t $(distdir)/ChangeLog; \ fi diff --git a/README b/README index deb865aa..349ecb85 100644 --- a/README +++ b/README @@ -1,45 +1,77 @@ # # Copyright (C) 2010-2025 Red Hat, Inc. All rights reserved. # # Author: Fabio M. Di Nitto # # This software licensed under GPL-2.0+ # Upstream resources ------------------ https://github.com/kronosnet/kronosnet/ https://ci.kronosnet.org/ https://projects.clusterlabs.org/project/board/86/ (TODO list and activities tracking) https://drive.google.com/drive/folders/0B_zxAPgZTkM_TklfYzN6a2FYUFE?resourcekey=0-Cfr5D94rZ8LVbeMPGjxbdg&usp=sharing (google shared drive) https://lists.kronosnet.org/mailman3/postorius/lists/users.lists.kronosnet.org/ https://lists.kronosnet.org/mailman3/postorius/lists/devel.lists.kronosnet.org/ https://lists.kronosnet.org/mailman3/postorius/lists/commits.lists.kronosnet.org/ https://kronosnet.org/ (web 0.1 style) IRC: #kronosnet on Libera.Chat Architecture ------------ Please refer to the google shared drive Presentations directory for diagrams and fancy schemas -Running knet on FreeBSD +Running on FreeBSD ----------------------- knet requires big socket buffers and you need to set: kern.ipc.maxsockbuf=18388608 in /etc/sysctl.conf or knet will fail to run. For version 12 (or lower), knet requires also: net.inet.sctp.blackhole=1 in /etc/sysctl.conf or knet will fail to work with SCTP. This sysctl is obsoleted in version 13. libnozzle requires if_tap.ko loaded in the kernel. Please avoid to use ifconfig_DEFAULT in /etc/rc.conf to use DHCP for all interfaces or the dhclient will interfere with libnozzle interface management, causing errors on some operations such as "ifconfig tap down". + +Building on Solaris / Illumos +----------------------------- + +tested on SunOS openindiana-x86-64 5.11 illumos-5b6ecd7fe9: + +# pkg install autoconf automake libtool pkg-config \ + gcc-14 gnu-binutils gnu-coreutils gnu-make \ + check system/mozilla-nss doxygen \ + header-tun tun + +optional: + +# pkg install developer/clang-20 + +GNU tools must be preferred: + +# export PATH=/usr/gnu/bin:$PATH + +mozilla-nss is currently broken in oi-userland and does not ship pkg-config files + +# ./autogen.sh && ./configure --disable-crypto-nss + +# make all -j && make check + +Running on Solaris / Illumos +---------------------------- + +Tune socket buffers for the protocol you intend to use: + +ipadm set-prop -p max_buf=8388608 sctp +ipadm set-prop -p max_buf=8388608 udp diff --git a/configure.ac b/configure.ac index 7d4b0459..ddccb9ad 100644 --- a/configure.ac +++ b/configure.ac @@ -1,475 +1,576 @@ # # Copyright (C) 2010-2025 Red Hat, Inc. All rights reserved. # # Authors: Fabio M. Di Nitto # Federico Simoncelli # # This software licensed under GPL-2.0+ # # -*- Autoconf -*- # Process this file with autoconf to produce a configure script. # AC_PREREQ([2.63]) AC_INIT([kronosnet], m4_esyscmd([build-aux/git-version-gen .tarball-version .gitarchivever]), [devel@lists.kronosnet.org]) # Don't let AC_PROC_CC (invoked by AC_USE_SYSTEM_EXTENSIONS) replace # undefined CFLAGS with -g -O2, overriding our special OPT_CFLAGS. : ${CFLAGS=""} AC_USE_SYSTEM_EXTENSIONS AM_INIT_AUTOMAKE([1.13 dist-bzip2 dist-xz color-tests -Wno-portability subdir-objects]) LT_PREREQ([2.2.6]) + +AC_CANONICAL_HOST # --enable-new-dtags: Use RUNPATH instead of RPATH. # It is necessary to have this done before libtool does linker detection. # See also: https://github.com/kronosnet/kronosnet/issues/107 # --as-needed: Modern systems have builtin ceil() making -lm superfluous but # AC_SEARCH_LIBS can't detect this because it tests with a false prototype -AX_CHECK_LINK_FLAG([-Wl,--enable-new-dtags], - [AM_LDFLAGS=-Wl,--enable-new-dtags], - [AC_MSG_ERROR(["Linker support for --enable-new-dtags is required"])]) +case "$host_os" in + solaris*) + AC_MSG_NOTICE([Skipping --enable-new-dtags check, Solaris' RPATH logic is like RUNPATH on other platforms]) + ;; + *) + AX_CHECK_LINK_FLAG([-Wl,--enable-new-dtags], + [AM_LDFLAGS=-Wl,--enable-new-dtags], + [AC_MSG_ERROR(["Linker support for --enable-new-dtags is required"])]) + ;; +esac AX_CHECK_LINK_FLAG([-Wl,--as-needed], [AM_LDFLAGS="$AM_LDFLAGS -Wl,--as-needed"]) saved_LDFLAGS="$LDFLAGS" LDFLAGS="$AM_LDFLAGS $LDFLAGS" LT_INIT LDFLAGS="$saved_LDFLAGS" AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_SRCDIR([libknet/handle.c]) AC_CONFIG_HEADERS([config.h]) -AC_CANONICAL_HOST - AC_LANG([C]) if test "$prefix" = "NONE"; then prefix="/usr" if test "$localstatedir" = "\${prefix}/var"; then localstatedir="/var" fi if test "$libdir" = "\${exec_prefix}/lib"; then if test -e /usr/lib64; then libdir="/usr/lib64" else libdir="/usr/lib" fi fi fi AC_PROG_AWK AC_PROG_GREP AC_PROG_SED AC_PROG_CPP AC_PROG_CC m4_version_prereq([2.70], [:], [AC_PROG_CC_C99]) if test "x$ac_cv_prog_cc_c99" = "xno"; then AC_MSG_ERROR(["C99 support is required"]) fi AC_PROG_LN_S AC_PROG_INSTALL AC_PROG_MAKE_SET PKG_PROG_PKG_CONFIG AC_CHECK_PROGS([VALGRIND_EXEC], [valgrind]) AM_CONDITIONAL([HAS_VALGRIND], [test x$VALGRIND_EXEC != "x"]) AC_CHECK_PROGS([COVBUILD_EXEC], [cov-build]) AM_CONDITIONAL([HAS_COVBUILD], [test x$COVBUILD_EXEC != "x"]) AC_CHECK_PROGS([COVANALYZE_EXEC], [cov-analyze]) AM_CONDITIONAL([HAS_COVANALYZE], [test x$COVANALYZE_EXEC != "x"]) AC_CHECK_PROGS([COVFORMATERRORS_EXEC], [cov-format-errors]) AM_CONDITIONAL([HAS_COVFORMATERRORS], [test x$COVFORMATERRORS_EXEC != "x"]) # KNET_OPTION_DEFINES(stem,type,detection code) # stem: enters name of option, Automake conditional and preprocessor define # type: compress or crypto, determines where the default comes from AC_DEFUN([KNET_OPTION_DEFINES],[ AC_ARG_ENABLE([$2-$1],[AS_HELP_STRING([--disable-$2-$1],[disable libknet $1 support])],, [enable_$2_$1="$enable_$2_all"]) AM_CONDITIONAL([BUILD_]m4_toupper([$2_$1]),[test "x$enable_$2_$1" = xyes]) if test "x$enable_$2_$1" = xyes; then $3 fi AC_DEFINE_UNQUOTED([WITH_]m4_toupper([$2_$1]), [`test "x$enable_$2_$1" != xyes; echo $?`], $1 $2 [built in]) ]) AC_ARG_ENABLE([man], [AS_HELP_STRING([--disable-man],[disable man page creation])],, [ enable_man="yes" ]) AM_CONDITIONAL([BUILD_MAN], [test x$enable_man = xyes]) AC_ARG_ENABLE([libknet-sctp], [AS_HELP_STRING([--disable-libknet-sctp],[disable libknet SCTP support])],, [ enable_libknet_sctp="yes" ]) AM_CONDITIONAL([BUILD_SCTP], [test x$enable_libknet_sctp = xyes]) AC_ARG_ENABLE([functional-tests], [AS_HELP_STRING([--disable-functional-tests],[disable execution of functional tests, useful for old and slow arches])],, [ enable_functional_tests="yes" ]) AM_CONDITIONAL([RUN_FUN_TESTS], [test x$enable_functional_tests = xyes]) AC_ARG_ENABLE([crypto-all], [AS_HELP_STRING([--disable-crypto-all],[disable libknet all crypto modules support])],, [ enable_crypto_all="yes" ]) KNET_OPTION_DEFINES([nss],[crypto],[PKG_CHECK_MODULES([nss], [nss])]) KNET_OPTION_DEFINES([openssl],[crypto],[PKG_CHECK_MODULES([openssl],[libcrypto])]) AC_ARG_ENABLE([compress-all], [AS_HELP_STRING([--disable-compress-all],[disable libknet all compress modules support])],, [ enable_compress_all="yes" ]) KNET_OPTION_DEFINES([zstd],[compress],[PKG_CHECK_MODULES([libzstd], [libzstd])]) KNET_OPTION_DEFINES([zlib],[compress],[PKG_CHECK_MODULES([zlib], [zlib])]) KNET_OPTION_DEFINES([lz4],[compress],[PKG_CHECK_MODULES([liblz4], [liblz4])]) KNET_OPTION_DEFINES([lzo2],[compress],[ PKG_CHECK_MODULES([lzo2], [lzo2], [# work around broken pkg-config file in v2.10 AC_SUBST([lzo2_CFLAGS],[`echo $lzo2_CFLAGS | sed 's,/lzo *, ,'`])], [AC_CHECK_HEADERS([lzo/lzo1x.h], [AC_CHECK_LIB([lzo2], [lzo1x_decompress_safe], [AC_SUBST([lzo2_LIBS], [-llzo2])])], [AC_MSG_ERROR(["missing required lzo/lzo1x.h header"])])]) ]) KNET_OPTION_DEFINES([lzma],[compress],[PKG_CHECK_MODULES([liblzma], [liblzma])]) KNET_OPTION_DEFINES([bzip2],[compress],[ PKG_CHECK_MODULES([bzip2], [bzip2],, [AC_CHECK_HEADERS([bzlib.h], [AC_CHECK_LIB([bz2], [BZ2_bzBuffToBuffCompress], [AC_SUBST([bzip2_LIBS], [-lbz2])])], [AC_MSG_ERROR(["missing required bzlib.h"])])]) ]) AC_ARG_ENABLE([install-tests], [AS_HELP_STRING([--enable-install-tests],[install tests])],, [ enable_install_tests="no" ]) AM_CONDITIONAL([INSTALL_TESTS], [test x$enable_install_tests = xyes]) AC_ARG_ENABLE([runautogen], [AS_HELP_STRING([--enable-runautogen],[run autogen.sh])],, [ enable_runautogen="no" ]) AM_CONDITIONAL([BUILD_RUNAUTOGEN], [test x$enable_runautogen = xyes]) override_rpm_debuginfo_option="yes" AC_ARG_ENABLE([rpm-debuginfo], [AS_HELP_STRING([--enable-rpm-debuginfo],[build debuginfo packages])],, [ enable_rpm_debuginfo="no", override_rpm_debuginfo_option="no" ]) AM_CONDITIONAL([BUILD_RPM_DEBUGINFO], [test x$enable_rpm_debuginfo = xyes]) AM_CONDITIONAL([OVERRIDE_RPM_DEBUGINFO], [test x$override_rpm_debuginfo_option = xyes]) AC_ARG_ENABLE([libnozzle], [AS_HELP_STRING([--enable-libnozzle],[libnozzle support])],, [ enable_libnozzle="yes" ]) AM_CONDITIONAL([BUILD_LIBNOZZLE], [test x$enable_libnozzle = xyes]) ## local helper functions # this function checks if CC support options passed as # args. Global CPPFLAGS are ignored during this test. cc_supports_flag() { saveCPPFLAGS="$CPPFLAGS" CPPFLAGS="-Werror $@" AC_MSG_CHECKING([whether $CC supports "$@"]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([#include ], [ #ifdef __USE_FORTIFY_LEVEL printf("%d\n", __USE_FORTIFY_LEVEL) #else printf("hello world\n") #endif ])], [RC=0; AC_MSG_RESULT([yes])], [RC=1; AC_MSG_RESULT([no])]) CPPFLAGS="$saveCPPFLAGS" return $RC } # Checks for libraries. AX_PTHREAD(,[AC_MSG_ERROR([POSIX threads support is required])]) saved_LIBS="$LIBS" LIBS= AC_SEARCH_LIBS([ceil], [m], , [AC_MSG_ERROR([ceil not found])]) AC_SUBST([m_LIBS], [$LIBS]) LIBS= AC_SEARCH_LIBS([clock_gettime], [rt], , [AC_MSG_ERROR([clock_gettime not found])]) AC_SUBST([rt_LIBS], [$LIBS]) LIBS= AC_SEARCH_LIBS([dlopen], [dl dld], , [AC_MSG_ERROR([dlopen not found])]) AC_SUBST([dl_LIBS], [$LIBS]) LIBS="$saved_LIBS" # Check RTLD_DI_ORIGIN (not decalred by musl. glibc has it as an enum so cannot use ifdef) AC_CHECK_DECL([RTLD_DI_ORIGIN], [AC_DEFINE([HAVE_RTLD_DI_ORIGIN], 1, [define when RTLD_DI_ORIGIN is declared])], ,[[#include ]]) +AC_CHECK_DECLS([MSG_NOSIGNAL], [], [CFLAGS="$CFLAGS -DMSG_NOSIGNAL=0"], [#include ]) + +AC_CHECK_LIB([socket], [socket], + [AC_SUBST([socket_LIBS], [-lsocket])]) + # OS detection +build_for_solaris=no AC_MSG_CHECKING([for os in ${host_os}]) case "$host_os" in *linux*) AC_DEFINE_UNQUOTED([KNET_LINUX], [1], [Compiling for Linux platform]) AC_MSG_RESULT([Linux]) ;; *bsd*) AC_DEFINE_UNQUOTED([KNET_BSD], [1], [Compiling for BSD platform]) AC_MSG_RESULT([BSD]) ;; + solaris*) + AC_DEFINE_UNQUOTED([KNET_SOLARIS], [1], [Compiling for Solaris platform]) + AC_MSG_RESULT([Solaris]) + build_for_solaris=yes + ;; *) AC_MSG_ERROR([Unsupported OS? hmmmm]) ;; esac +AM_CONDITIONAL([BUILD_FOR_SOLARIS], [test "x$build_for_solaris" = "xyes"]) + # Checks for header files. AC_CHECK_HEADERS([sys/epoll.h]) AC_CHECK_FUNCS([kevent]) # if neither sys/epoll.h nor kevent are present, we should fail. if test "x$ac_cv_header_sys_epoll_h" = xno && test "x$ac_cv_func_kevent" = xno; then AC_MSG_ERROR([Both epoll and kevent unavailable on this OS]) fi if test "x$ac_cv_header_sys_epoll_h" = xyes && test "x$ac_cv_func_kevent" = xyes; then AC_MSG_ERROR([Both epoll and kevent available on this OS, please contact the maintainers to fix the code]) fi if test "x$enable_libknet_sctp" = xyes; then - AC_CHECK_HEADERS([netinet/sctp.h],, [AC_MSG_ERROR(["missing required SCTP headers"])]) + AC_CHECK_HEADERS([netinet/sctp.h],, + AC_CACHE_CHECK([for netinet/sctp.h with netinet/in.h included], [ac_cv_have_sctp], [ + ac_cv_have_sctp=no + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([ + #include + #include + ], [ + sctp_assoc_t my_sctp_assoc; + ])], + [ac_cv_have_sctp=yes], + [AC_MSG_ERROR(["missing required SCTP headers"])]) + ]) + AC_CHECK_LIB([sctp], [sctp_bindx], [sctp_LIBS="-lsctp"]) + if test "x$ac_cv_have_sctp" = xyes; then + AC_DEFINE([HAVE_NETINET_SCTP_H], [1], [define when HAVE_NETINET_SCTP_H is declared]) + fi + ) fi # Checks for typedefs, structures, and compiler characteristics. AC_C_INLINE AC_TYPE_PID_T AC_TYPE_SIZE_T AC_TYPE_SSIZE_T AC_TYPE_UINT8_T AC_TYPE_UINT16_T AC_TYPE_UINT32_T AC_TYPE_UINT64_T AC_TYPE_INT8_T AC_TYPE_INT16_T AC_TYPE_INT32_T AC_TYPE_INT64_T PKG_CHECK_MODULES([libqb], [libqb]) if test "x$enable_man" = "xyes"; then AC_ARG_VAR([DOXYGEN], [override doxygen executable]) AC_CHECK_PROGS([DOXYGEN], [doxygen], [no]) if test "x$DOXYGEN" = xno; then AC_MSG_ERROR(["Doxygen command not found"]) fi AC_ARG_VAR([DOXYGEN2MAN], [override doxygen2man executable]) if test "x$cross_compiling" = "xno"; then PKG_CHECK_VAR([libqb_PREFIX], [libqb], [prefix]) AC_PATH_PROG([DOXYGEN2MAN], [doxygen2man], [no], [$libqb_PREFIX/bin$PATH_SEPARATOR$PATH]) fi if test "x$DOXYGEN2MAN" = "xno"; then # required by doxyxml to build man pages dynamically # Don't let AC_PROC_CC (invoked by AX_PROG_CC_FOR_BUILD) replace # undefined CFLAGS_FOR_BUILD with -g -O2, overriding our special OPT_CFLAGS. : ${CFLAGS_FOR_BUILD=""} AX_PROG_CC_FOR_BUILD saved_PKG_CONFIG="$PKG_CONFIG" saved_ac_cv_path_PKG_CONFIG="$ac_cv_path_PKG_CONFIG" unset PKG_CONFIG ac_cv_path_PKG_CONFIG AC_PATH_PROG([PKG_CONFIG], [pkg-config]) PKG_CHECK_MODULES([libqb_BUILD], [libqb]) PKG_CHECK_VAR([libqb_BUILD_PREFIX], [libqb], [prefix]) AC_PATH_PROG([DOXYGEN2MAN], [doxygen2man], [no], [$libqb_BUILD_PREFIX/bin$PATH_SEPARATOR$PATH]) if test "x$DOXYGEN2MAN" = "xno"; then PKG_CHECK_MODULES([libxml_BUILD], [libxml-2.0]) DOXYGEN2MAN="\${abs_top_builddir}/man/doxyxml" build_doxy=yes fi PKG_CONFIG="$saved_PKG_CONFIG" ac_cv_path_PKG_CONFIG="$saved_ac_cv_path_PKG_CONFIG" fi AC_SUBST([DOXYGEN2MAN]) fi AM_CONDITIONAL([BUILD_DOXYXML], [test "x$build_doxy" = "xyes"]) # checks for libnozzle if test "x$enable_libnozzle" = xyes; then - if `echo $host_os | grep -q linux`; then - PKG_CHECK_MODULES([libnl], [libnl-3.0]) - PKG_CHECK_MODULES([libnlroute], [libnl-route-3.0 >= 3.3], [], - [PKG_CHECK_MODULES([libnlroute], [libnl-route-3.0 < 3.3], - [AC_DEFINE_UNQUOTED([LIBNL3_WORKAROUND], [1], [Enable libnl < 3.3 build workaround])], [])]) - fi + case $host_os in + *linux*) + PKG_CHECK_MODULES([libnl], [libnl-3.0]) + PKG_CHECK_MODULES([libnlroute], [libnl-route-3.0 >= 3.3], [], + [PKG_CHECK_MODULES([libnlroute], [libnl-route-3.0 < 3.3], + [AC_DEFINE_UNQUOTED([LIBNL3_WORKAROUND], [1], [Enable libnl < 3.3 build workaround])], [])]) + ;; + *solaris*) + saved_LIBS="$LIBS" + AC_SEARCH_LIBS([dlpi_open], [dlpi], , [AC_MSG_ERROR([dlpi_open not found])]) + AC_SUBST([dlpi_LIBS], [$LIBS]) + LIBS="$saved_LIBS" + AC_CHECK_HEADERS([net/if_tun.h],, [AC_MSG_ERROR([net/if_tun.h not found])]) + ;; + *) + ;; + esac fi # local options AC_ARG_ENABLE([debug], [AS_HELP_STRING([--enable-debug],[enable debug build])]) AC_ARG_ENABLE([hardening], [AS_HELP_STRING([--disable-hardening],[disable hardening build flags])],, [ enable_hardening="yes" ]) AC_ARG_WITH([testdir], [AS_HELP_STRING([--with-testdir=DIR],[path to /usr/lib../kronosnet/tests/ dir where to install the test suite])], [ TESTDIR="$withval" ], [ TESTDIR="$libdir/kronosnet/tests" ]) ## do subst AC_SUBST([TESTDIR]) # debug build stuff if test "x${enable_debug}" = xyes; then AC_DEFINE_UNQUOTED([DEBUG], [1], [Compiling Debugging code]) OPT_CFLAGS="-O0" else OPT_CFLAGS="-O3" fi +# check flags for CMSG_SPACE() +AC_CACHE_CHECK([for CMSG_SPACE() requiring _XOPEN_SOURCE], [ac_cv_macro_xopen_source], [ + ac_cv_macro_xopen_source=yes + saveCPPFLAGS="$CPPFLAGS" + CPPFLAGS="-Werror $CPPFLAGS" + # First, try to link with strdup() using standard flags + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([ + #include + ], [ + unsigned char c = CMSG_SPACE(1); + ])], [ac_cv_macro_xopen_source=no], [ + # Failure, try again with -D_XOPEN_SOURCE + XOPEN_CPPFLAGS="-D_XOPEN_SOURCE -D_XOPEN_SOURCE_EXTENDED=1" + CPPFLAGS="$CPPFLAGS $XOPEN_CPPFLAGS" + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([ + #include + ], [ + unsigned char c = CMSG_SPACE(1); + ], + [ac_cv_macro_xopen_source=yes], + [AC_MSG_ERROR([no CMSG_SPACE() found])])]) + ]) + CPPFLAGS="$saveCPPFLAGS" +]) + +AC_SUBST([XOPEN_CPPFLAGS]) + # Check for availablility of hardening options annocheck=no if test "x${enable_hardening}" = xyes; then # support only gcc for now if echo $CC | grep -q gcc; then ANNOPLUGIN="-fplugin=annobin" annocheck=yes fi FORTIFY_CFLAGS="" if test "x${enable_debug}" != xyes; then for j in 3 2; do FORTIFY_CFLAGS_TEMP="-D_FORTIFY_SOURCE=$j" if cc_supports_flag "$OPT_CFLAGS $FORTIFY_CFLAGS_TEMP"; then FORTIFY_CFLAGS="$FORTIFY_CFLAGS_TEMP" break fi done fi - HARDENING_CFLAGS_ANNOCHECK="$ANNOPLUGIN -fPIC -DPIC -pie -fstack-protector-strong -fexceptions -D_GLIBCXX_ASSERTIONS -Wl,-z,now" + HARDENING_CFLAGS_ANNOCHECK="$ANNOPLUGIN -fPIC -DPIC" + case "$host_os" in + solaris*) + # no support for -pie when linking, but supported during compile + ;; + *) + HARDENING_CFLAGS_ANNOCHECK="$HARDENING_CFLAGS_ANNOCHECK -pie" + ;; + esac + HARDENING_CFLAGS_ANNOCHECK="$HARDENING_CFLAGS_ANNOCHECK -fstack-protector-strong -fexceptions -D_GLIBCXX_ASSERTIONS -Wl,-z,now" HARDENING_CFLAGS="-fstack-clash-protection -fcf-protection=full -mcet -mstackrealign" EXTRA_HARDENING_CFLAGS="" # check for annobin required cflags/ldflags for j in $HARDENING_CFLAGS_ANNOCHECK; do if cc_supports_flag $j; then EXTRA_HARDENING_CFLAGS="$EXTRA_HARDENING_CFLAGS $j" else annocheck=no fi done # check for other hardening cflags/ldflags for j in $HARDENING_CFLAGS; do if cc_supports_flag $j; then EXTRA_HARDENING_CFLAGS="$EXTRA_HARDENING_CFLAGS $j" fi done EXTRA_HARDENING_CFLAGS="$EXTRA_HARDENING_CFLAGS $FORTIFY_CFLAGS" # check if annocheck binary is available if test "x${annocheck}" = xyes; then AC_CHECK_PROGS([ANNOCHECK_EXEC], [annocheck]) if test "x${ANNOCHECK_EXEC}" = x; then annocheck=no fi fi AM_LDFLAGS="$AM_LDFLAGS $EXTRA_HARDENING_CFLAGS" fi if test "x${enable_debug}" = xyes; then annocheck=no fi AM_CONDITIONAL([HAS_ANNOCHECK], [test "x$annocheck" = "xyes"]) # gdb flags if test "x${GCC}" = xyes; then GDB_CFLAGS="-ggdb3" else GDB_CFLAGS="-g" fi DEFAULT_CFLAGS="-Werror -Wall -Wextra" # manual overrides # generates too much noise for stub APIs -UNWANTED_CFLAGS="-Wno-unused-parameter" +UNWANTED_CFLAGS="-Wno-unused-parameter -Wno-gnu-folding-constant" + +# check for character index warning +AC_CACHE_CHECK([if -Wchar-subscripts warns for int8_t], [ac_cv_int8_t_warn], [ + saveCFLAGS="$CFLAGS" + CFLAGS="-Werror -Wchar-subscripts" + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([#include ], [ + char arr[256]; + uint8_t i = 200; + arr[i] = 'J'; + return 0; + ])], + [ac_cv_int8_t_warn=no], + [ac_cv_int8_t_warn=yes]) + CFLAGS="$saveCFLAGS" +]) +if test "x$ac_cv_int8_t_warn" = xyes; then + UNWANTED_CFLAGS="$UNWANTED_CFLAGS -Wno-char-subscripts" +fi AC_SUBST([AM_CFLAGS],["$OPT_CFLAGS $GDB_CFLAGS $DEFAULT_CFLAGS $EXTRA_HARDENING_CFLAGS $UNWANTED_CFLAGS"]) AC_SUBST([AM_LDFLAGS]) AX_PROG_DATE AS_IF([test "$ax_cv_prog_date_gnu_date:$ax_cv_prog_date_gnu_utc" = yes:yes], [UTC_DATE_AT="date -u -d@"], [AS_IF([test "x$ax_cv_prog_date_bsd_date" = xyes], [UTC_DATE_AT="date -u -r"], [AC_MSG_ERROR([date utility unable to convert epoch to UTC])])]) AC_SUBST([UTC_DATE_AT]) AC_ARG_VAR([SOURCE_EPOCH],[last modification date of the source]) AC_MSG_NOTICE([trying to determine source epoch]) AC_MSG_CHECKING([for source epoch in \$SOURCE_EPOCH]) AS_IF([test -n "$SOURCE_EPOCH"], [AC_MSG_RESULT([yes])], [AC_MSG_RESULT([no]) AC_MSG_CHECKING([for source epoch in source_epoch file]) AS_IF([test -e "$srcdir/source_epoch"], [read SOURCE_EPOCH <"$srcdir/source_epoch" AC_MSG_RESULT([yes])], [AC_MSG_RESULT([no]) AC_MSG_CHECKING([for source epoch baked in by gitattributes export-subst]) SOURCE_EPOCH='$Format:%at$' # template for rewriting by git-archive AS_CASE([$SOURCE_EPOCH], [?Format:*], # was not rewritten [AC_MSG_RESULT([no]) AC_MSG_CHECKING([for source epoch in \$SOURCE_DATE_EPOCH]) AS_IF([test "x$SOURCE_DATE_EPOCH" != x], [SOURCE_EPOCH="$SOURCE_DATE_EPOCH" AC_MSG_RESULT([yes])], [AC_MSG_RESULT([no]) AC_MSG_CHECKING([whether git log can provide a source epoch]) SOURCE_EPOCH=f${SOURCE_EPOCH#\$F} # convert into git log --pretty format SOURCE_EPOCH=$(cd "$srcdir" && git log -1 --pretty=${SOURCE_EPOCH%$} 2>/dev/null) AS_IF([test -n "$SOURCE_EPOCH"], [AC_MSG_RESULT([yes])], [AC_MSG_RESULT([no, using current time and breaking reproducibility]) SOURCE_EPOCH=$(date +%s)])])], [AC_MSG_RESULT([yes])] )]) ]) AC_MSG_NOTICE([using source epoch $($UTC_DATE_AT$SOURCE_EPOCH +'%F %T %Z')]) AC_CONFIG_FILES([ Makefile libnozzle/Makefile libnozzle/libnozzle.pc libnozzle/tests/Makefile libknet/Makefile libknet/libknet.pc libknet/tests/Makefile man/Makefile man/Doxyfile-knet man/Doxyfile-nozzle ]) if test "x$VERSION" = "xUNKNOWN"; then AC_MSG_ERROR([m4_text_wrap([ configure was unable to determine the source tree's current version. This generally happens when using git archive (or the github download button) generated tarball/zip file. In order to workaround this issue, either use git clone https://github.com/kronosnet/kronosnet.git or use an official release tarball, available at https://kronosnet.org/releases/. Alternatively you can add a compatible version in a .tarball-version file at the top of the source tree, wipe your autom4te.cache dir and generated configure, and rerun autogen.sh. ], [ ], [ ], [76])]) fi AC_OUTPUT diff --git a/libknet/Makefile.am b/libknet/Makefile.am index 3790c3a8..dd98fbb8 100644 --- a/libknet/Makefile.am +++ b/libknet/Makefile.am @@ -1,170 +1,176 @@ # # Copyright (C) 2010-2025 Red Hat, Inc. All rights reserved. # # Authors: Fabio M. Di Nitto # Federico Simoncelli # # This software licensed under GPL-2.0+ # MAINTAINERCLEANFILES = Makefile.in include $(top_srcdir)/build-aux/check.mk SYMFILE = libknet_exported_syms EXTRA_DIST = $(SYMFILE) SUBDIRS = . tests # https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html -libversion = 6:0:5 +libknetversion = 6:0:5 # override global LIBS that pulls in lots of craft we don't need here LIBS = sources = \ common.c \ compat.c \ compress.c \ crypto.c \ handle.c \ handle_api.c \ host.c \ lib_config.c \ links.c \ links_acl.c \ links_acl_ip.c \ links_acl_loopback.c \ logging.c \ netutils.c \ onwire.c \ threads_common.c \ threads_dsthandler.c \ threads_heartbeat.c \ threads_pmtud.c \ threads_rx.c \ threads_tx.c \ transports.c \ transport_common.c \ transport_loopback.c \ transport_udp.c \ transport_sctp.c include_HEADERS = libknet.h pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = libknet.pc noinst_HEADERS = \ common.h \ compat.h \ compress.h \ compress_model.h \ crypto.h \ crypto_model.h \ host.h \ internals.h \ links.h \ links_acl.h \ links_acl_ip.h \ links_acl_loopback.h \ logging.h \ netutils.h \ onwire.h \ threads_common.h \ threads_dsthandler.h \ threads_heartbeat.h \ threads_pmtud.h \ threads_rx.h \ threads_tx.h \ transports.h \ transport_common.h \ transport_loopback.h \ transport_udp.h \ transport_sctp.h lib_LTLIBRARIES = libknet.la libknet_la_SOURCES = $(sources) -AM_CFLAGS += $(libqb_CFLAGS) +AM_CFLAGS += $(XOPEN_CPPFLAGS) $(libqb_CFLAGS) libknet_la_CFLAGS = $(AM_CFLAGS) $(PTHREAD_CFLAGS) \ -DPLUGINPATH="\"$(pkglibdir)\"" EXTRA_libknet_la_DEPENDENCIES = $(SYMFILE) +if BUILD_FOR_SOLARIS libknet_la_LDFLAGS = $(AM_LDFLAGS) \ - -Wl,--version-script=$(srcdir)/$(SYMFILE) \ - -version-info $(libversion) + -Wl,-M$(abs_srcdir)/$(SYMFILE) \ + -version-info $(libknetversion) +else +libknet_la_LDFLAGS = $(AM_LDFLAGS) \ + -Wl,--version-script=$(abs_srcdir)/$(SYMFILE) \ + -version-info $(libknetversion) +endif -libknet_la_LIBADD = $(PTHREAD_LIBS) $(dl_LIBS) $(rt_LIBS) $(m_LIBS) +libknet_la_LIBADD = $(PTHREAD_LIBS) $(socket_LIBS) $(dl_LIBS) $(rt_LIBS) $(m_LIBS) check-local: check-annocheck-libs # Prepare empty value for appending pkglib_LTLIBRARIES = # MODULE_LDFLAGS would mean a target-specific variable for Automake MODULELDFLAGS = $(AM_LDFLAGS) -module -avoid-version -export-dynamic if BUILD_COMPRESS_ZSTD pkglib_LTLIBRARIES += compress_zstd.la compress_zstd_la_LDFLAGS = $(MODULELDFLAGS) compress_zstd_la_CFLAGS = $(AM_CFLAGS) $(libzstd_CFLAGS) compress_zstd_la_LIBADD = $(libzstd_LIBS) endif if BUILD_COMPRESS_ZLIB pkglib_LTLIBRARIES += compress_zlib.la compress_zlib_la_LDFLAGS = $(MODULELDFLAGS) compress_zlib_la_CFLAGS = $(AM_CFLAGS) $(zlib_CFLAGS) compress_zlib_la_LIBADD = $(zlib_LIBS) endif if BUILD_COMPRESS_LZ4 pkglib_LTLIBRARIES += compress_lz4.la compress_lz4hc.la compress_lz4_la_LDFLAGS = $(MODULELDFLAGS) compress_lz4_la_CFLAGS = $(AM_CFLAGS) $(liblz4_CFLAGS) compress_lz4_la_LIBADD = $(liblz4_LIBS) compress_lz4hc_la_LDFLAGS = $(MODULELDFLAGS) compress_lz4hc_la_CFLAGS = $(AM_CFLAGS) $(liblz4_CFLAGS) compress_lz4hc_la_LIBADD = $(liblz4_LIBS) endif if BUILD_COMPRESS_LZO2 pkglib_LTLIBRARIES += compress_lzo2.la compress_lzo2_la_LDFLAGS = $(MODULELDFLAGS) compress_lzo2_la_CFLAGS = $(AM_CFLAGS) $(lzo2_CFLAGS) compress_lzo2_la_LIBADD = $(lzo2_LIBS) endif if BUILD_COMPRESS_LZMA pkglib_LTLIBRARIES += compress_lzma.la compress_lzma_la_LDFLAGS = $(MODULELDFLAGS) compress_lzma_la_CFLAGS = $(AM_CFLAGS) $(liblzma_CFLAGS) compress_lzma_la_LIBADD = $(liblzma_LIBS) endif if BUILD_COMPRESS_BZIP2 pkglib_LTLIBRARIES += compress_bzip2.la compress_bzip2_la_LDFLAGS = $(MODULELDFLAGS) compress_bzip2_la_CFLAGS = $(AM_CFLAGS) $(bzip2_CFLAGS) compress_bzip2_la_LIBADD = $(bzip2_LIBS) endif if BUILD_CRYPTO_NSS pkglib_LTLIBRARIES += crypto_nss.la crypto_nss_la_LDFLAGS = $(MODULELDFLAGS) crypto_nss_la_CFLAGS = $(AM_CFLAGS) $(nss_CFLAGS) crypto_nss_la_LIBADD = $(nss_LIBS) endif if BUILD_CRYPTO_OPENSSL pkglib_LTLIBRARIES += crypto_openssl.la crypto_openssl_la_LDFLAGS = $(MODULELDFLAGS) crypto_openssl_la_CFLAGS = $(AM_CFLAGS) $(openssl_CFLAGS) crypto_openssl_la_LIBADD = $(openssl_LIBS) endif diff --git a/libknet/compress.c b/libknet/compress.c index 7cb703bd..9108e145 100644 --- a/libknet/compress.c +++ b/libknet/compress.c @@ -1,544 +1,544 @@ /* * Copyright (C) 2017-2025 Red Hat, Inc. All rights reserved. * * Author: Fabio M. Di Nitto * * This software licensed under LGPL-2.0+ */ #include "config.h" #include #include #include #include #include #include "internals.h" #include "compress.h" #include "compress_model.h" #include "logging.h" #include "threads_common.h" #include "common.h" /* * internal module switch data */ /* * DO NOT CHANGE MODEL_ID HERE OR ONWIRE COMPATIBILITY * WILL BREAK! * * Always add new items before the last NULL. */ static compress_model_t compress_modules_cmds[KNET_MAX_COMPRESS_METHODS + 1] = { { "none" , 0, 0, 0, NULL }, { "zlib" , 1, WITH_COMPRESS_ZLIB , 0, NULL }, { "lz4" , 2, WITH_COMPRESS_LZ4 , 0, NULL }, { "lz4hc", 3, WITH_COMPRESS_LZ4 , 0, NULL }, { "lzo2" , 4, WITH_COMPRESS_LZO2 , 0, NULL }, { "lzma" , 5, WITH_COMPRESS_LZMA , 0, NULL }, { "bzip2", 6, WITH_COMPRESS_BZIP2, 0, NULL }, { "zstd" , 7, WITH_COMPRESS_ZSTD, 0, NULL }, { NULL, KNET_MAX_COMPRESS_METHODS, 0, 0, NULL } }; -static int max_model = 0; +static unsigned int max_model = 0; static struct timespec last_load_failure; static int compress_get_model(const char *model) { int idx = 0; while (compress_modules_cmds[idx].model_name != NULL) { if (!strcmp(compress_modules_cmds[idx].model_name, model)) { return compress_modules_cmds[idx].model_id; } idx++; } return -1; } static int compress_get_max_model(void) { int idx = 0; while (compress_modules_cmds[idx].model_name != NULL) { idx++; } return idx - 1; } static int compress_is_valid_model(int compress_model) { int idx = 0; while (compress_modules_cmds[idx].model_name != NULL) { if ((compress_model == compress_modules_cmds[idx].model_id) && (compress_modules_cmds[idx].built_in == 1)) { return 0; } idx++; } return -1; } static int val_level( knet_handle_t knet_h, int compress_model, int compress_level) { if (compress_modules_cmds[compress_model].ops->val_level != NULL) { return compress_modules_cmds[compress_model].ops->val_level(knet_h, compress_level); } return 0; } /* * compress_check_lib_is_init needs to be invoked in a locked context! */ static int compress_check_lib_is_init(knet_handle_t knet_h, int cmp_model) { /* * lack of a .is_init function means that the module does not require * init per handle so we use a fake reference in the compress_int_data * to identify that we already increased the libref for this handle */ if (compress_modules_cmds[cmp_model].loaded == 1) { if (compress_modules_cmds[cmp_model].ops->is_init == NULL) { if (knet_h->compress_int_data[cmp_model] != NULL) { return 1; } } else { if (compress_modules_cmds[cmp_model].ops->is_init(knet_h, cmp_model) == 1) { return 1; } } } return 0; } /* * compress_load_lib should _always_ be invoked in write lock context */ static int compress_load_lib(knet_handle_t knet_h, int cmp_model, int rate_limit) { struct timespec clock_now; unsigned long long timediff; /* * checking again for paranoia and because * compress_check_lib_is_init is usually invoked in read context * and we need to switch from read to write locking in between. * another thread might have init the library in the meantime */ if (compress_check_lib_is_init(knet_h, cmp_model)) { return 0; } /* * due to the fact that decompress can load libraries * on demand, depending on the compress model selected * on other nodes, it is possible for an attacker * to send crafted packets to attempt to load libraries * at random in a DoS fashion. * If there is an error loading a library, then we want * to rate_limit a retry to reload the library every X * seconds to avoid a lock DoS that could greatly slow * down libknet. */ if (rate_limit) { if ((last_load_failure.tv_sec != 0) || (last_load_failure.tv_nsec != 0)) { clock_gettime(CLOCK_MONOTONIC, &clock_now); timespec_diff(last_load_failure, clock_now, &timediff); if (timediff < 10000000000) { errno = EAGAIN; return -1; } } } if (compress_modules_cmds[cmp_model].loaded == 0) { compress_modules_cmds[cmp_model].ops = load_module (knet_h, "compress", compress_modules_cmds[cmp_model].model_name); if (!compress_modules_cmds[cmp_model].ops) { clock_gettime(CLOCK_MONOTONIC, &last_load_failure); return -1; } if (compress_modules_cmds[cmp_model].ops->abi_ver != KNET_COMPRESS_MODEL_ABI) { log_err(knet_h, KNET_SUB_COMPRESS, "ABI mismatch loading module %s. knet ver: %d, module ver: %d", compress_modules_cmds[cmp_model].model_name, KNET_COMPRESS_MODEL_ABI, compress_modules_cmds[cmp_model].ops->abi_ver); errno = EINVAL; return -1; } compress_modules_cmds[cmp_model].loaded = 1; } if (compress_modules_cmds[cmp_model].ops->init != NULL) { if (compress_modules_cmds[cmp_model].ops->init(knet_h, cmp_model) < 0) { return -1; } } else { knet_h->compress_int_data[cmp_model] = (void *)&"1"; } return 0; } static int compress_lib_test(knet_handle_t knet_h) { int savederrno = 0; unsigned char src[KNET_DATABUFSIZE]; unsigned char dst[KNET_DATABUFSIZE_COMPRESS]; ssize_t dst_comp_len = KNET_DATABUFSIZE_COMPRESS, dst_decomp_len = KNET_DATABUFSIZE; unsigned int i; int request_level; memset(src, 0, KNET_DATABUFSIZE); memset(dst, 0, KNET_DATABUFSIZE_COMPRESS); /* * NOTE: we cannot use compress and decompress API calls due to locking * so we need to call directly into the modules */ if (compress_modules_cmds[knet_h->compress_model].ops->compress(knet_h, src, KNET_DATABUFSIZE, dst, &dst_comp_len) < 0) { savederrno = errno; log_err(knet_h, KNET_SUB_COMPRESS, "Unable to compress test buffer. Please check your compression settings: %s", strerror(savederrno)); errno = savederrno; return -1; } else if ((long unsigned int)dst_comp_len >= KNET_DATABUFSIZE) { /* * compress not effective, try again using default compression level when available */ request_level = knet_h->compress_level; log_warn(knet_h, KNET_SUB_COMPRESS, "Requested compression level (%d) did not generate any compressed data (source: %zu destination: %zu)", request_level, sizeof(src), dst_comp_len); if ((!compress_modules_cmds[knet_h->compress_model].ops->get_default_level()) || ((knet_h->compress_level = compress_modules_cmds[knet_h->compress_model].ops->get_default_level()) == KNET_COMPRESS_UNKNOWN_DEFAULT)) { log_err(knet_h, KNET_SUB_COMPRESS, "compression %s does not provide a default value", compress_modules_cmds[knet_h->compress_model].model_name); errno = EINVAL; return -1; } else { memset(src, 0, KNET_DATABUFSIZE); memset(dst, 0, KNET_DATABUFSIZE_COMPRESS); dst_comp_len = KNET_DATABUFSIZE_COMPRESS; if (compress_modules_cmds[knet_h->compress_model].ops->compress(knet_h, src, KNET_DATABUFSIZE, dst, &dst_comp_len) < 0) { savederrno = errno; log_err(knet_h, KNET_SUB_COMPRESS, "Unable to compress with default compression level: %s", strerror(savederrno)); errno = savederrno; return -1; } log_warn(knet_h, KNET_SUB_COMPRESS, "Requested compression level (%d) did not work, switching to default (%d)", request_level, knet_h->compress_level); } } if (compress_modules_cmds[knet_h->compress_model].ops->decompress(knet_h, dst, dst_comp_len, src, &dst_decomp_len) < 0) { savederrno = errno; log_err(knet_h, KNET_SUB_COMPRESS, "Unable to decompress test buffer. Please check your compression settings: %s", strerror(savederrno)); errno = savederrno; return -1; } for (i = 0; i < KNET_DATABUFSIZE; i++) { if (src[i] != 0) { log_err(knet_h, KNET_SUB_COMPRESS, "Decompressed buffer contains incorrect data"); errno = EINVAL; return -1; } } return 0; } int compress_init( knet_handle_t knet_h) { max_model = compress_get_max_model(); if (max_model > KNET_MAX_COMPRESS_METHODS) { log_err(knet_h, KNET_SUB_COMPRESS, "Too many compress methods defined in compress.c."); errno = EINVAL; return -1; } memset(&last_load_failure, 0, sizeof(struct timespec)); return 0; } static int compress_cfg( knet_handle_t knet_h, struct knet_handle_compress_cfg *knet_handle_compress_cfg) { int savederrno = 0, err = 0; int cmp_model; cmp_model = compress_get_model(knet_handle_compress_cfg->compress_model); if (cmp_model < 0) { log_err(knet_h, KNET_SUB_COMPRESS, "compress model %s not supported", knet_handle_compress_cfg->compress_model); errno = EINVAL; return -1; } log_debug(knet_h, KNET_SUB_COMPRESS, "Initializing compress module [%s/%d/%u]", knet_handle_compress_cfg->compress_model, knet_handle_compress_cfg->compress_level, knet_handle_compress_cfg->compress_threshold); if (cmp_model > 0) { if (compress_modules_cmds[cmp_model].built_in == 0) { log_err(knet_h, KNET_SUB_COMPRESS, "compress model %s support has not been built in. Please contact your vendor or fix the build", knet_handle_compress_cfg->compress_model); errno = EINVAL; return -1; } if (knet_handle_compress_cfg->compress_threshold > KNET_MAX_PACKET_SIZE) { log_err(knet_h, KNET_SUB_COMPRESS, "compress threshold cannot be higher than KNET_MAX_PACKET_SIZE (%d).", KNET_MAX_PACKET_SIZE); errno = EINVAL; return -1; } if (knet_handle_compress_cfg->compress_threshold == 0) { knet_h->compress_threshold = KNET_COMPRESS_THRESHOLD; log_debug(knet_h, KNET_SUB_COMPRESS, "resetting compression threshold to default (%d)", KNET_COMPRESS_THRESHOLD); } else { knet_h->compress_threshold = knet_handle_compress_cfg->compress_threshold; } savederrno = pthread_rwlock_rdlock(&shlib_rwlock); if (savederrno) { log_err(knet_h, KNET_SUB_COMPRESS, "Unable to get read lock: %s", strerror(savederrno)); errno = savederrno; return -1; } if (!compress_check_lib_is_init(knet_h, cmp_model)) { /* * need to switch to write lock, load the lib, and return with a write lock * this is not racy because compress_load_lib is written idempotent. */ pthread_rwlock_unlock(&shlib_rwlock); savederrno = pthread_rwlock_wrlock(&shlib_rwlock); if (savederrno) { log_err(knet_h, KNET_SUB_COMPRESS, "Unable to get write lock: %s", strerror(savederrno)); errno = savederrno; return -1; } if (compress_load_lib(knet_h, cmp_model, 0) < 0) { savederrno = errno; log_err(knet_h, KNET_SUB_COMPRESS, "Unable to load library: %s", strerror(savederrno)); err = -1; goto out_unlock; } } if (val_level(knet_h, cmp_model, knet_handle_compress_cfg->compress_level) < 0) { log_err(knet_h, KNET_SUB_COMPRESS, "compress level %d not supported for model %s", knet_handle_compress_cfg->compress_level, knet_handle_compress_cfg->compress_model); savederrno = EINVAL; err = -1; goto out_unlock; } knet_h->compress_model = cmp_model; knet_h->compress_level = knet_handle_compress_cfg->compress_level; if (compress_lib_test(knet_h) < 0) { savederrno = errno; err = -1; goto out_unlock; } out_unlock: pthread_rwlock_unlock(&shlib_rwlock); } if (err) { knet_h->compress_model = 0; knet_h->compress_level = 0; } errno = savederrno; return err; } void compress_fini( knet_handle_t knet_h, int all) { int savederrno = 0; - int idx = 0; + unsigned int idx = 0; savederrno = pthread_rwlock_wrlock(&shlib_rwlock); if (savederrno) { log_err(knet_h, KNET_SUB_COMPRESS, "Unable to get write lock: %s", strerror(savederrno)); return; } while (idx < KNET_MAX_COMPRESS_METHODS) { if ((compress_modules_cmds[idx].model_name != NULL) && (compress_modules_cmds[idx].built_in == 1) && (compress_modules_cmds[idx].loaded == 1) && (compress_modules_cmds[idx].model_id > 0) && (knet_h->compress_int_data[idx] != NULL)) { if ((all) || (compress_modules_cmds[idx].model_id == knet_h->compress_model)) { if (compress_modules_cmds[idx].ops->fini != NULL) { compress_modules_cmds[idx].ops->fini(knet_h, idx); } else { knet_h->compress_int_data[idx] = NULL; } } } idx++; } pthread_rwlock_unlock(&shlib_rwlock); return; } /* * compress does not require compress_check_lib_is_init * because it's protected by compress_cfg */ int compress( knet_handle_t knet_h, const unsigned char *buf_in, const ssize_t buf_in_len, unsigned char *buf_out, ssize_t *buf_out_len) { return compress_modules_cmds[knet_h->compress_model].ops->compress(knet_h, buf_in, buf_in_len, buf_out, buf_out_len); } int decompress( knet_handle_t knet_h, - int compress_model, + unsigned int compress_model, const unsigned char *buf_in, const ssize_t buf_in_len, unsigned char *buf_out, ssize_t *buf_out_len) { int savederrno = 0, err = 0; if (compress_model > max_model) { log_err(knet_h, KNET_SUB_COMPRESS, "Received packet with unknown compress model %d", compress_model); errno = EINVAL; return -1; } if (compress_is_valid_model(compress_model) < 0) { log_err(knet_h, KNET_SUB_COMPRESS, "Received packet compressed with %s but support is not built in this version of libknet. Please contact your distribution vendor or fix the build.", compress_modules_cmds[compress_model].model_name); errno = EINVAL; return -1; } savederrno = pthread_rwlock_rdlock(&shlib_rwlock); if (savederrno) { log_err(knet_h, KNET_SUB_COMPRESS, "Unable to get read lock: %s", strerror(savederrno)); errno = savederrno; return -1; } if (!compress_check_lib_is_init(knet_h, compress_model)) { /* * need to switch to write lock, load the lib, and return with a write lock * this is not racy because compress_load_lib is written idempotent. */ pthread_rwlock_unlock(&shlib_rwlock); savederrno = pthread_rwlock_wrlock(&shlib_rwlock); if (savederrno) { log_err(knet_h, KNET_SUB_COMPRESS, "Unable to get write lock: %s", strerror(savederrno)); errno = savederrno; return -1; } if (compress_load_lib(knet_h, compress_model, 1) < 0) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_COMPRESS, "Unable to load library: %s", strerror(savederrno)); goto out_unlock; } } err = compress_modules_cmds[compress_model].ops->decompress(knet_h, buf_in, buf_in_len, buf_out, buf_out_len); savederrno = errno; out_unlock: pthread_rwlock_unlock(&shlib_rwlock); errno = savederrno; return err; } int knet_handle_compress(knet_handle_t knet_h, struct knet_handle_compress_cfg *knet_handle_compress_cfg) { int savederrno = 0; int err = 0; if (!_is_valid_handle(knet_h)) { return -1; } if (!knet_handle_compress_cfg) { errno = EINVAL; return -1; } savederrno = get_global_wrlock(knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to get write lock: %s", strerror(savederrno)); errno = savederrno; return -1; } compress_fini(knet_h, 0); err = compress_cfg(knet_h, knet_handle_compress_cfg); savederrno = errno; pthread_rwlock_unlock(&knet_h->global_rwlock); errno = err ? savederrno : 0; return err; } int knet_get_compress_list(struct knet_compress_info *compress_list, size_t *compress_list_entries) { int err = 0; int idx = 0; int outidx = 0; if (!compress_list_entries) { errno = EINVAL; return -1; } while (compress_modules_cmds[idx].model_name != NULL) { if (compress_modules_cmds[idx].built_in) { if (compress_list) { compress_list[outidx].name = compress_modules_cmds[idx].model_name; } outidx++; } idx++; } *compress_list_entries = outidx; if (!err) errno = 0; return err; } diff --git a/libknet/compress.h b/libknet/compress.h index 5bb28428..393d4d49 100644 --- a/libknet/compress.h +++ b/libknet/compress.h @@ -1,36 +1,36 @@ /* * Copyright (C) 2017-2025 Red Hat, Inc. All rights reserved. * * Author: Fabio M. Di Nitto * * This software licensed under LGPL-2.0+ */ #ifndef __KNET_COMPRESS_H__ #define __KNET_COMPRESS_H__ #include "internals.h" int compress_init( knet_handle_t knet_h); void compress_fini( knet_handle_t knet_h, int all); int compress( knet_handle_t knet_h, const unsigned char *buf_in, const ssize_t buf_in_len, unsigned char *buf_out, ssize_t *buf_out_len); int decompress( knet_handle_t knet_h, - int compress_model, + unsigned int compress_model, const unsigned char *buf_in, const ssize_t buf_in_len, unsigned char *buf_out, ssize_t *buf_out_len); #endif diff --git a/libknet/handle.c b/libknet/handle.c index 3755927b..79521b89 100644 --- a/libknet/handle.c +++ b/libknet/handle.c @@ -1,765 +1,765 @@ /* * Copyright (C) 2010-2025 Red Hat, Inc. All rights reserved. * * Authors: Fabio M. Di Nitto * Federico Simoncelli * * This software licensed under LGPL-2.0+ */ #include "config.h" #include #include #include #include #include #include #include #include #include "internals.h" #include "crypto.h" #include "links.h" #include "compress.h" #include "compat.h" #include "common.h" #include "threads_common.h" #include "threads_heartbeat.h" #include "threads_pmtud.h" #include "threads_dsthandler.h" #include "threads_rx.h" #include "threads_tx.h" #include "transports.h" #include "transport_common.h" #include "logging.h" static int _init_locks(knet_handle_t knet_h) { int savederrno = 0; savederrno = pthread_rwlock_init(&knet_h->global_rwlock, NULL); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to initialize list rwlock: %s", strerror(savederrno)); goto exit_fail; } savederrno = pthread_mutex_init(&knet_h->handle_stats_mutex, NULL); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to initialize handle stats mutex: %s", strerror(savederrno)); goto exit_fail; } savederrno = pthread_mutex_init(&knet_h->threads_status_mutex, NULL); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to initialize threads status mutex: %s", strerror(savederrno)); goto exit_fail; } savederrno = pthread_mutex_init(&knet_h->pmtud_mutex, NULL); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to initialize pmtud mutex: %s", strerror(savederrno)); goto exit_fail; } savederrno = pthread_mutex_init(&knet_h->kmtu_mutex, NULL); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to initialize kernel_mtu mutex: %s", strerror(savederrno)); goto exit_fail; } savederrno = pthread_cond_init(&knet_h->pmtud_cond, NULL); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to initialize pmtud conditional mutex: %s", strerror(savederrno)); goto exit_fail; } savederrno = pthread_mutex_init(&knet_h->hb_mutex, NULL); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to initialize hb_thread mutex: %s", strerror(savederrno)); goto exit_fail; } savederrno = pthread_mutex_init(&knet_h->tx_mutex, NULL); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to initialize tx_thread mutex: %s", strerror(savederrno)); goto exit_fail; } savederrno = pthread_mutex_init(&knet_h->backoff_mutex, NULL); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to initialize pong timeout backoff mutex: %s", strerror(savederrno)); goto exit_fail; } savederrno = pthread_mutex_init(&knet_h->tx_seq_num_mutex, NULL); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to initialize tx_seq_num_mutex mutex: %s", strerror(savederrno)); goto exit_fail; } return 0; exit_fail: errno = savederrno; return -1; } static void _destroy_locks(knet_handle_t knet_h) { pthread_rwlock_destroy(&knet_h->global_rwlock); pthread_mutex_destroy(&knet_h->pmtud_mutex); pthread_mutex_destroy(&knet_h->kmtu_mutex); pthread_cond_destroy(&knet_h->pmtud_cond); pthread_mutex_destroy(&knet_h->hb_mutex); pthread_mutex_destroy(&knet_h->tx_mutex); pthread_mutex_destroy(&knet_h->backoff_mutex); pthread_mutex_destroy(&knet_h->tx_seq_num_mutex); pthread_mutex_destroy(&knet_h->threads_status_mutex); pthread_mutex_destroy(&knet_h->handle_stats_mutex); } static int _init_socks(knet_handle_t knet_h) { int savederrno = 0; if (_init_socketpair(knet_h, knet_h->dstsockfd)) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to initialize internal dstsockpair: %s", strerror(savederrno)); goto exit_fail; } return 0; exit_fail: errno = savederrno; return -1; } static void _close_socks(knet_handle_t knet_h) { _close_socketpair(knet_h, knet_h->dstsockfd); } static int _init_buffers(knet_handle_t knet_h) { int savederrno = 0; - int i; + unsigned int i; size_t bufsize; for (i = 0; i < PCKT_FRAG_MAX; i++) { bufsize = ceil((float)KNET_MAX_PACKET_SIZE / (i + 1)) + KNET_HEADER_ALL_SIZE; knet_h->send_to_links_buf[i] = malloc(bufsize); if (!knet_h->send_to_links_buf[i]) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to allocate memory datafd to link buffer: %s", strerror(savederrno)); goto exit_fail; } memset(knet_h->send_to_links_buf[i], 0, bufsize); } for (i = 0; i < PCKT_RX_BUFS; i++) { knet_h->recv_from_links_buf[i] = malloc(KNET_DATABUFSIZE); if (!knet_h->recv_from_links_buf[i]) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to allocate memory for link to datafd buffer: %s", strerror(savederrno)); goto exit_fail; } memset(knet_h->recv_from_links_buf[i], 0, KNET_DATABUFSIZE); } knet_h->recv_from_sock_buf = malloc(KNET_DATABUFSIZE); if (!knet_h->recv_from_sock_buf) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to allocate memory for app to datafd buffer: %s", strerror(savederrno)); goto exit_fail; } memset(knet_h->recv_from_sock_buf, 0, KNET_DATABUFSIZE); knet_h->pingbuf = malloc(KNET_HEADER_PING_SIZE); if (!knet_h->pingbuf) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to allocate memory for hearbeat buffer: %s", strerror(savederrno)); goto exit_fail; } memset(knet_h->pingbuf, 0, KNET_HEADER_PING_SIZE); knet_h->pmtudbuf = malloc(KNET_PMTUD_SIZE_V6 + KNET_HEADER_ALL_SIZE); if (!knet_h->pmtudbuf) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to allocate memory for pmtud buffer: %s", strerror(savederrno)); goto exit_fail; } memset(knet_h->pmtudbuf, 0, KNET_PMTUD_SIZE_V6 + KNET_HEADER_ALL_SIZE); for (i = 0; i < PCKT_FRAG_MAX; i++) { bufsize = ceil((float)KNET_MAX_PACKET_SIZE / (i + 1)) + KNET_HEADER_ALL_SIZE + KNET_DATABUFSIZE_CRYPT_PAD; knet_h->send_to_links_buf_crypt[i] = malloc(bufsize); if (!knet_h->send_to_links_buf_crypt[i]) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to allocate memory for crypto datafd to link buffer: %s", strerror(savederrno)); goto exit_fail; } memset(knet_h->send_to_links_buf_crypt[i], 0, bufsize); } knet_h->recv_from_links_buf_decrypt = malloc(KNET_DATABUFSIZE_CRYPT); if (!knet_h->recv_from_links_buf_decrypt) { savederrno = errno; log_err(knet_h, KNET_SUB_CRYPTO, "Unable to allocate memory for crypto link to datafd buffer: %s", strerror(savederrno)); goto exit_fail; } memset(knet_h->recv_from_links_buf_decrypt, 0, KNET_DATABUFSIZE_CRYPT); knet_h->recv_from_links_buf_crypt = malloc(KNET_DATABUFSIZE_CRYPT); if (!knet_h->recv_from_links_buf_crypt) { savederrno = errno; log_err(knet_h, KNET_SUB_CRYPTO, "Unable to allocate memory for crypto link to datafd buffer: %s", strerror(savederrno)); goto exit_fail; } memset(knet_h->recv_from_links_buf_crypt, 0, KNET_DATABUFSIZE_CRYPT); knet_h->pingbuf_crypt = malloc(KNET_DATABUFSIZE_CRYPT); if (!knet_h->pingbuf_crypt) { savederrno = errno; log_err(knet_h, KNET_SUB_CRYPTO, "Unable to allocate memory for crypto hearbeat buffer: %s", strerror(savederrno)); goto exit_fail; } memset(knet_h->pingbuf_crypt, 0, KNET_DATABUFSIZE_CRYPT); knet_h->pmtudbuf_crypt = malloc(KNET_DATABUFSIZE_CRYPT); if (!knet_h->pmtudbuf_crypt) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to allocate memory for crypto pmtud buffer: %s", strerror(savederrno)); goto exit_fail; } memset(knet_h->pmtudbuf_crypt, 0, KNET_DATABUFSIZE_CRYPT); knet_h->recv_from_links_buf_decompress = malloc(KNET_DATABUFSIZE_COMPRESS); if (!knet_h->recv_from_links_buf_decompress) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to allocate memory for decompress buffer: %s", strerror(savederrno)); goto exit_fail; } memset(knet_h->recv_from_links_buf_decompress, 0, KNET_DATABUFSIZE_COMPRESS); knet_h->send_to_links_buf_compress = malloc(KNET_DATABUFSIZE_COMPRESS); if (!knet_h->send_to_links_buf_compress) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to allocate memory for compress buffer: %s", strerror(savederrno)); goto exit_fail; } memset(knet_h->send_to_links_buf_compress, 0, KNET_DATABUFSIZE_COMPRESS); memset(knet_h->knet_transport_fd_tracker, 0, sizeof(knet_h->knet_transport_fd_tracker)); for (i = 0; i < KNET_MAX_FDS; i++) { knet_h->knet_transport_fd_tracker[i].transport = KNET_MAX_TRANSPORTS; } return 0; exit_fail: errno = savederrno; return -1; } static void _destroy_buffers(knet_handle_t knet_h) { - int i; + unsigned int i; for (i = 0; i < PCKT_FRAG_MAX; i++) { free(knet_h->send_to_links_buf[i]); free(knet_h->send_to_links_buf_crypt[i]); } for (i = 0; i < PCKT_RX_BUFS; i++) { free(knet_h->recv_from_links_buf[i]); } free(knet_h->recv_from_links_buf_decompress); free(knet_h->send_to_links_buf_compress); free(knet_h->recv_from_sock_buf); free(knet_h->recv_from_links_buf_decrypt); free(knet_h->recv_from_links_buf_crypt); free(knet_h->pingbuf); free(knet_h->pingbuf_crypt); free(knet_h->pmtudbuf); free(knet_h->pmtudbuf_crypt); } static int _init_epolls(knet_handle_t knet_h) { struct epoll_event ev; int savederrno = 0; /* * even if the kernel does dynamic allocation with epoll_ctl * we need to reserve one extra for host to host communication */ knet_h->send_to_links_epollfd = epoll_create(KNET_EPOLL_MAX_EVENTS + 1); if (knet_h->send_to_links_epollfd < 0) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to create epoll datafd to link fd: %s", strerror(savederrno)); goto exit_fail; } knet_h->recv_from_links_epollfd = epoll_create(KNET_EPOLL_MAX_EVENTS); if (knet_h->recv_from_links_epollfd < 0) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to create epoll link to datafd fd: %s", strerror(savederrno)); goto exit_fail; } knet_h->dst_link_handler_epollfd = epoll_create(KNET_EPOLL_MAX_EVENTS); if (knet_h->dst_link_handler_epollfd < 0) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to create epoll dst cache fd: %s", strerror(savederrno)); goto exit_fail; } if (_fdset_cloexec(knet_h->send_to_links_epollfd)) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to set CLOEXEC on datafd to link epoll fd: %s", strerror(savederrno)); goto exit_fail; } if (_fdset_cloexec(knet_h->recv_from_links_epollfd)) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to set CLOEXEC on link to datafd epoll fd: %s", strerror(savederrno)); goto exit_fail; } if (_fdset_cloexec(knet_h->dst_link_handler_epollfd)) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to set CLOEXEC on dst cache epoll fd: %s", strerror(savederrno)); goto exit_fail; } memset(&ev, 0, sizeof(struct epoll_event)); ev.events = EPOLLIN; ev.data.fd = knet_h->dstsockfd[0]; if (epoll_ctl(knet_h->dst_link_handler_epollfd, EPOLL_CTL_ADD, knet_h->dstsockfd[0], &ev)) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to add dstsockfd[0] to epoll pool: %s", strerror(savederrno)); goto exit_fail; } return 0; exit_fail: errno = savederrno; return -1; } static void _close_epolls(knet_handle_t knet_h) { struct epoll_event ev; int i; memset(&ev, 0, sizeof(struct epoll_event)); for (i = 0; i < KNET_DATAFD_MAX; i++) { if (knet_h->sockfd[i].in_use) { epoll_ctl(knet_h->send_to_links_epollfd, EPOLL_CTL_DEL, knet_h->sockfd[i].sockfd[knet_h->sockfd[i].is_created], &ev); if (knet_h->sockfd[i].sockfd[knet_h->sockfd[i].is_created]) { _close_socketpair(knet_h, knet_h->sockfd[i].sockfd); } } } epoll_ctl(knet_h->dst_link_handler_epollfd, EPOLL_CTL_DEL, knet_h->dstsockfd[0], &ev); close(knet_h->send_to_links_epollfd); close(knet_h->recv_from_links_epollfd); close(knet_h->dst_link_handler_epollfd); } static int _start_threads(knet_handle_t knet_h) { int savederrno = 0; pthread_attr_t attr; set_thread_status(knet_h, KNET_THREAD_PMTUD, KNET_THREAD_REGISTERED); savederrno = pthread_attr_init(&attr); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to init pthread attributes: %s", strerror(savederrno)); goto exit_fail; } savederrno = pthread_attr_setstacksize(&attr, KNET_THREAD_STACK_SIZE); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to set stack size attribute: %s", strerror(savederrno)); goto exit_fail; } savederrno = pthread_create(&knet_h->pmtud_link_handler_thread, &attr, _handle_pmtud_link_thread, (void *) knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to start pmtud link thread: %s", strerror(savederrno)); goto exit_fail; } set_thread_status(knet_h, KNET_THREAD_DST_LINK, KNET_THREAD_REGISTERED); savederrno = pthread_create(&knet_h->dst_link_handler_thread, &attr, _handle_dst_link_handler_thread, (void *) knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to start dst cache thread: %s", strerror(savederrno)); goto exit_fail; } set_thread_status(knet_h, KNET_THREAD_TX, KNET_THREAD_REGISTERED); savederrno = pthread_create(&knet_h->send_to_links_thread, &attr, _handle_send_to_links_thread, (void *) knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to start datafd to link thread: %s", strerror(savederrno)); goto exit_fail; } set_thread_status(knet_h, KNET_THREAD_RX, KNET_THREAD_REGISTERED); savederrno = pthread_create(&knet_h->recv_from_links_thread, &attr, _handle_recv_from_links_thread, (void *) knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to start link to datafd thread: %s", strerror(savederrno)); goto exit_fail; } set_thread_status(knet_h, KNET_THREAD_HB, KNET_THREAD_REGISTERED); savederrno = pthread_create(&knet_h->heartbt_thread, &attr, _handle_heartbt_thread, (void *) knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to start heartbeat thread: %s", strerror(savederrno)); goto exit_fail; } savederrno = pthread_attr_destroy(&attr); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to destroy pthread attributes: %s", strerror(savederrno)); /* * Do not return error code. Error is not critical. */ } return 0; exit_fail: errno = savederrno; return -1; } static void _stop_threads(knet_handle_t knet_h) { void *retval; wait_all_threads_status(knet_h, KNET_THREAD_STOPPED); if (knet_h->heartbt_thread) { pthread_cancel(knet_h->heartbt_thread); pthread_join(knet_h->heartbt_thread, &retval); } if (knet_h->send_to_links_thread) { pthread_cancel(knet_h->send_to_links_thread); pthread_join(knet_h->send_to_links_thread, &retval); } if (knet_h->recv_from_links_thread) { pthread_cancel(knet_h->recv_from_links_thread); pthread_join(knet_h->recv_from_links_thread, &retval); } if (knet_h->dst_link_handler_thread) { pthread_cancel(knet_h->dst_link_handler_thread); pthread_join(knet_h->dst_link_handler_thread, &retval); } if (knet_h->pmtud_link_handler_thread) { pthread_cancel(knet_h->pmtud_link_handler_thread); pthread_join(knet_h->pmtud_link_handler_thread, &retval); } } knet_handle_t knet_handle_new_ex(knet_node_id_t host_id, int log_fd, uint8_t default_log_level, uint64_t flags) { knet_handle_t knet_h; int savederrno = 0; struct rlimit cur; if (getrlimit(RLIMIT_NOFILE, &cur) < 0) { return NULL; } if ((log_fd < 0) || ((unsigned int)log_fd >= cur.rlim_max)) { errno = EINVAL; return NULL; } /* * validate incoming request */ if ((log_fd) && (default_log_level > KNET_LOG_DEBUG)) { errno = EINVAL; return NULL; } if (flags > KNET_HANDLE_FLAG_PRIVILEGED * 2 - 1) { errno = EINVAL; return NULL; } /* * allocate handle */ knet_h = malloc(sizeof(struct knet_handle)); if (!knet_h) { errno = ENOMEM; return NULL; } memset(knet_h, 0, sizeof(struct knet_handle)); /* * setting up some handle data so that we can use logging * also when initializing the library global locks * and trackers */ knet_h->flags = flags; /* * copy config in place */ knet_h->host_id = host_id; knet_h->logfd = log_fd; if (knet_h->logfd > 0) { memset(&knet_h->log_levels, default_log_level, KNET_MAX_SUBSYSTEMS); } /* * set pmtud default timers */ knet_h->pmtud_interval = KNET_PMTUD_DEFAULT_INTERVAL; /* * set transports reconnect default timers */ knet_h->reconnect_int = KNET_TRANSPORT_DEFAULT_RECONNECT_INTERVAL; /* * Set the default path for plugins */ knet_h->plugin_path = PLUGINPATH; /* * Set 'min' stats to the maximum value so the * first value we get is always less */ knet_h->stats.tx_compress_time_min = UINT64_MAX; knet_h->stats.rx_compress_time_min = UINT64_MAX; knet_h->stats.tx_crypt_time_min = UINT64_MAX; knet_h->stats.rx_crypt_time_min = UINT64_MAX; /* * init global shared bits */ savederrno = pthread_mutex_lock(&handle_config_mutex); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to get handle mutex lock: %s", strerror(savederrno)); free(knet_h); knet_h = NULL; errno = savederrno; return NULL; } if (!handle_list_init) { qb_list_init(&handle_list.head); handle_list_init = 1; } qb_list_add(&knet_h->list, &handle_list.head); /* * init global shlib tracker */ if (_init_shlib_tracker(knet_h) < 0) { savederrno = errno; log_err(knet_h, KNET_SUB_HANDLE, "Unable to init handle tracker: %s", strerror(savederrno)); errno = savederrno; pthread_mutex_unlock(&handle_config_mutex); goto exit_fail; } pthread_mutex_unlock(&handle_config_mutex); /* * init main locking structures */ if (_init_locks(knet_h)) { savederrno = errno; goto exit_fail; } /* * init sockets */ if (_init_socks(knet_h)) { savederrno = errno; goto exit_fail; } /* * allocate packet buffers */ if (_init_buffers(knet_h)) { savederrno = errno; goto exit_fail; } if (compress_init(knet_h)) { savederrno = errno; goto exit_fail; } /* * create epoll fds */ if (_init_epolls(knet_h)) { savederrno = errno; goto exit_fail; } /* * start transports */ if (start_all_transports(knet_h)) { savederrno = errno; goto exit_fail; } /* * start internal threads */ if (_start_threads(knet_h)) { savederrno = errno; goto exit_fail; } wait_all_threads_status(knet_h, KNET_THREAD_STARTED); errno = 0; return knet_h; exit_fail: knet_handle_free(knet_h); errno = savederrno; return NULL; } knet_handle_t knet_handle_new(knet_node_id_t host_id, int log_fd, uint8_t default_log_level) { return knet_handle_new_ex(host_id, log_fd, default_log_level, KNET_HANDLE_FLAG_PRIVILEGED); } int knet_handle_free(knet_handle_t knet_h) { int savederrno = 0; if (!_is_valid_handle(knet_h)) { return -1; } savederrno = get_global_wrlock(knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to get write lock: %s", strerror(savederrno)); errno = savederrno; return -1; } if (knet_h->host_head != NULL) { savederrno = EBUSY; log_err(knet_h, KNET_SUB_HANDLE, "Unable to free handle: host(s) or listener(s) are still active: %s", strerror(savederrno)); pthread_rwlock_unlock(&knet_h->global_rwlock); errno = savederrno; return -1; } knet_h->fini_in_progress = 1; pthread_rwlock_unlock(&knet_h->global_rwlock); _stop_threads(knet_h); stop_all_transports(knet_h); _close_epolls(knet_h); _destroy_buffers(knet_h); _close_socks(knet_h); crypto_fini(knet_h, KNET_MAX_CRYPTO_INSTANCES + 1); /* values above MAX_CRYPTO will release all crypto resources */ compress_fini(knet_h, 1); _destroy_locks(knet_h); (void)pthread_mutex_lock(&handle_config_mutex); qb_list_del(&knet_h->list); _fini_shlib_tracker(); pthread_mutex_unlock(&handle_config_mutex); free(knet_h); knet_h = NULL; errno = 0; return 0; } diff --git a/libknet/libknet.h b/libknet/libknet.h index 643a5aea..60be5a6d 100644 --- a/libknet/libknet.h +++ b/libknet/libknet.h @@ -1,2484 +1,2485 @@ /* * Copyright (C) 2010-2025 Red Hat, Inc. All rights reserved. * * Authors: Fabio M. Di Nitto * Federico Simoncelli * * This software licensed under LGPL-2.0+ */ #ifndef __LIBKNET_H__ #define __LIBKNET_H__ #include #include #include #include #include +#include /** * @file libknet.h * @brief kronosnet API include file * @copyright Copyright (C) 2010-2025 Red Hat, Inc. All rights reserved. * * Kronosnet is an advanced VPN system for High Availability applications. */ #define KNET_API_VER 1 /* * libknet limits */ /** typedef for a knet node */ typedef uint16_t knet_node_id_t; /* * Maximum number of hosts */ #define KNET_MAX_HOST 65536 /* * Maximum number of links between 2 hosts */ #define KNET_MAX_LINK 8 /* * Maximum packet size that should be written to datafd * see knet_handle_new for details */ #define KNET_MAX_PACKET_SIZE 65536 /* * Buffers used for pretty logging * host is used to store both ip addresses and hostnames */ #define KNET_MAX_HOST_LEN 256 #define KNET_MAX_PORT_LEN 6 /* * Some notifications can be generated either on TX or RX */ #define KNET_NOTIFY_TX 0 #define KNET_NOTIFY_RX 1 /* * Link flags */ /* * Where possible, set traffic priority to high. * On Linux this sets the TOS to INTERACTIVE (6), * see tc-prio(8) for more infomation * A dscp value may be configured, see knet_handle_setprio_dscp. */ #define KNET_LINK_FLAG_TRAFFICHIPRIO (1ULL << 0) /* * Handle flags */ /* * Use privileged operations during socket setup. */ #define KNET_HANDLE_FLAG_PRIVILEGED (1ULL << 0) /** * Opaque handle for this knet connection, created with knet_handle_new() and * freed with knet_handle_free() */ typedef struct knet_handle *knet_handle_t; /* * Handle structs/API calls */ /** * knet_handle_new_ex * * @brief create a new instance of a knet handle * * host_id - Each host in a knet is identified with a unique * ID. when creating a new handle local host_id * must be specified (0 to UINT16_MAX are all valid). * It is the user's responsibility to check that the value * is unique, or bad things might happen. * * log_fd - Write file descriptor. If set to a value > 0, it will be used * to write log packets from libknet to the application. * Setting to 0 will disable logging from libknet. * It is possible to enable logging at any given time (see logging API). * Make sure to either read from this filedescriptor properly and/or * mark it O_NONBLOCK, otherwise if the fd becomes full, libknet could * block. * It is strongly encouraged to use pipes (ex: pipe(2) or pipe2(2)) for * logging fds due to the atomic nature of writes between fds. * See also libknet test suite for reference and guidance. * The caller is responsible for management of the FD. eg. knet will not * close it when knet_handle_free(3) is called * * default_log_level - * If logfd is specified, it will initialize all subsystems to log * at default_log_level value. (see logging API) * * flags - bitwise OR of some of the following flags: * KNET_HANDLE_FLAG_PRIVILEGED: use privileged operations setting up the * communication sockets. If disabled, failure to acquire large * enough socket buffers is ignored but logged. Inadequate buffers * lead to poor performance. * * @return * on success, a new knet_handle_t is returned. * on failure, NULL is returned and errno is set. * knet-specific errno values: * ENAMETOOLONG - socket buffers couldn't be set big enough and KNET_HANDLE_FLAG_PRIVILEGED was specified * ERANGE - buffer size readback returned unexpected type */ knet_handle_t knet_handle_new_ex(knet_node_id_t host_id, int log_fd, uint8_t default_log_level, uint64_t flags); /** * knet_handle_new * * @brief knet_handle_new_ex with flags = KNET_HANDLE_FLAG_PRIVILEGED. */ knet_handle_t knet_handle_new(knet_node_id_t host_id, int log_fd, uint8_t default_log_level); /** * knet_handle_free * * @brief Destroy a knet handle, free all resources * * knet_h - pointer to knet_handle_t * * @return * knet_handle_free returns * 0 on success * -1 on error and errno is set. */ int knet_handle_free(knet_handle_t knet_h); /** * knet_handle_enable_sock_notify * * @brief Register a callback to receive socket events * * knet_h - pointer to knet_handle_t * * sock_notify_fn_private_data * void pointer to data that can be used to identify * the callback. * * sock_notify_fn * A callback function that is invoked every time * a socket in the datafd pool will report an error (-1) * or an end of read (0) (see socket.7). * This function MUST NEVER block or add substantial delays. * The callback is invoked in an internal unlocked area * to allow calls to knet_handle_add_datafd/knet_handle_remove_datafd * to swap/replace the bad fd. * if both err and errno are 0, it means that the socket * has received a 0 byte packet (EOF?). * The callback function must either remove the fd from knet * (by calling knet_handle_remove_fd()) or dup a new fd in its place. * Failure to do this can cause problems. * * @return * knet_handle_enable_sock_notify returns * 0 on success * -1 on error and errno is set. */ int knet_handle_enable_sock_notify(knet_handle_t knet_h, void *sock_notify_fn_private_data, void (*sock_notify_fn) ( void *private_data, int datafd, int8_t channel, uint8_t tx_rx, int error, int errorno)); /* sorry! can't call it errno ;) */ #define KNET_DATAFD_MAX 32 /** * knet_handle_add_datafd * * @brief Install a file descriptor for communication * * IMPORTANT: In order to add datafd to knet, knet_handle_enable_sock_notify * _MUST_ be set and be able to handle both errors (-1) and * 0 bytes read / write from the provided datafd. * On read error (< 0) from datafd, the socket is automatically * removed from polling to avoid spinning on dead sockets. * It is safe to call knet_handle_remove_datafd even on sockets * that have been removed. * * knet_h - pointer to knet_handle_t * * *datafd - read/write file descriptor. * knet will read data here to send to the other hosts * and will write data received from the network. * Each data packet can be of max size KNET_MAX_PACKET_SIZE! * Applications using knet_send/knet_recv will receive a * proper error if the packet size is not within boundaries. * Applications using their own functions to write to the * datafd should NOT write more than KNET_MAX_PACKET_SIZE. * * Please refer to handle.c on how to set up a socketpair. * * datafd can be 0, and knet_handle_add_datafd will create a properly * populated socket pair the same way as ping_test, or a value * higher than 0. A negative number will return an error. * On exit knet_handle_free will take care to cleanup the * socketpair only if they have been created by knet_handle_add_datafd. * * It is possible to pass either sockets or normal fds. * User provided datafd will be marked as non-blocking and close-on-exec. * * *channel - This value is analogous to the tag in VLAN tagging. * A negative value will auto-allocate a channel. * Setting a value between 0 and 31 will try to allocate that * specific channel (unless already in use). * * It is possible to add up to 32 datafds but be aware that each * one of them must have a receiving end on the other host. * * Example: * hostA channel 0 will be delivered to datafd on hostB channel 0 * hostA channel 1 to hostB channel 1. * * Each channel must have a unique file descriptor. * * If your application could have 2 channels on one host and one * channel on another host, then you can use dst_host_filter * to manipulate channel values on TX and RX. * * @return * knet_handle_add_datafd returns * @retval 0 on success, * *datafd will be populated with a socket if the original value was 0 * or if a specific fd was set, the value is untouched. * *channel will be populated with a channel number if the original value * was negative or the value is untouched if a specific channel * was requested. * * @retval -1 on error and errno is set. * *datafd and *channel are untouched or empty. */ int knet_handle_add_datafd(knet_handle_t knet_h, int *datafd, int8_t *channel); /** * knet_handle_remove_datafd * * @brief Remove a file descriptor from knet * * knet_h - pointer to knet_handle_t * * datafd - file descriptor to remove. * NOTE that if the socket/fd was created by knet_handle_add_datafd, * the socket will be closed by libknet. * * @return * knet_handle_remove_datafd returns * 0 on success * -1 on error and errno is set. */ int knet_handle_remove_datafd(knet_handle_t knet_h, int datafd); /** * knet_handle_get_channel * * @brief Get the channel associated with a file descriptor * * knet_h - pointer to knet_handle_t * * datafd - get the channel associated to this datafd * * *channel - will contain the result * * @return * knet_handle_get_channel returns * @retval 0 on success * and *channel will contain the result * @retval -1 on error and errno is set. * and *channel content is meaningless */ int knet_handle_get_channel(knet_handle_t knet_h, const int datafd, int8_t *channel); /** * knet_handle_get_datafd * * @brief Get the file descriptor associated with a channel * * knet_h - pointer to knet_handle_t * * channel - get the datafd associated to this channel * * *datafd - will contain the result * * @return * knet_handle_get_datafd returns * @retval 0 on success * and *datafd will contain the results * @retval -1 on error and errno is set. * and *datafd content is meaningless */ int knet_handle_get_datafd(knet_handle_t knet_h, const int8_t channel, int *datafd); /** * knet_recv * * @brief Receive data from knet nodes * * knet_h - pointer to knet_handle_t * * buff - pointer to buffer to store the received data * * buff_len - buffer length * * channel - channel number * * @return * knet_recv is a commodity function to wrap iovec operations * around a socket. It returns a call to readv(2). */ ssize_t knet_recv(knet_handle_t knet_h, char *buff, const size_t buff_len, const int8_t channel); /** * knet_send * * @brief Send data to knet nodes * * knet_h - pointer to knet_handle_t * * buff - pointer to the buffer of data to send * * buff_len - length of data to send * * channel - channel number * * @return * knet_send is a commodity function to wrap iovec operations * around a socket. It returns a call to writev(2). */ ssize_t knet_send(knet_handle_t knet_h, const char *buff, const size_t buff_len, const int8_t channel); /** * knet_send_sync * * @brief Synchronously send data to knet nodes * * knet_h - pointer to knet_handle_t * * buff - pointer to the buffer of data to send * * buff_len - length of data to send * * channel - data channel to use (see knet_handle_add_datafd(3)) * * All knet RX/TX operations are async for performance reasons. * There are applications that might need a sync version of data * transmission and receive errors in case of failure to deliver * to another host. * knet_send_sync bypasses the whole TX async layer and delivers * data directly to the link layer, and returns errors accordingly. * knet_send_sync sends only one packet to one host at a time. * It does NOT support multiple destinations or multicast packets. * Decision is still based on dst_host_filter_fn. * * @return * knet_send_sync returns 0 on success and -1 on error. * In addition to normal sendmmsg errors, knet_send_sync can fail * due to: * * @retval ECANCELED - data forward is disabled * @retval EFAULT - dst_host_filter fatal error * @retval EINVAL - dst_host_filter did not provide dst_host_ids_entries on unicast pckts * @retval E2BIG - dst_host_filter did return more than one dst_host_ids_entries on unicast pckts * @retval ENOMSG - received unknown message type * @retval EHOSTDOWN - unicast pckt cannot be delivered because dest host is not connected yet * @retval ECHILD - crypto failed * @retval EAGAIN - sendmmsg was unable to send all messages and there was no progress during retry * @retval ENETDOWN - a packet filter was not installed (necessary for knet_send_sync, but not knet_send) */ int knet_send_sync(knet_handle_t knet_h, const char *buff, const size_t buff_len, const int8_t channel); /** * knet_handle_enable_filter * * @brief install a filter to route packets * * knet_h - pointer to knet_handle_t * * dst_host_filter_fn_private_data * void pointer to data that can be used to identify * the callback. * * dst_host_filter_fn - * is a callback function that is invoked every time * a packet hits datafd (see knet_handle_new(3)). * the function allows users to tell libknet where the * packet has to be delivered. * * const unsigned char *outdata - is a pointer to the * current packet * ssize_t outdata_len - length of the above data * uint8_t tx_rx - filter is called on tx or rx * (KNET_NOTIFY_TX, KNET_NOTIFY_RX) * knet_node_id_t this_host_id - host_id processing the packet * knet_node_id_t src_host_id - host_id that generated the * packet * knet_node_id_t *dst_host_ids - array of KNET_MAX_HOST knet_node_id_t * where to store the destinations * (uninitialized by caller, callee should never * read it) * size_t *dst_host_ids_entries - number of hosts to send the message * * dst_host_filter_fn should return * -1 on error, packet is discarded. * 0 packet is unicast and should be sent to dst_host_ids and there are * dst_host_ids_entries in the buffer. * 1 packet is broadcast/multicast and is sent all hosts. * contents of dst_host_ids and dst_host_ids_entries are ignored. * * @return * knet_handle_enable_filter returns * 0 on success * -1 on error and errno is set. */ int knet_handle_enable_filter(knet_handle_t knet_h, void *dst_host_filter_fn_private_data, int (*dst_host_filter_fn) ( void *private_data, const unsigned char *outdata, ssize_t outdata_len, uint8_t tx_rx, knet_node_id_t this_host_id, knet_node_id_t src_host_id, int8_t *channel, knet_node_id_t *dst_host_ids, size_t *dst_host_ids_entries)); /** * knet_handle_setfwd * * @brief Start packet forwarding * * knet_h - pointer to knet_handle_t * * enable - set to 1 to allow data forwarding, 0 to disable data forwarding. * * @return * knet_handle_setfwd returns * 0 on success * -1 on error and errno is set. * * By default data forwarding is off and no traffic will pass through knet until * it is set on. */ int knet_handle_setfwd(knet_handle_t knet_h, unsigned int enabled); /** * knet_handle_setprio_dscp * * @brief Use dscp for IP_TOS on socket to implement KNET_LINK_FLAG_TRAFFICHIPRIO * * knet_h - pointer to knet_handle_t * * dscp - dscp value to set on all new sockets * * This function must be called prior to configure knet links. * * It disables the use of IPTOS_LOWDELAY and uses the given dscp value in the * IP header's TOS field instead. * * Setting dscp to 0 reverts to using IPTOS_LOWDELAY. * * @return * knet_handle_setprio_dscp returns * 0 on success * -1 on error and errno is set. */ int knet_handle_setprio_dscp(knet_handle_t knet_h, uint8_t dscp); /** * knet_handle_enable_access_lists * * @brief Enable or disable usage of access lists (default: off) * * knet_h - pointer to knet_handle_t * * enable - set to 1 to use access lists, 0 to disable access_lists. * * @return * knet_handle_enable_access_lists returns * 0 on success * -1 on error and errno is set. * * access lists are bound to links. There are 2 types of links: * 1) point to point, where both source and destinations are well known * at configuration time. * 2) open links, where only the source is known at configuration time. * * knet will automatically generate access lists for point to point links. * * For open links, knet provides 4 API calls to manipulate access lists: * knet_link_add_acl(3), knet_link_rm_acl(3), knet_link_insert_acl(3) * and knet_link_clear_acl(3). * Those API calls will work exclusively on open links as they * are of no use on point to point links. * * knet will not enforce any access list unless specifically enabled by * knet_handle_enable_access_lists(3). * * From a security / programming perspective we recommend: * - create the knet handle * - enable access lists * - configure hosts and links * - configure access lists for open links */ int knet_handle_enable_access_lists(knet_handle_t knet_h, unsigned int enabled); #define KNET_PMTUD_DEFAULT_INTERVAL 60 /** * knet_handle_pmtud_setfreq * * @brief Set the interval between PMTUd scans * * knet_h - pointer to knet_handle_t * * interval - define the interval in seconds between PMTUd scans * range from 1 to 86400 (24h) * * @return * knet_handle_pmtud_setfreq returns * 0 on success * -1 on error and errno is set. * * default interval is 60. */ int knet_handle_pmtud_setfreq(knet_handle_t knet_h, unsigned int interval); /** * knet_handle_pmtud_getfreq * * @brief Get the interval between PMTUd scans * * knet_h - pointer to knet_handle_t * * interval - pointer where to store the current interval value * * @return * knet_handle_pmtud_setfreq returns * 0 on success * -1 on error and errno is set. */ int knet_handle_pmtud_getfreq(knet_handle_t knet_h, unsigned int *interval); /** * knet_handle_enable_pmtud_notify * * @brief install a callback to receive PMTUd changes * * knet_h - pointer to knet_handle_t * * pmtud_notify_fn_private_data * void pointer to data that can be used to identify * the callback. * * pmtud_notify_fn * is a callback function that is invoked every time * a path MTU size change is detected. * The function allows libknet to notify the user * of data MTU, that's the max value that can be send * onwire without fragmentation. The data MTU will always * be lower than real link MTU because it accounts for * protocol overhead, knet packet header and (if configured) * crypto overhead, * This function MUST NEVER block or add substantial delays. * * @return * knet_handle_enable_pmtud_notify returns * 0 on success * -1 on error and errno is set. */ int knet_handle_enable_pmtud_notify(knet_handle_t knet_h, void *pmtud_notify_fn_private_data, void (*pmtud_notify_fn) ( void *private_data, unsigned int data_mtu)); /** * knet_handle_pmtud_set * * @brief Set the current interface MTU * * knet_h - pointer to knet_handle_t * * iface_mtu - current interface MTU, value 0 to 65535. 0 will * re-enable automatic MTU discovery. * In a setup with multiple interfaces, please specify * the lowest MTU between the selected intefaces. * knet will automatically adjust this value for * all headers overhead and set the correct data_mtu. * data_mtu can be retrivied with knet_handle_pmtud_get(3) * or applications will receive a pmtud_nofity event * if enabled via knet_handle_enable_pmtud_notify(3). * * @return * knet_handle_pmtud_set returns * 0 on success * -1 on error and errno is set. */ int knet_handle_pmtud_set(knet_handle_t knet_h, unsigned int iface_mtu); /** * knet_handle_pmtud_get * * @brief Get the current data MTU * * knet_h - pointer to knet_handle_t * * data_mtu - pointer where to store data_mtu * * @return * knet_handle_pmtud_get returns * 0 on success * -1 on error and errno is set. */ int knet_handle_pmtud_get(knet_handle_t knet_h, unsigned int *data_mtu); #define KNET_MIN_KEY_LEN 128 #define KNET_MAX_KEY_LEN 4096 /** * Structure passed into knet_handle_set_crypto_config() to determine * the crypto options to use for the current communications handle */ struct knet_handle_crypto_cfg { /** Model to use. nss, openssl, etc */ char crypto_model[16]; /** Cipher type name for encryption. aes 256 etc */ char crypto_cipher_type[16]; /** Hash type for digest. sha512 etc */ char crypto_hash_type[16]; /** Private key */ unsigned char private_key[KNET_MAX_KEY_LEN]; /** Length of private key */ unsigned int private_key_len; }; /** * knet_handle_crypto_set_config * * @brief set up packet cryptographic signing & encryption * * knet_h - pointer to knet_handle_t * * knet_handle_crypto_cfg - * pointer to a knet_handle_crypto_cfg structure * * crypto_model should contain the model name. * Currently only "openssl" and "nss" are supported. * Setting to "none" will disable crypto. * * crypto_cipher_type * should contain the cipher algo name. * It can be set to "none" to disable * encryption. * Currently supported by "nss" model: * "aes128", "aes192" and "aes256". * "openssl" model supports more modes and it strictly * depends on the openssl build. See: EVP_get_cipherbyname * openssl API call for details. * * crypto_hash_type * should contain the hashing algo name. * It can be set to "none" to disable * hashing. * Currently supported by "nss" model: * "md5", "sha1", "sha256", "sha384" and "sha512". * "openssl" model supports more modes and it strictly * depends on the openssl build. See: EVP_get_digestbyname * openssl API call for details. * * private_key will contain the private shared key. * It has to be at least KNET_MIN_KEY_LEN long. * * private_key_len * length of the provided private_key. * * config_num - knet supports 2 concurrent sets of crypto configurations, * to allow runtime change of crypto config and keys. * On RX both configurations will be used sequentially * in an attempt to decrypt/validate a packet (when 2 are available). * Note that this might slow down performance during a reconfiguration. * See also knet_handle_crypto_rx_clear_traffic(3) to enable / disable * processing of clear (unencrypted) traffic. * For TX, the user needs to specify which configuration to use via * knet_handle_crypto_use_config(3). * config_num accepts 0, 1 or 2 as the value. 0 should be used when * all crypto is being disabled. * Calling knet_handle_crypto_set_config(3) twice with * the same config_num will REPLACE the configuration and * NOT activate the second key. If the configuration is currently in use * EBUSY will be returned. See also knet_handle_crypto_use_config(3). * The correct sequence to perform a runtime rekey / reconfiguration * is: * - knet_handle_crypto_set_config(..., 1). -> first time config, will use config1 * - knet_handle_crypto_use_config(..., 1). -> switch TX to config 1 * - knet_handle_crypto_set_config(..., 2). -> install config2 and use it only for RX * - knet_handle_crypto_use_config(..., 2). -> switch TX to config 2 * - knet_handle_crypto_set_config(..., 1). -> with a "none"/"none"/"none" configuration to * release the resources previously allocated * The application is responsible for synchronizing calls on the nodes * to make sure the new config is in place before switching the TX configuration. * Failure to do so will result in knet being unable to talk to some of the nodes. * * Implementation notes/current limitations: * - enabling crypto, will increase latency as packets have * to processed. * - enabling crypto might reduce the overall throughtput * due to crypto data overhead. * - private/public key encryption/hashing is not currently * planned. * - crypto key must be the same for all hosts in the same * knet instance / configX. * - it is safe to call knet_handle_crypto_set_config multiple times at runtime. * The last config will be used. * IMPORTANT: a call to knet_handle_crypto_set_config can fail due to: * 1) failure to obtain locking * 2) errors to initializing the crypto level. * This can happen even in subsequent calls to knet_handle_crypto_set_config(3). * A failure in crypto init will restore the previous crypto configuration if any. * * @return * knet_handle_crypto_set_config returns: * @retval 0 on success * @retval -1 on error and errno is set. * @retval -2 on crypto subsystem initialization error. No errno is provided at the moment (yet). */ int knet_handle_crypto_set_config(knet_handle_t knet_h, struct knet_handle_crypto_cfg *knet_handle_crypto_cfg, uint8_t config_num); #define KNET_CRYPTO_RX_ALLOW_CLEAR_TRAFFIC 0 #define KNET_CRYPTO_RX_DISALLOW_CLEAR_TRAFFIC 1 /** * knet_handle_crypto_rx_clear_traffic * * @brief enable or disable RX processing of clear (unencrypted) traffic * * knet_h - pointer to knet_handle_t * * value - KNET_CRYPTO_RX_ALLOW_CLEAR_TRAFFIC or KNET_CRYPTO_RX_DISALLOW_CLEAR_TRAFFIC * * @return * knet_handle_crypto_use_config returns: * @retval 0 on success * @retval -1 on error and errno is set. */ int knet_handle_crypto_rx_clear_traffic(knet_handle_t knet_h, uint8_t value); /** * knet_handle_crypto_use_config * * @brief specify crypto configuration to use for TX * * knet_h - pointer to knet_handle_t * * config_num - 1|2 use configuration 1 or 2, 0 for clear (unencrypted) traffic. * * @return * knet_handle_crypto_use_config returns: * @retval 0 on success * @retval -1 on error and errno is set. */ int knet_handle_crypto_use_config(knet_handle_t knet_h, uint8_t config_num); /** * knet_handle_crypto * * @brief set up packet cryptographic signing & encryption * * knet_h - pointer to knet_handle_t * * knet_handle_crypto_cfg - * pointer to a knet_handle_crypto_cfg structure * see knet_handle_crypto_set_config(3) for details. * * * Implementation notes: * * knet_handle_crypto(3) is now a wrapper for knet_handle_crypto_set_config(3) * and knet_handle_crypto_use_config(3) with config_num set to 1. * * @return * knet_handle_crypto returns: * @retval 0 on success * @retval -1 on error and errno is set. * @retval -2 on crypto subsystem initialization error. No errno is provided at the moment (yet). */ int knet_handle_crypto(knet_handle_t knet_h, struct knet_handle_crypto_cfg *knet_handle_crypto_cfg); #define KNET_COMPRESS_THRESHOLD 100 /** * Structure passed into knet_handle_compress() * to tell knet what type of compression to use * for this communiction */ struct knet_handle_compress_cfg { /** Compression library to use, bzip2 etc... */ char compress_model[16]; /** Threshold. Packets smaller than this will not be compressed */ uint32_t compress_threshold; /** Passed into the compression library as an indication of the level of compression to apply */ int compress_level; }; /** * knet_handle_compress * * @brief Set up packet compression * * knet_h - pointer to knet_handle_t * * knet_handle_compress_cfg - * pointer to a knet_handle_compress_cfg structure * * compress_model contains the model name. * See "compress_level" for the list of accepted values. * Setting the value to "none" disables compression. * * compress_threshold * tells the transmission thread to NOT compress * any packets that are smaller than the value * indicated. Default 100 bytes. * Set to 0 to reset to the default. * Set to 1 to compress everything. * Max accepted value is KNET_MAX_PACKET_SIZE. * * compress_level is the "level" parameter for most models: * zlib: 0 (no compression), 1 (minimal) .. 9 (max compression). * lz4: 1 (max compression)... 9 (fastest compression). * lz4hc: 1 (min compression) ... LZ4HC_MAX_CLEVEL (16) or LZ4HC_CLEVEL_MAX (12) * depending on the version of lz4hc libknet was built with. * lzma: 0 (minimal) .. 9 (max compression) * bzip2: 1 (minimal) .. 9 (max compression) * For lzo2 it selects the algorithm to use: * 1 : lzo1x_1_compress (default) * 11 : lzo1x_1_11_compress * 12 : lzo1x_1_12_compress * 15 : lzo1x_1_15_compress * 999: lzo1x_999_compress * Other values select the default algorithm. * Please refer to the documentation of the respective * compression library for guidance about setting this * value. * * Implementation notes: * - it is possible to enable/disable compression at any time. * - nodes can be using a different compression algorithm at any time. * - knet does NOT implement the compression algorithm directly. it relies * on external libraries for this functionality. Please read * the libraries man pages to figure out which algorithm/compression * level is best for the data you are planning to transmit. * * @return * knet_handle_compress returns * 0 on success * -1 on error and errno is set. EINVAL means that either the model or the * level are not supported. */ int knet_handle_compress(knet_handle_t knet_h, struct knet_handle_compress_cfg *knet_handle_compress_cfg); /** * Detailed stats for this knet handle as returned by knet_handle_get_stats() */ struct knet_handle_stats { /** Size of the structure. set this to sizeof(struct knet_handle_stats) before calling */ size_t size; /** Number of uncompressed packets sent */ uint64_t tx_uncompressed_packets; /** Number of compressed packets sent */ uint64_t tx_compressed_packets; /** Number of bytes sent (as if uncompressed, ie actual data bytes) */ uint64_t tx_compressed_original_bytes; /** Number of bytes sent on the wire after compression */ uint64_t tx_compressed_size_bytes; /** Average(mean) time take to compress transmitted packets */ uint64_t tx_compress_time_ave; /** Minimum time taken to compress transmitted packets */ uint64_t tx_compress_time_min; /** Maximum time taken to compress transmitted packets */ uint64_t tx_compress_time_max; /** Number of compressed packets received */ uint64_t rx_compressed_packets; /** Number of bytes received - after decompression */ uint64_t rx_compressed_original_bytes; /** Number of compressed bytes received before decompression */ uint64_t rx_compressed_size_bytes; /** Average(mean) time take to decompress received packets */ uint64_t rx_compress_time_ave; /** Minimum time take to decompress received packets */ uint64_t rx_compress_time_min; /** Maximum time take to decompress received packets */ uint64_t rx_compress_time_max; /** Number of encrypted packets sent */ uint64_t tx_crypt_packets; /** Cumulative byte overhead of encrypted traffic */ uint64_t tx_crypt_byte_overhead; /** Average(mean) time take to encrypt packets in usecs */ uint64_t tx_crypt_time_ave; /** Minimum time take to encrypto packets in usecs */ uint64_t tx_crypt_time_min; /** Maximum time take to encrypto packets in usecs */ uint64_t tx_crypt_time_max; /** Number of encrypted packets received */ uint64_t rx_crypt_packets; /** Average(mean) time take to decrypt received packets */ uint64_t rx_crypt_time_ave; /** Minimum time take to decrypt received packets in usecs */ uint64_t rx_crypt_time_min; /** Maximum time take to decrypt received packets in usecs */ uint64_t rx_crypt_time_max; }; /** * knet_handle_get_stats * * @brief Get statistics for compression & crypto * * knet_h - pointer to knet_handle_t * * knet_handle_stats * pointer to a knet_handle_stats structure * * struct_size * size of knet_handle_stats structure to allow * for backwards compatibility. libknet will only * copy this much data into the stats structure * so that older callers will not get overflowed if * new fields are added. * * @return * 0 on success * -1 on error and errno is set. * */ int knet_handle_get_stats(knet_handle_t knet_h, struct knet_handle_stats *stats, size_t struct_size); /* * Tell knet_handle_clear_stats whether to clear just the handle stats * or all of them. */ #define KNET_CLEARSTATS_HANDLE_ONLY 1 #define KNET_CLEARSTATS_HANDLE_AND_LINK 2 /** * knet_handle_clear_stats * * @brief Clear knet stats, link and/or handle * * knet_h - pointer to knet_handle_t * * clear_option - Which stats to clear, must be one of * * KNET_CLEARSTATS_HANDLE_ONLY or * KNET_CLEARSTATS_HANDLE_AND_LINK * * @return * 0 on success * -1 on error and errno is set. * */ int knet_handle_clear_stats(knet_handle_t knet_h, int clear_option); /** * Structure returned from get_crypto_list() containing * information about the installed cryptographic systems */ struct knet_crypto_info { /** Name of the crypto library/ openssl, nss,etc .. */ const char *name; /** Properties - currently unused */ uint8_t properties; /** Currently unused padding */ char pad[256]; }; /** * knet_get_crypto_list * * @brief Get a list of supported crypto libraries * * crypto_list - array of struct knet_crypto_info * * If NULL then only the number of structs is returned in crypto_list_entries * to allow the caller to allocate sufficient space. * libknet does not allow more than 256 crypto methods at the moment. * it is safe to allocate 256 structs to avoid calling * knet_get_crypto_list twice. * * crypto_list_entries - returns the number of structs in crypto_list * * @return * knet_get_crypto_list returns * 0 on success * -1 on error and errno is set. */ int knet_get_crypto_list(struct knet_crypto_info *crypto_list, size_t *crypto_list_entries); /** * Structure returned from get_compress_list() containing * information about the installed compression systems */ struct knet_compress_info { /** Name of the compression type bzip2, lz4, etc.. */ const char *name; /** Properties - currently unused */ uint8_t properties; /** Currently unused padding */ char pad[256]; }; /** * knet_get_compress_list * * @brief Get a list of support compression types * * compress_list - array of struct knet_compress_info * * If NULL then only the number of structs is returned in compress_list_entries * to allow the caller to allocate sufficient space. * libknet does not allow more than 256 compress methods at the moment. * it is safe to allocate 256 structs to avoid calling * knet_get_compress_list twice. * * compress_list_entries - returns the number of structs in compress_list * * @return * knet_get_compress_list returns * 0 on success * -1 on error and errno is set. */ int knet_get_compress_list(struct knet_compress_info *compress_list, size_t *compress_list_entries); /* * host structs/API calls */ /** * knet_host_add * * @brief Add a new host ID to knet * * knet_h - pointer to knet_handle_t * * host_id - each host in a knet is identified with a unique ID * (see also knet_handle_new(3)) * * @return * knet_host_add returns: * 0 on success * -1 on error and errno is set. */ int knet_host_add(knet_handle_t knet_h, knet_node_id_t host_id); /** * knet_host_remove * * @brief Remove a host ID from knet * * knet_h - pointer to knet_handle_t * * host_id - each host in a knet is identified with a unique ID * (see also knet_handle_new(3)) * * @return * knet_host_remove returns: * 0 on success * -1 on error and errno is set. */ int knet_host_remove(knet_handle_t knet_h, knet_node_id_t host_id); /** * knet_host_set_name * * @brief Set the name of a knet host * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * name - this name will be used for pretty logging and eventually * search for hosts (see also knet_handle_host_get_name(2) and knet_handle_host_get_id(3)). * Only up to KNET_MAX_HOST_LEN - 1 bytes will be accepted and * name has to be unique for each host. * * @return * knet_host_set_name returns: * 0 on success * -1 on error and errno is set. */ int knet_host_set_name(knet_handle_t knet_h, knet_node_id_t host_id, const char *name); /** * knet_host_get_name_by_host_id * * @brief Get the name of a host given its ID * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * name - pointer to a preallocated buffer of at least size KNET_MAX_HOST_LEN * where the current host name will be stored * (as set by knet_host_set_name or default by knet_host_add) * * @return * knet_host_get_name_by_host_id returns: * 0 on success * -1 on error and errno is set (name is left untouched) */ int knet_host_get_name_by_host_id(knet_handle_t knet_h, knet_node_id_t host_id, char *name); /** * knet_host_get_id_by_host_name * * @brief Get the ID of a host given its name * * knet_h - pointer to knet_handle_t * * name - name to lookup, max len KNET_MAX_HOST_LEN * * host_id - where to store the result * * @return * knet_host_get_id_by_host_name returns: * 0 on success * -1 on error and errno is set. */ int knet_host_get_id_by_host_name(knet_handle_t knet_h, const char *name, knet_node_id_t *host_id); /** * knet_host_get_host_list * * @brief Get a list of hosts known to knet * * knet_h - pointer to knet_handle_t * * host_ids - array of at lest KNET_MAX_HOST size * * host_ids_entries - * number of entries writted in host_ids * * @return * knet_host_get_host_list returns * 0 on success * -1 on error and errno is set. */ int knet_host_get_host_list(knet_handle_t knet_h, knet_node_id_t *host_ids, size_t *host_ids_entries); /* * define switching policies */ #define KNET_LINK_POLICY_PASSIVE 0 #define KNET_LINK_POLICY_ACTIVE 1 #define KNET_LINK_POLICY_RR 2 /** * knet_host_set_policy * * @brief Set the switching policy for a host's links * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * policy - there are currently 3 kind of simple switching policies * based on link configuration. * KNET_LINK_POLICY_PASSIVE - the active link with the highest * priority (highest number) will be used. * if one or more active links share * the same priority, the one with * lowest link_id will be used. * * KNET_LINK_POLICY_ACTIVE - all active links will be used * simultaneously to send traffic. * link priority is ignored. * * KNET_LINK_POLICY_RR - round-robin policy, every packet * will be send on a different active * link. * * @return * knet_host_set_policy returns * 0 on success * -1 on error and errno is set. */ int knet_host_set_policy(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t policy); /** * knet_host_get_policy * * @brief Get the switching policy for a host's links * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * policy - will contain the current configured switching policy. * Default is passive when creating a new host. * * @return * knet_host_get_policy returns * 0 on success * -1 on error and errno is set. */ int knet_host_get_policy(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t *policy); /** * knet_host_enable_status_change_notify * * @brief Install a callback to get host status change events * * knet_h - pointer to knet_handle_t * * host_status_change_notify_fn_private_data - * void pointer to data that can be used to identify * the callback * * host_status_change_notify_fn - * is a callback function that is invoked every time * there is a change in the host status. * host status is identified by: * - reachable, this host can send/receive data to/from host_id * - remote, 0 if the host_id is connected locally or 1 if * the there is one or more knet host(s) in between. * NOTE: re-switching is NOT currently implemented, * but this is ready for future and can avoid * an API/ABI breakage later on. * - external, 0 if the host_id is configured locally or 1 if * it has been added from remote nodes config. * NOTE: dynamic topology is NOT currently implemented, * but this is ready for future and can avoid * an API/ABI breakage later on. * This function MUST NEVER block or add substantial delays. * * @return * knet_host_status_change_notify returns * 0 on success * -1 on error and errno is set. */ int knet_host_enable_status_change_notify(knet_handle_t knet_h, void *host_status_change_notify_fn_private_data, void (*host_status_change_notify_fn) ( void *private_data, knet_node_id_t host_id, uint8_t reachable, uint8_t remote, uint8_t external)); /* * define host status structure for quick lookup * struct is in flux as more stats will be added soon * * reachable host_id can be seen either directly connected * or via another host_id * * remote 0 = node is connected locally, 1 is visible via * via another host_id * * external 0 = node is configured/known locally, * 1 host_id has been received via another host_id */ /** * status of a knet host, returned from knet_host_get_status() */ struct knet_host_status { /** Whether the host is currently reachable */ uint8_t reachable; /** Whether the host is a remote node (not currently implemented) */ uint8_t remote; /** Whether the host is external (not currently implemented) */ uint8_t external; /* add host statistics */ }; /** * knet_host_get_status * * @brief Get the status of a host * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * status - pointer to knet_host_status struct * * @return * knet_handle_pmtud_get returns * 0 on success * -1 on error and errno is set. */ int knet_host_get_status(knet_handle_t knet_h, knet_node_id_t host_id, struct knet_host_status *status); /* * link structs/API calls * * every host allocated/managed by knet_host_* has * KNET_MAX_LINK structures to define the network * paths that connect 2 hosts. * * Each link is identified by a link_id that has a * values between 0 and KNET_MAX_LINK - 1. * * KNOWN LIMITATIONS: * * - let's assume the scenario where two hosts are connected * with any number of links. link_id must match on both sides. * If host_id 0 link_id 0 is configured to connect IP1 to IP2 and * host_id 0 link_id 1 is configured to connect IP3 to IP4, * host_id 1 link_id 0 _must_ connect IP2 to IP1 and likewise * host_id 1 link_id 1 _must_ connect IP4 to IP3. * We might be able to lift this restriction in future, by using * other data to determine src/dst link_id, but for now, deal with it. */ /* * commodity functions to convert strings to sockaddr and viceversa */ /** * knet_strtoaddr * * @brief Convert a hostname string to an address * * host - IPaddr/hostname to convert * be aware only the first IP address will be returned * in case a hostname resolves to multiple IP * * port - port to connect to * * ss - sockaddr_storage where to store the converted data * * sslen - len of the sockaddr_storage * * @return * knet_strtoaddr returns same error codes as getaddrinfo * */ int knet_strtoaddr(const char *host, const char *port, struct sockaddr_storage *ss, socklen_t sslen); /** * knet_addrtostr * * @brief Convert an address to a host name * * ss - sockaddr_storage to convert * * sslen - len of the sockaddr_storage * * host - IPaddr/hostname where to store data * (recommended size: KNET_MAX_HOST_LEN) * * port - port buffer where to store data * (recommended size: KNET_MAX_PORT_LEN) * * @return * knet_strtoaddr returns same error codes as getnameinfo */ int knet_addrtostr(const struct sockaddr_storage *ss, socklen_t sslen, char *addr_buf, size_t addr_buf_size, char *port_buf, size_t port_buf_size); #define KNET_TRANSPORT_LOOPBACK 0 #define KNET_TRANSPORT_UDP 1 #define KNET_TRANSPORT_SCTP 2 #define KNET_MAX_TRANSPORTS UINT8_MAX /* * The Loopback transport is only valid for connections to localhost, the host * with the same node_id specified in knet_handle_new(). Only one link of this * type is allowed. Data sent down a LOOPBACK link will be copied directly from * the knet send datafd to the knet receive datafd so the application must be set * up to take data from that socket at least as often as it is sent or deadlocks * could occur. If used, a LOOPBACK link must be the only link configured to the * local host. */ /** * Transport information returned from knet_get_transport_list() */ struct knet_transport_info { /** Transport name. UDP, SCTP, etc... */ const char *name; /** value that can be used for knet_link_set_config() */ uint8_t id; /** currently unused */ uint8_t properties; /** currently unused */ char pad[256]; }; /** * knet_get_transport_list * * @brief Get a list of the transports support by this build of knet * * transport_list - an array of struct transport_info that must be * at least of size struct transport_info * KNET_MAX_TRANSPORTS * * transport_list_entries - pointer to a size_t where to store how many transports * are available in this build of libknet. * * @return * knet_get_transport_list returns * 0 on success * -1 on error and errno is set. */ int knet_get_transport_list(struct knet_transport_info *transport_list, size_t *transport_list_entries); /** * knet_get_transport_name_by_id * * @brief Get a transport name from its ID number * * transport - one of the KNET_TRANSPORT_xxx constants * * @return * knet_get_transport_name_by_id returns: * * @retval pointer to the name on success or * @retval NULL on error and errno is set. */ const char *knet_get_transport_name_by_id(uint8_t transport); /** * knet_get_transport_id_by_name * * @brief Get a transport ID from its name * * name - transport name (UDP/SCTP/etc) * * @return * knet_get_transport_name_by_id returns: * * @retval KNET_MAX_TRANSPORTS on error and errno is set accordingly * @retval KNET_TRANSPORT_xxx on success. */ uint8_t knet_get_transport_id_by_name(const char *name); #define KNET_TRANSPORT_DEFAULT_RECONNECT_INTERVAL 1000 /** * knet_handle_set_transport_reconnect_interval * * @brief Set the interval between transport attempts to reconnect a failed link * * knet_h - pointer to knet_handle_t * * msecs - milliseconds * * @return * knet_handle_set_transport_reconnect_interval returns * 0 on success * -1 on error and errno is set. */ int knet_handle_set_transport_reconnect_interval(knet_handle_t knet_h, uint32_t msecs); /** * knet_handle_get_transport_reconnect_interval * * @brief Get the interval between transport attempts to reconnect a failed link * * knet_h - pointer to knet_handle_t * * msecs - milliseconds * * @return * knet_handle_get_transport_reconnect_interval returns * 0 on success * -1 on error and errno is set. */ int knet_handle_get_transport_reconnect_interval(knet_handle_t knet_h, uint32_t *msecs); /** * knet_link_set_config * * @brief Configure the link to a host * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * link_id - see knet_link_set_config(3) * * transport - one of the KNET_TRANSPORT_xxx constants * * src_addr - sockaddr_storage that can be either IPv4 or IPv6 * * dst_addr - sockaddr_storage that can be either IPv4 or IPv6 * this can be null if we don't know the incoming * IP address/port and the link will remain quiet * till the node on the other end will initiate a * connection * * flags - KNET_LINK_FLAG_* * * @return * knet_link_set_config returns * 0 on success * -1 on error and errno is set. */ int knet_link_set_config(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id, uint8_t transport, struct sockaddr_storage *src_addr, struct sockaddr_storage *dst_addr, uint64_t flags); /** * knet_link_get_config * * @brief Get the link configutation information * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * link_id - see knet_link_set_config(3) * * transport - see knet_link_set_config(3) * * src_addr - sockaddr_storage that can be either IPv4 or IPv6 * * dst_addr - sockaddr_storage that can be either IPv4 or IPv6 * * dynamic - 0 if dst_addr is static or 1 if dst_addr is dynamic. * In case of 1, dst_addr can be NULL and it will be left * untouched. * * flags - KNET_LINK_FLAG_* * * @return * knet_link_get_config returns * 0 on success. * -1 on error and errno is set. */ int knet_link_get_config(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id, uint8_t *transport, struct sockaddr_storage *src_addr, struct sockaddr_storage *dst_addr, uint8_t *dynamic, uint64_t *flags); /** * knet_link_clear_config * * @brief Clear link information and disconnect the link * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * link_id - see knet_link_set_config(3) * * @return * knet_link_clear_config returns * 0 on success. * -1 on error and errno is set. */ int knet_link_clear_config(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id); /* * Access lists management for open links * see also knet_handle_enable_access_lists(3) */ /** * check_type_t * @brief address type enum for knet access lists * * CHECK_TYPE_ADDRESS is the equivalent of a single entry / IP address. * for example: 10.1.9.3 * and the entry is stored in ss1. ss2 can be NULL. * * CHECK_TYPE_MASK is used to configure network/netmask. * for example: 192.168.0.0/24 * the network is stored in ss1 and the netmask in ss2. * * CHECK_TYPE_RANGE defines a value / range of ip addresses. * for example: 172.16.0.1-172.16.0.10 * the start is stored in ss1 and the end in ss2. * * Please be aware that the above examples refer only to IP based protocols. * Other protocols might use ss1 and ss2 in slightly different ways. * At the moment knet only supports IP based protocol, though that might change * in the future. */ typedef enum { CHECK_TYPE_ADDRESS, CHECK_TYPE_MASK, CHECK_TYPE_RANGE } check_type_t; /** * check_acceptreject_t * * @brief enum for accept/reject in knet access lists * * accept or reject incoming packets defined in the access list entry */ typedef enum { CHECK_ACCEPT, CHECK_REJECT } check_acceptreject_t; /** * knet_link_add_acl * * @brief Add access list entry to an open link * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * link_id - see knet_link_set_config(3) * * ss1 / ss2 / type / acceptreject - see typedef definitions for details * * IMPORTANT: the order in which access lists are added is critical and it * is left to the user to add them in the right order. knet * will not attempt to logically sort them. * * For example: * 1 - accept from 10.0.0.0/8 * 2 - reject from 10.0.0.1/32 * * is not the same as: * * 1 - reject from 10.0.0.1/32 * 2 - accept from 10.0.0.0/8 * * In the first example, rule number 2 will never match because * packets from 10.0.0.1 will be accepted by rule number 1. * * @return * knet_link_add_acl returns * 0 on success. * -1 on error and errno is set. */ int knet_link_add_acl(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id, struct sockaddr_storage *ss1, struct sockaddr_storage *ss2, check_type_t type, check_acceptreject_t acceptreject); /** * knet_link_insert_acl * * @brief Insert access list entry to an open link at given index * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * link_id - see knet_link_set_config(3) * * index - insert at position "index" where 0 is the first entry and -1 * appends to the current list. * * ss1 / ss2 / type / acceptreject - see typedef definitions for details * * @return * knet_link_insert_acl returns * 0 on success. * -1 on error and errno is set. */ int knet_link_insert_acl(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id, int index, struct sockaddr_storage *ss1, struct sockaddr_storage *ss2, check_type_t type, check_acceptreject_t acceptreject); /** * knet_link_rm_acl * * @brief Remove access list entry from an open link * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * link_id - see knet_link_set_config(3) * * ss1 / ss2 / type / acceptreject - see typedef definitions for details * * IMPORTANT: the data passed to this API call must match exactly that passed * to knet_link_add_acl(3). * * @return * knet_link_rm_acl returns * 0 on success. * -1 on error and errno is set. */ int knet_link_rm_acl(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id, struct sockaddr_storage *ss1, struct sockaddr_storage *ss2, check_type_t type, check_acceptreject_t acceptreject); /** * knet_link_clear_acl * * @brief Remove all access list entries from an open link * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * link_id - see knet_link_set_config(3) * * @return * knet_link_clear_acl returns * 0 on success. * -1 on error and errno is set. */ int knet_link_clear_acl(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id); /** * knet_link_set_enable * * @brief Enable traffic on a link * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * link_id - see knet_link_set_config(3) * * enabled - 0 disable the link, 1 enable the link * * @return * knet_link_set_enable returns * 0 on success * -1 on error and errno is set. */ int knet_link_set_enable(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id, unsigned int enabled); /** * knet_link_get_enable * * @brief Find out whether a link is enabled or not * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * link_id - see knet_link_set_config(3) * * enabled - 0 disable the link, 1 enable the link * * @return * knet_link_get_enable returns * 0 on success * -1 on error and errno is set. */ int knet_link_get_enable(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id, unsigned int *enabled); #define KNET_LINK_DEFAULT_PING_INTERVAL 1000 /* 1 second */ #define KNET_LINK_DEFAULT_PING_TIMEOUT 2000 /* 2 seconds */ #define KNET_LINK_DEFAULT_PING_PRECISION 2048 /* samples */ /** * knet_link_set_ping_timers * * @brief Set the ping timers for a link * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * link_id - see knet_link_set_config(3) * * interval - specify the ping interval in milliseconds. * * timeout - if no pong is received within this time, * the link is declared dead, in milliseconds. * NOTE: in future it will be possible to set timeout to 0 * for an autocalculated timeout based on interval, pong_count * and latency. The API already accept 0 as value and it will * return ENOSYS / -1. Once the automatic calculation feature * will be implemented, this call will only return EINVAL * for incorrect values. * * precision - how many values of latency are used to calculate * the average link latency (see also knet_link_get_status(3)) * * @return * knet_link_set_ping_timers returns * 0 on success * -1 on error and errno is set. */ int knet_link_set_ping_timers(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id, time_t interval, time_t timeout, unsigned int precision); /** * knet_link_get_ping_timers * * @brief Get the ping timers for a link * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * link_id - see knet_link_set_config(3) * * interval - ping interval * * timeout - if no pong is received within this time, * the link is declared dead * * precision - how many values of latency are used to calculate * the average link latency (see also knet_link_get_status(3)) * * @return * knet_link_get_ping_timers returns * 0 on success * -1 on error and errno is set. */ int knet_link_get_ping_timers(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id, time_t *interval, time_t *timeout, unsigned int *precision); #define KNET_LINK_DEFAULT_PONG_COUNT 5 /** * knet_link_set_pong_count * * @brief Set the pong count for a link * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * link_id - see knet_link_set_config(3) * * pong_count - how many valid ping/pongs before a link is marked UP. * default: 5, value should be > 0 * * @return * knet_link_set_pong_count returns * 0 on success * -1 on error and errno is set. */ int knet_link_set_pong_count(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id, uint8_t pong_count); /** * knet_link_get_pong_count * * @brief Get the pong count for a link * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * link_id - see knet_link_set_config(3) * * pong_count - how many valid ping/pongs before a link is marked UP. * default: 5, value should be > 0 * * @return * knet_link_get_pong_count returns * 0 on success * -1 on error and errno is set. */ int knet_link_get_pong_count(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id, uint8_t *pong_count); /** * knet_link_set_priority * * @brief Set the priority for a link * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * link_id - see knet_link_set_config(3) * * priority - specify the switching priority for this link * see also knet_host_set_policy * * @return * knet_link_set_priority returns * 0 on success * -1 on error and errno is set. */ int knet_link_set_priority(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id, uint8_t priority); /** * knet_link_get_priority * * @brief Get the priority for a link * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * link_id - see knet_link_set_config(3) * * priority - gather the switching priority for this link * see also knet_host_set_policy * * @return * knet_link_get_priority returns * 0 on success * -1 on error and errno is set. */ int knet_link_get_priority(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id, uint8_t *priority); /** * knet_link_get_link_list * * @brief Get a list of links connecting a host * * knet_h - pointer to knet_handle_t * * link_ids - array of at lest KNET_MAX_LINK size * with the list of configured links for a certain host. * * link_ids_entries - * number of entries contained in link_ids * * @return * knet_link_get_link_list returns * 0 on success * -1 on error and errno is set. */ int knet_link_get_link_list(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t *link_ids, size_t *link_ids_entries); /* * define link status structure for quick lookup * * src/dst_{ipaddr,port} strings are filled by * getnameinfo(3) when configuring the link. * if the link is dynamic (see knet_link_set_config(3)) * dst_ipaddr/port will contain ipaddr/port of the currently * connected peer or "Unknown" if it was not possible * to determine the ipaddr/port at runtime. * * enabled see also knet_link_set/get_enable. * * connected the link is connected to a peer and ping/pong traffic * is flowing. * * dynconnected the link has dynamic ip on the other end, and * we can see the other host is sending pings to us. * * latency average latency of this link * see also knet_link_set/get_timeout. * * pong_last if the link is down, this value tells us how long * ago this link was active. A value of 0 means that the link * has never been active. * * knet_link_stats structure that contains details statistics for the link */ #define MAX_LINK_EVENTS 16 /** * Stats for a knet link * returned from knet_link_get_status() as part of a knet_link_status structure * link stats are 'onwire', ie they indicate the number of actual bytes/packets * sent including overheads, not just data packets. */ struct knet_link_stats { /** Number of data packets sent */ uint64_t tx_data_packets; /** Number of data packets received */ uint64_t rx_data_packets; /** Number of data bytes sent */ uint64_t tx_data_bytes; /** Number of data bytes received */ uint64_t rx_data_bytes; /** Number of ping packets sent */ uint64_t rx_ping_packets; /** Number of ping packets received */ uint64_t tx_ping_packets; /** Number of ping bytes sent */ uint64_t rx_ping_bytes; /** Number of ping bytes received */ uint64_t tx_ping_bytes; /** Number of pong packets sent */ uint64_t rx_pong_packets; /** Number of pong packets received */ uint64_t tx_pong_packets; /** Number of pong bytes sent */ uint64_t rx_pong_bytes; /** Number of pong bytes received */ uint64_t tx_pong_bytes; /** Number of pMTU packets sent */ uint64_t rx_pmtu_packets; /** Number of pMTU packets received */ uint64_t tx_pmtu_packets; /** Number of pMTU bytes sent */ uint64_t rx_pmtu_bytes; /** Number of pMTU bytes received */ uint64_t tx_pmtu_bytes; /* These are only filled in when requested ie. they are not collected in realtime */ /** Total of all packets sent */ uint64_t tx_total_packets; /** Total of all packets received */ uint64_t rx_total_packets; /** Total number of bytes sent */ uint64_t tx_total_bytes; /** Total number of bytes received */ uint64_t rx_total_bytes; /** Total number of errors that occurred while sending */ uint64_t tx_total_errors; /** Total number of retries that occurred while sending */ uint64_t tx_total_retries; /** Total number of errors that occurred while sending pMTU packets */ uint32_t tx_pmtu_errors; /** Total number of retries that occurred while sending pMTU packets */ uint32_t tx_pmtu_retries; /** Total number of errors that occurred while sending ping packets */ uint32_t tx_ping_errors; /** Total number of retries that occurred while sending ping packets */ uint32_t tx_ping_retries; /** Total number of errors that occurred while sending pong packets */ uint32_t tx_pong_errors; /** Total number of retries that occurred while sending pong packets */ uint32_t tx_pong_retries; /** Total number of errors that occurred while sending data packets */ uint32_t tx_data_errors; /** Total number of retries that occurred while sending data packets */ uint32_t tx_data_retries; /** Minimum latency measured in usecs */ uint32_t latency_min; /** Maximum latency measured in usecs */ uint32_t latency_max; /** Average(mean) latency measured in usecs */ uint32_t latency_ave; /** Number of samples used to calculate latency */ uint32_t latency_samples; /** How many times the link has gone down */ uint32_t down_count; /** How many times the link has come up */ uint32_t up_count; /** * A circular buffer of time_t structs collecting the history * of up events on this link. * The index indicates current/last event. * it is safe to walk back the history by decreasing the index */ time_t last_up_times[MAX_LINK_EVENTS]; /** * A circular buffer of time_t structs collecting the history * of down events on this link. * The index indicates current/last event. * it is safe to walk back the history by decreasing the index */ time_t last_down_times[MAX_LINK_EVENTS]; /** Index of last element in the last_up_times[] array */ int8_t last_up_time_index; /** Index of last element in the last_down_times[] array */ int8_t last_down_time_index; /* Always add new stats at the end */ }; /** * Status of a knet link as returned from knet_link_get_status() */ struct knet_link_status { /** Size of the structure for ABI checking, set this to sizeof(knet_link_status) before calling knet_link_get_status() */ size_t size; /** Local IP address as a string*/ char src_ipaddr[KNET_MAX_HOST_LEN]; /** Local IP port as a string */ char src_port[KNET_MAX_PORT_LEN]; /** Remote IP address as a string */ char dst_ipaddr[KNET_MAX_HOST_LEN]; /** Remote IP port as a string*/ char dst_port[KNET_MAX_PORT_LEN]; /** Link is configured and admin enabled for traffic */ uint8_t enabled; /** Link is connected for data (local view) */ uint8_t connected; /** Link has been activated by remote dynip */ uint8_t dynconnected; /** average latency computed by fix/exp */ unsigned long long latency; /** Timestamp of the past pong received */ struct timespec pong_last; /** Currently detected MTU on this link */ unsigned int mtu; /** * Contains the size of the IP protocol, knet headers and * crypto headers (if configured). This value is filled in * ONLY after the first PMTUd run on that given link, * and can change if link configuration or crypto configuration * changes at runtime. * WARNING: in general mtu + proto_overhead might or might * not match the output of ifconfig mtu due to crypto * requirements to pad packets to some specific boundaries. */ unsigned int proto_overhead; /** Link statistics */ struct knet_link_stats stats; }; /** * knet_link_get_status * * @brief Get the status (and statistics) for a link * * knet_h - pointer to knet_handle_t * * host_id - see knet_host_add(3) * * link_id - see knet_link_set_config(3) * * status - pointer to knet_link_status struct * * struct_size - max size of knet_link_status - allows library to * add fields without ABI change. Returned structure * will be truncated to this length and .size member * indicates the full size. * * @return * knet_link_get_status returns * 0 on success * -1 on error and errno is set. */ int knet_link_get_status(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id, struct knet_link_status *status, size_t struct_size); /* * logging structs/API calls */ /* * libknet is composed of several subsystems. In order * to easily distinguish log messages coming from different * places, each subsystem has its own ID. * * 0-19 config/management * 20-39 internal threads * 40-59 transports * 60-69 crypto implementations */ #define KNET_SUB_COMMON 0 /* common.c */ #define KNET_SUB_HANDLE 1 /* handle.c alloc/dealloc config changes */ #define KNET_SUB_HOST 2 /* host add/del/modify */ #define KNET_SUB_LISTENER 3 /* listeners add/del/modify... */ #define KNET_SUB_LINK 4 /* link add/del/modify */ #define KNET_SUB_TRANSPORT 5 /* Transport common */ #define KNET_SUB_CRYPTO 6 /* crypto.c config generic layer */ #define KNET_SUB_COMPRESS 7 /* compress.c config generic layer */ #define KNET_SUB_FILTER 19 /* allocated for users to log from dst_filter */ #define KNET_SUB_DSTCACHE 20 /* switching thread (destination cache handling) */ #define KNET_SUB_HEARTBEAT 21 /* heartbeat thread */ #define KNET_SUB_PMTUD 22 /* Path MTU Discovery thread */ #define KNET_SUB_TX 23 /* send to link thread */ #define KNET_SUB_RX 24 /* recv from link thread */ #define KNET_SUB_TRANSP_BASE 40 /* Base log level for transports */ #define KNET_SUB_TRANSP_LOOPBACK (KNET_SUB_TRANSP_BASE + KNET_TRANSPORT_LOOPBACK) #define KNET_SUB_TRANSP_UDP (KNET_SUB_TRANSP_BASE + KNET_TRANSPORT_UDP) #define KNET_SUB_TRANSP_SCTP (KNET_SUB_TRANSP_BASE + KNET_TRANSPORT_SCTP) #define KNET_SUB_NSSCRYPTO 60 /* nsscrypto.c */ #define KNET_SUB_OPENSSLCRYPTO 61 /* opensslcrypto.c */ #define KNET_SUB_ZLIBCOMP 70 /* compress_zlib.c */ #define KNET_SUB_LZ4COMP 71 /* compress_lz4.c */ #define KNET_SUB_LZ4HCCOMP 72 /* compress_lz4.c */ #define KNET_SUB_LZO2COMP 73 /* compress_lzo.c */ #define KNET_SUB_LZMACOMP 74 /* compress_lzma.c */ #define KNET_SUB_BZIP2COMP 75 /* compress_bzip2.c */ #define KNET_SUB_ZSTDCOMP 76 /* compress_zstd.c */ #define KNET_SUB_UNKNOWN UINT8_MAX - 1 #define KNET_MAX_SUBSYSTEMS UINT8_MAX /* * Convert between subsystem IDs and names */ /** * knet_log_get_subsystem_name * * @brief Get a logging system name from its numeric ID * * @return * returns internal name of the subsystem or "common" */ const char *knet_log_get_subsystem_name(uint8_t subsystem); /** * knet_log_get_subsystem_id * * @brief Get a logging system ID from its name * * @return * returns internal ID of the subsystem or KNET_SUB_COMMON */ uint8_t knet_log_get_subsystem_id(const char *name); /* * 5 log levels are enough for everybody */ #define KNET_LOG_ERR 0 /* unrecoverable errors/conditions */ #define KNET_LOG_WARN 1 /* recoverable errors/conditions */ #define KNET_LOG_INFO 2 /* info, link up/down, config changes.. */ #define KNET_LOG_DEBUG 3 #define KNET_LOG_TRACE 4 /* * Convert between log level values and names */ /** * knet_log_get_loglevel_name * * @brief Get a logging level name from its numeric ID * * @return * returns internal name of the log level or "ERROR" for unknown values */ const char *knet_log_get_loglevel_name(uint8_t level); /** * knet_log_get_loglevel_id * * @brief Get a logging level ID from its name * * @return * returns internal log level ID or KNET_LOG_ERR for invalid names */ uint8_t knet_log_get_loglevel_id(const char *name); /* * every log message is composed by a text message * and message level/subsystem IDs. * In order to make debugging easier it is possible to send those packets * straight to stdout/stderr (see knet_bench.c stdout option). */ #define KNET_MAX_LOG_MSG_SIZE 254 #if KNET_MAX_LOG_MSG_SIZE > PIPE_BUF #error KNET_MAX_LOG_MSG_SIZE cannot be bigger than PIPE_BUF for guaranteed system atomic writes #endif /** * Structure of a log message sent to the logging fd */ struct knet_log_msg { /** Text of the log message */ char msg[KNET_MAX_LOG_MSG_SIZE]; /** Subsystem that sent this message. KNET_SUB_* */ uint8_t subsystem; /** Logging level of this message. KNET_LOG_* */ uint8_t msglevel; }; /** * knet_log_set_loglevel * * @brief Set the logging level for a subsystem * * knet_h - same as above * * subsystem - same as above * * level - same as above * * knet_log_set_loglevel allows fine control of log levels by subsystem. * See also knet_handle_new for defaults. * * @return * knet_log_set_loglevel returns * 0 on success * -1 on error and errno is set. */ int knet_log_set_loglevel(knet_handle_t knet_h, uint8_t subsystem, uint8_t level); /** * knet_log_get_loglevel * * @brief Get the logging level for a subsystem * * knet_h - same as above * * subsystem - same as above * * level - same as above * * @return * knet_log_get_loglevel returns * 0 on success * -1 on error and errno is set. */ int knet_log_get_loglevel(knet_handle_t knet_h, uint8_t subsystem, uint8_t *level); #endif diff --git a/libknet/libknet_exported_syms b/libknet/libknet_exported_syms index 0ccf8608..517ad40d 100644 --- a/libknet/libknet_exported_syms +++ b/libknet/libknet_exported_syms @@ -1,15 +1,83 @@ # Version and symbol export for libknet.so # # Copyright (C) 2016-2025 Red Hat, Inc. All rights reserved. # # Author: Fabio M. Di Nitto # # This software licensed under LGPL-2.0+ # LIBKNET { global: - knet_*; + knet_addrtostr; + knet_get_compress_list; + knet_get_crypto_list; + knet_get_transport_id_by_name; + knet_get_transport_list; + knet_get_transport_name_by_id; + knet_handle_add_datafd; + knet_handle_clear_stats; + knet_handle_compress; + knet_handle_crypto; + knet_handle_crypto_rx_clear_traffic; + knet_handle_crypto_set_config; + knet_handle_crypto_use_config; + knet_handle_enable_access_lists; + knet_handle_enable_filter; + knet_handle_enable_pmtud_notify; + knet_handle_enable_sock_notify; + knet_handle_setprio_dscp; + knet_handle_free; + knet_handle_get_channel; + knet_handle_get_datafd; + knet_handle_get_stats; + knet_handle_get_transport_reconnect_interval; + knet_handle_new; + knet_handle_new_ex; + knet_handle_pmtud_get; + knet_handle_pmtud_getfreq; + knet_handle_pmtud_set; + knet_handle_pmtud_setfreq; + knet_handle_remove_datafd; + knet_handle_set_transport_reconnect_interval; + knet_handle_setfwd; + knet_host_add; + knet_host_enable_status_change_notify; + knet_host_get_host_list; + knet_host_get_id_by_host_name; + knet_host_get_name_by_host_id; + knet_host_get_policy; + knet_host_get_status; + knet_host_remove; + knet_host_set_name; + knet_host_set_policy; + knet_link_add_acl; + knet_link_clear_acl; + knet_link_clear_config; + knet_link_get_config; + knet_link_get_enable; + knet_link_get_link_list; + knet_link_get_ping_timers; + knet_link_get_pong_count; + knet_link_get_priority; + knet_link_get_status; + knet_link_insert_acl; + knet_link_rm_acl; + knet_link_set_config; + knet_link_set_enable; + knet_link_set_ping_timers; + knet_link_set_pong_count; + knet_link_set_priority; + knet_log_get_loglevel_id; + knet_log_get_loglevel_name; + knet_log_get_loglevel; + knet_log_get_subsystem_id; + knet_log_get_subsystem_name; + knet_log_set_loglevel; + knet_recv; + knet_send_sync; + knet_send; + knet_strtoaddr; local: *; }; diff --git a/libknet/netutils.h b/libknet/netutils.h index 824aaf42..61e98692 100644 --- a/libknet/netutils.h +++ b/libknet/netutils.h @@ -1,31 +1,39 @@ /* * Copyright (C) 2010-2025 Red Hat, Inc. All rights reserved. * * Authors: Fabio M. Di Nitto * Federico Simoncelli * * This software licensed under LGPL-2.0+ */ #ifndef __KNET_NETUTILS_H__ #define __KNET_NETUTILS_H__ #include #include /* * s6_addr32 is not defined in BSD userland, only kernel. * definition is the same as linux and it works fine for * what we need. */ #ifndef s6_addr32 -#define s6_addr32 __u6_addr.__u6_addr32 +# ifdef KNET_SOLARIS +# define s6_addr32 _S6_un._S6_u32 +# else +# define s6_addr32 __u6_addr.__u6_addr32 +# endif +#endif + +#ifndef SOL_IP +# define SOL_IP IPPROTO_IP #endif int cmpaddr(const struct sockaddr_storage *ss1, const struct sockaddr_storage *ss2); void copy_sockaddr(struct sockaddr_storage *sout, const struct sockaddr_storage *sin); socklen_t sockaddr_len(const struct sockaddr_storage *ss); #endif diff --git a/libknet/onwire.h b/libknet/onwire.h index 0212a879..ec341ae6 100644 --- a/libknet/onwire.h +++ b/libknet/onwire.h @@ -1,135 +1,140 @@ /* * Copyright (C) 2012-2025 Red Hat, Inc. All rights reserved. * * Authors: Fabio M. Di Nitto * Federico Simoncelli * * This software licensed under LGPL-2.0+ */ #ifndef __KNET_ONWIRE_H__ #define __KNET_ONWIRE_H__ +#include + +#include "config.h" +#include "libknet.h" + /* * data structures to define network packets. * Start from knet_header at the bottom */ #include #include "libknet.h" /* * typedef uint64_t seq_num_t; * #define SEQ_MAX UINT64_MAX */ typedef uint16_t seq_num_t; #define SEQ_MAX UINT16_MAX struct knet_header_payload_data { seq_num_t khp_data_seq_num; /* pckt seq number used to deduplicate pkcts */ uint8_t khp_data_compress; /* identify if user data are compressed */ uint8_t khp_data_pad1; /* make sure to have space in the header to grow features */ uint8_t khp_data_bcast; /* data destination bcast/ucast */ uint8_t khp_data_frag_num; /* number of fragments of this pckt. 1 is not fragmented */ uint8_t khp_data_frag_seq; /* as above, indicates the frag sequence number */ int8_t khp_data_channel; /* transport channel data for localsock <-> knet <-> localsock mapping */ uint8_t khp_data_userdata[0]; /* pointer to the real user data */ } __attribute__((packed)); struct knet_header_payload_ping { uint8_t khp_ping_link; /* source link id */ uint32_t khp_ping_time[4]; /* ping timestamp */ seq_num_t khp_ping_seq_num; /* transport host seq_num */ uint8_t khp_ping_timed; /* timed pinged (1) or forced by seq_num (0) */ } __attribute__((packed)); /* taken from tracepath6 */ #define KNET_PMTUD_SIZE_V4 65535 #define KNET_PMTUD_SIZE_V6 KNET_PMTUD_SIZE_V4 /* * IPv4/IPv6 header size */ #define KNET_PMTUD_OVERHEAD_V4 20 #define KNET_PMTUD_OVERHEAD_V6 40 #define KNET_PMTUD_MIN_MTU_V4 576 #define KNET_PMTUD_MIN_MTU_V6 1280 struct knet_header_payload_pmtud { uint8_t khp_pmtud_link; /* source link id */ uint16_t khp_pmtud_size; /* size of the current packet */ uint8_t khp_pmtud_data[0]; /* pointer to empty/random data/fill buffer */ } __attribute__((packed)); /* * union to reference possible individual payloads */ union knet_header_payload { struct knet_header_payload_data khp_data; /* pure data packet struct */ struct knet_header_payload_ping khp_ping; /* heartbeat packet struct */ struct knet_header_payload_pmtud khp_pmtud; /* Path MTU discovery packet struct */ } __attribute__((packed)); /* * starting point */ #define KNET_HEADER_VERSION 0x01 /* we currently support only one version */ #define KNET_HEADER_TYPE_DATA 0x00 /* pure data packet */ #define KNET_HEADER_TYPE_PMSK 0x80 /* packet mask */ #define KNET_HEADER_TYPE_PING 0x81 /* heartbeat */ #define KNET_HEADER_TYPE_PONG 0x82 /* reply to heartbeat */ #define KNET_HEADER_TYPE_PMTUD 0x83 /* Used to determine Path MTU */ #define KNET_HEADER_TYPE_PMTUD_REPLY 0x84 /* reply from remote host */ struct knet_header { uint8_t kh_version; /* pckt format/version */ uint8_t kh_type; /* from above defines. Tells what kind of pckt it is */ knet_node_id_t kh_node; /* host id of the source host for this pckt */ uint8_t kh_pad1; /* make sure to have space in the header to grow features */ uint8_t kh_pad2; union knet_header_payload kh_payload; /* union of potential data struct based on kh_type */ } __attribute__((packed)); /* * commodoty defines to hide structure nesting * (needs review and cleanup) */ #define khp_data_seq_num kh_payload.khp_data.khp_data_seq_num #define khp_data_frag_num kh_payload.khp_data.khp_data_frag_num #define khp_data_frag_seq kh_payload.khp_data.khp_data_frag_seq #define khp_data_userdata kh_payload.khp_data.khp_data_userdata #define khp_data_bcast kh_payload.khp_data.khp_data_bcast #define khp_data_channel kh_payload.khp_data.khp_data_channel #define khp_data_compress kh_payload.khp_data.khp_data_compress #define khp_ping_link kh_payload.khp_ping.khp_ping_link #define khp_ping_time kh_payload.khp_ping.khp_ping_time #define khp_ping_seq_num kh_payload.khp_ping.khp_ping_seq_num #define khp_ping_timed kh_payload.khp_ping.khp_ping_timed #define khp_pmtud_link kh_payload.khp_pmtud.khp_pmtud_link #define khp_pmtud_size kh_payload.khp_pmtud.khp_pmtud_size #define khp_pmtud_data kh_payload.khp_pmtud.khp_pmtud_data /* * extra defines to avoid mingling with sizeof() too much */ #define KNET_HEADER_ALL_SIZE sizeof(struct knet_header) #define KNET_HEADER_SIZE (KNET_HEADER_ALL_SIZE - sizeof(union knet_header_payload)) #define KNET_HEADER_PING_SIZE (KNET_HEADER_SIZE + sizeof(struct knet_header_payload_ping)) #define KNET_HEADER_PMTUD_SIZE (KNET_HEADER_SIZE + sizeof(struct knet_header_payload_pmtud)) #define KNET_HEADER_DATA_SIZE (KNET_HEADER_SIZE + sizeof(struct knet_header_payload_data)) size_t calc_data_outlen(knet_handle_t knet_h, size_t inlen); size_t calc_max_data_outlen(knet_handle_t knet_h, size_t inlen); size_t calc_min_mtu(knet_handle_t knet_h); #endif diff --git a/libknet/tests/Makefile.am b/libknet/tests/Makefile.am index 12c2574f..18b126aa 100644 --- a/libknet/tests/Makefile.am +++ b/libknet/tests/Makefile.am @@ -1,122 +1,122 @@ # # Copyright (C) 2016-2025 Red Hat, Inc. All rights reserved. # # Authors: Fabio M. Di Nitto # # This software licensed under GPL-2.0+ # MAINTAINERCLEANFILES = Makefile.in include $(top_srcdir)/build-aux/check.mk include $(top_srcdir)/libknet/tests/api-check.mk EXTRA_DIST = \ api-test-coverage \ api-check.mk -AM_CPPFLAGS = -I$(top_srcdir)/libknet +AM_CPPFLAGS = -I$(top_srcdir)/libknet $(XOPEN_CPPFLAGS) AM_CFLAGS += $(PTHREAD_CFLAGS) $(libqb_CFLAGS) LIBS = $(top_builddir)/libknet/libknet.la \ $(PTHREAD_LIBS) $(dl_LIBS) noinst_HEADERS = \ test-common.h # the order of those tests is NOT random. # some functions can only be tested properly after some dependents # API have been validated upfront. check_PROGRAMS = \ $(api_checks) \ $(int_checks) if RUN_FUN_TESTS check_PROGRAMS += $(fun_checks) endif int_checks = \ int_links_acl_ip_test \ int_timediff_test fun_checks = \ fun_config_crypto_test \ fun_acl_check_test # checks below need to be executed manually # or with a specifi environment long_run_checks = \ fun_pmtud_crypto_test benchmarks = \ knet_bench_test noinst_PROGRAMS = \ api_knet_handle_new_limit_test \ pckt_test \ $(benchmarks) \ $(long_run_checks) \ $(api_checks) \ $(int_checks) \ $(fun_checks) noinst_SCRIPTS = \ api-test-coverage TESTS = $(check_PROGRAMS) if INSTALL_TESTS testsuitedir = $(TESTDIR) testsuite_PROGRAMS = $(noinst_PROGRAMS) endif check-local: check-api-test-coverage check-annocheck-bins check-api-test-coverage: chmod u+x $(top_srcdir)/libknet/tests/api-test-coverage $(top_srcdir)/libknet/tests/api-test-coverage $(top_srcdir) $(top_builddir) pckt_test_SOURCES = pckt_test.c int_links_acl_ip_test_SOURCES = int_links_acl_ip.c \ ../common.c \ ../compat.c \ ../logging.c \ ../netutils.c \ ../threads_common.c \ ../onwire.c \ ../transports.c \ ../transport_common.c \ ../transport_loopback.c \ ../transport_sctp.c \ ../transport_udp.c \ ../links_acl.c \ ../links_acl_ip.c \ ../links_acl_loopback.c \ ../lib_config.c int_timediff_test_SOURCES = int_timediff.c knet_bench_test_SOURCES = knet_bench.c \ test-common.c \ ../common.c \ ../logging.c \ ../compat.c \ ../transport_common.c \ ../threads_common.c \ ../onwire.c \ ../lib_config.c fun_pmtud_crypto_test_SOURCES = fun_pmtud_crypto.c \ test-common.c \ ../onwire.c \ ../logging.c \ ../threads_common.c \ ../lib_config.c fun_config_crypto_test_SOURCES = fun_config_crypto.c \ test-common.c fun_acl_check_test_SOURCES = fun_acl_check.c \ test-common.c diff --git a/libknet/tests/api-test-coverage b/libknet/tests/api-test-coverage index 6b4c0954..409c33b9 100755 --- a/libknet/tests/api-test-coverage +++ b/libknet/tests/api-test-coverage @@ -1,90 +1,91 @@ #!/bin/sh # # Copyright (C) 2016-2025 Red Hat, Inc. All rights reserved. # # Author: Fabio M. Di Nitto # # This software licensed under GPL-2.0+ # srcdir="$1"/libknet/tests builddir="$2"/libknet/tests headerapicalls="$(grep knet_ "$srcdir"/../libknet.h | grep -v "^ \*" | grep -v ^struct | grep -v "^[[:space:]]" | grep -v typedef | sed -e 's/(.*//g' -e 's/^const //g' -e 's/\*//g' | awk '{print $2}')" # The PowerPC64 ELFv1 ABI defines the address of a function as that of a # function descriptor defined in .opd, a data (D) section. Other ABIs # use the entry address of the function itself in the text (T) section. -exportedapicalls="$(nm -B -D "$builddir"/../.libs/libknet.so | grep ' [DT] ' | awk '{print $3}' | sed -e 's#@@LIBKNET##g')" +# Filter additional symbols exported by ln(1) on Solaris. +exportedapicalls="$(nm -B -D "$builddir"/../.libs/libknet.so | grep ' [DT] ' | awk '{if ($3 != "_edata" && $3 != "_GLOBAL_OFFSET_TABLE_" && $3 != "_PROCEDURE_LINKAGE_TABLE_") {print $3}}' | sed -e 's#@@LIBKNET##g')" echo "Checking for exported symbols NOT available in header file" for i in $exportedapicalls; do found=0 for x in $headerapicalls; do if [ "$x" = "$i" ]; then found=1 break; fi done if [ "$found" = 0 ]; then echo "Symbol $i not found in header file" exit 1 fi done echo "Checking for symbols in header file NOT exported by binary lib" for i in $headerapicalls; do found=0 for x in $exportedapicalls; do if [ "$x" = "$i" ]; then found=1 break; fi done if [ "$found" = 0 ]; then echo "Symbol $i not found in binary lib" exit 1 fi done echo "Checking for tests with memcheck exceptions" for i in $(grep -l is_memcheck "$srcdir"/*.c | grep -v test-common); do echo "WARNING: $(basename $i) - has memcheck exception enabled" done echo "Checking for tests with helgrind exceptions" for i in $(grep -l is_helgrind "$srcdir"/*.c | grep -v test-common); do echo "WARNING: $(basename $i) has helgrind exception enabled" done echo "Checking for api test coverage" numapicalls=0 found=0 missing=0 for i in $headerapicalls; do [ "$i" = knet_handle_new_ex ] && i=knet_handle_new # tested together numapicalls=$((numapicalls + 1)) if [ -f $srcdir/api_${i}.c ]; then found=$((found + 1)) else missing=$((missing + 1)) echo "MISSING: $i" fi done echo "Summary" echo "-------" echo "Found : $found" echo "Missing : $missing" echo "Total : $numapicalls" which bc > /dev/null 2>&1 && { coverage=$(echo "scale=3; $found / $numapicalls * 100" | bc -l) echo "Coverage: $coverage%" } exit 0 diff --git a/libknet/tests/api_knet_addrtostr.c b/libknet/tests/api_knet_addrtostr.c index cacd19bf..5eefa005 100644 --- a/libknet/tests/api_knet_addrtostr.c +++ b/libknet/tests/api_knet_addrtostr.c @@ -1,127 +1,127 @@ /* * Copyright (C) 2017-2025 Red Hat, Inc. All rights reserved. * * Authors: Fabio M. Di Nitto * Federico Simoncelli * * This software licensed under GPL-2.0+ */ #include "config.h" #include #include #include #include #include #include #include #include "libknet.h" #include "test-common.h" static void test(void) { struct sockaddr_storage addr; struct sockaddr_in *addrv4; struct sockaddr_in6 *addrv6; char addr_str[KNET_MAX_HOST_LEN]; char port_str[KNET_MAX_PORT_LEN]; memset(&addr, 0, sizeof(struct sockaddr_storage)); printf("Checking knet_addrtostr with invalid ss\n"); if (!knet_addrtostr(NULL, sizeof(struct sockaddr_storage), addr_str, KNET_MAX_HOST_LEN, port_str, KNET_MAX_PORT_LEN) || (errno != EINVAL)) { printf("knet_addrtostr accepted invalid ss or returned incorrect error: %s\n", strerror(errno)); exit(FAIL); } printf("Checking knet_addrtostr with invalid sslen\n"); if (!knet_addrtostr(&addr, 0, addr_str, KNET_MAX_HOST_LEN, port_str, KNET_MAX_PORT_LEN) || (errno != EINVAL)) { printf("knet_addrtostr accepted invalid sslen or returned incorrect error: %s\n", strerror(errno)); exit(FAIL); } printf("Checking knet_addrtostr with invalid addr_str\n"); if (!knet_addrtostr(&addr, sizeof(struct sockaddr_storage), NULL, KNET_MAX_HOST_LEN, port_str, KNET_MAX_PORT_LEN) || (errno != EINVAL)) { printf("knet_addrtostr accepted invalid addr_str or returned incorrect error: %s\n", strerror(errno)); exit(FAIL); } printf("Checking knet_addrtostr with invalid port_str\n"); if (!knet_addrtostr(&addr, sizeof(struct sockaddr_storage), addr_str, KNET_MAX_HOST_LEN, NULL, KNET_MAX_PORT_LEN) || (errno != EINVAL)) { printf("knet_addrtostr accepted invalid addr_str or returned incorrect error: %s\n", strerror(errno)); exit(FAIL); } addrv4 = (struct sockaddr_in *)&addr; addrv4->sin_family = AF_INET; addrv4->sin_addr.s_addr = htonl(0xc0a80001); /* 192.168.0.1 */ addrv4->sin_port = htons(50000); printf("Checking knet_addrtostr with valid data (192.168.0.1:50000)\n"); if (knet_addrtostr(&addr, sizeof(struct sockaddr_storage), addr_str, KNET_MAX_HOST_LEN, port_str, KNET_MAX_PORT_LEN) < 0) { printf("Unable to convert 192.168.0.1:50000\n"); exit(FAIL); } if (strcmp(addr_str, "192.168.0.1") != 0) { printf("Wrong address conversion: %s\n", addr_str); exit(EXIT_FAILURE); } if (strcmp(port_str, "50000") != 0) { printf("Wrong port conversion: %s\n", port_str); exit(EXIT_FAILURE); } printf("Checking knet_addrtostr with valid data ([fd00::1]:50000)\n"); memset(&addr, 0, sizeof(struct sockaddr_storage)); addrv6 = (struct sockaddr_in6 *)&addr; addrv6->sin6_family = AF_INET6; - addrv6->sin6_addr.s6_addr16[0] = htons(0xfd00); /* fd00::1 */ - addrv6->sin6_addr.s6_addr16[7] = htons(0x0001); + addrv6->sin6_addr.s6_addr32[0] = htonl(0xfd000000); /* fd00::1 */ + addrv6->sin6_addr.s6_addr32[3] = htonl(0x00000001); addrv6->sin6_port = htons(50000); if (knet_addrtostr(&addr, sizeof(struct sockaddr_storage), addr_str, KNET_MAX_HOST_LEN, port_str, KNET_MAX_PORT_LEN) < 0) { printf("Unable to convert [fd00::1]:50000\n"); exit(FAIL); } if (strcmp(addr_str, "fd00::1") != 0) { printf("Wrong address conversion: %s\n", addr_str); exit(FAIL); } if (strcmp(port_str, "50000") != 0) { printf("Wrong port conversion: %s\n", port_str); exit(EXIT_FAILURE); } } int main(int argc, char *argv[]) { test(); exit(PASS); } diff --git a/libknet/tests/api_knet_handle_new_limit.c b/libknet/tests/api_knet_handle_new_limit.c index ed606a0d..07c6d2b0 100644 --- a/libknet/tests/api_knet_handle_new_limit.c +++ b/libknet/tests/api_knet_handle_new_limit.c @@ -1,70 +1,70 @@ /* * Copyright (C) 2017-2025 Red Hat, Inc. All rights reserved. * * Authors: Fabio M. Di Nitto * * This software licensed under GPL-2.0+ */ #include "config.h" #include #include #include #include #include #include #include #include "libknet.h" #include "internals.h" #include "test-common.h" static void test(void) { knet_handle_t knet_h[UINT8_MAX + 1]; int logfds[2]; - int idx; + unsigned int idx; setup_logpipes(logfds); for (idx = 0; idx < UINT8_MAX; idx++) { printf("Allocating %d\n", idx); knet_h[idx] = knet_handle_new(1, logfds[1], KNET_LOG_DEBUG); if (!knet_h[idx]) { printf("knet_handle_new[%d] failed: %s\n", idx, strerror(errno)); flush_logs(logfds[0], stdout); close_logpipes(logfds); exit(FAIL); } flush_logs(logfds[0], stdout); } printf("forcing UINT8_T MAX\n"); knet_h[UINT8_MAX] = knet_handle_new(1, logfds[1], KNET_LOG_DEBUG); if (knet_h[UINT8_MAX]) { printf("off by one somewhere\n"); knet_handle_free(knet_h[UINT8_MAX]); } flush_logs(logfds[0], stdout); for (idx = 0; idx < UINT8_MAX; idx++) { printf("Freeing %d\n", idx); knet_handle_free(knet_h[idx]); flush_logs(logfds[0], stdout); } flush_logs(logfds[0], stdout); close_logpipes(logfds); } int main(int argc, char *argv[]) { if ((is_memcheck()) || (is_helgrind())) { return SKIP; } test(); return PASS; } diff --git a/libknet/tests/api_knet_send.c b/libknet/tests/api_knet_send.c index 604ac7ec..376084f5 100644 --- a/libknet/tests/api_knet_send.c +++ b/libknet/tests/api_knet_send.c @@ -1,177 +1,168 @@ /* * Copyright (C) 2016-2025 Red Hat, Inc. All rights reserved. * * Authors: Fabio M. Di Nitto * * This software licensed under GPL-2.0+ */ #include "config.h" #include #include #include #include #include #include #include "libknet.h" #include "internals.h" #include "netutils.h" #include "test-common.h" static int private_data; static void sock_notify(void *pvt_data, int datafd, int8_t channel, uint8_t tx_rx, int error, int errorno) { return; } static void test(uint8_t transport) { knet_handle_t knet_h1, knet_h[2]; int logfds[2]; int datafd = 0; int8_t channel = 0; struct knet_link_status link_status; char send_buff[KNET_MAX_PACKET_SIZE + 1]; char recv_buff[KNET_MAX_PACKET_SIZE]; ssize_t send_len = 0; int recv_len = 0; int savederrno; int res; struct sockaddr_storage lo; memset(send_buff, 0, sizeof(send_buff)); printf("Test knet_send incorrect knet_h\n"); if ((!knet_send(NULL, send_buff, KNET_MAX_PACKET_SIZE, channel)) || (errno != EINVAL)) { printf("knet_send accepted invalid knet_h or returned incorrect error: %s\n", strerror(errno)); exit(FAIL); } setup_logpipes(logfds); knet_h1 = knet_handle_start(logfds, KNET_LOG_DEBUG, knet_h); FAIL_ON_ERR(knet_handle_enable_access_lists(knet_h1, 1)); printf("Test knet_send with no send_buff\n"); FAIL_ON_SUCCESS(knet_send(knet_h1, NULL, KNET_MAX_PACKET_SIZE, channel), EINVAL); printf("Test knet_send with invalid send_buff len (0)\n"); FAIL_ON_SUCCESS(knet_send(knet_h1, send_buff, 0, channel), EINVAL); printf("Test knet_send with invalid send_buff len (> KNET_MAX_PACKET_SIZE)\n"); FAIL_ON_SUCCESS(knet_send(knet_h1, send_buff, KNET_MAX_PACKET_SIZE + 1, channel), EINVAL); printf("Test knet_send with invalid channel (-1)\n"); channel = -1; FAIL_ON_SUCCESS(knet_send(knet_h1, send_buff, KNET_MAX_PACKET_SIZE, channel), EINVAL); printf("Test knet_send with invalid channel (KNET_DATAFD_MAX)\n"); channel = KNET_DATAFD_MAX; FAIL_ON_SUCCESS(knet_send(knet_h1, send_buff, KNET_MAX_PACKET_SIZE, channel), EINVAL); printf("Test knet_send with unconfigured channel\n"); channel = 0; FAIL_ON_SUCCESS(knet_send(knet_h1, send_buff, KNET_MAX_PACKET_SIZE, channel), EINVAL); printf("Test knet_send with valid data\n"); FAIL_ON_ERR(knet_handle_enable_access_lists(knet_h1, 1)); FAIL_ON_ERR(knet_handle_enable_sock_notify(knet_h1, &private_data, sock_notify)); datafd = 0; channel = -1; FAIL_ON_ERR(knet_handle_add_datafd(knet_h1, &datafd, &channel)); FAIL_ON_ERR(knet_host_add(knet_h1, 1)); if (_knet_link_set_config(knet_h1, 1, 0, transport, 0, AF_INET, 0, &lo) < 0 ) { int exit_status = transport == KNET_TRANSPORT_SCTP && errno == EPROTONOSUPPORT ? SKIP : FAIL; printf("Unable to configure link: %s\n", strerror(errno)); CLEAN_EXIT(exit_status); } FAIL_ON_ERR(knet_link_set_enable(knet_h1, 1, 0, 1)); FAIL_ON_ERR(knet_handle_setfwd(knet_h1, 1)); FAIL_ON_ERR(wait_for_host(knet_h1, 1, 10, logfds[0], stdout)); send_len = knet_send(knet_h1, send_buff, KNET_MAX_PACKET_SIZE, channel); if (send_len <= 0) { printf("knet_send failed: %s\n", strerror(errno)); CLEAN_EXIT(FAIL); } if (send_len != sizeof(send_buff) - 1) { printf("knet_send sent only %zd bytes: %s\n", send_len, strerror(errno)); CLEAN_EXIT(FAIL); } FAIL_ON_ERR(knet_handle_setfwd(knet_h1, 0)); FAIL_ON_ERR(wait_for_packet(knet_h1, 10, datafd, logfds[0], stdout)); recv_len = knet_recv(knet_h1, recv_buff, KNET_MAX_PACKET_SIZE, channel); savederrno = errno; if (recv_len != send_len) { printf("knet_recv received only %d bytes: %s (errno: %d)\n", recv_len, strerror(errno), errno); if ((is_helgrind()) && (recv_len == -1) && (savederrno == EAGAIN)) { printf("helgrind exception. this is normal due to possible timeouts\n"); CLEAN_EXIT(PASS); } CLEAN_EXIT(FAIL); } if (memcmp(recv_buff, send_buff, KNET_MAX_PACKET_SIZE)) { printf("recv and send buffers are different!\n"); CLEAN_EXIT(FAIL); } /* A sanity check on the stats */ FAIL_ON_ERR(knet_link_get_status(knet_h1, 1, 0, &link_status, sizeof(link_status))); if (link_status.stats.tx_data_packets != 2 || link_status.stats.rx_data_packets != 2 || link_status.stats.tx_data_bytes < KNET_MAX_PACKET_SIZE || link_status.stats.rx_data_bytes < KNET_MAX_PACKET_SIZE || link_status.stats.tx_data_bytes > KNET_MAX_PACKET_SIZE*2 || link_status.stats.rx_data_bytes > KNET_MAX_PACKET_SIZE*2) { printf("stats look wrong: tx_packets: %" PRIu64 " (%" PRIu64 " bytes), rx_packets: %" PRIu64 " (%" PRIu64 " bytes)\n", link_status.stats.tx_data_packets, link_status.stats.tx_data_bytes, link_status.stats.rx_data_packets, link_status.stats.rx_data_bytes); } FAIL_ON_ERR(knet_handle_setfwd(knet_h1, 1)); - printf("try to send big packet to local datafd (bypass knet_send)\n"); - if (write(datafd, &send_buff, sizeof(send_buff)) != KNET_MAX_PACKET_SIZE + 1) { - printf("Error writing to datafd: %s\n", strerror(errno)); - } - - if (!wait_for_packet(knet_h1, 2, datafd, logfds[0], stdout)) { - printf("Received unexpected packet!\n"); - CLEAN_EXIT(FAIL); - } CLEAN_EXIT(CONTINUE); } int main(int argc, char *argv[]) { printf("Testing with UDP\n"); test(KNET_TRANSPORT_UDP); #ifdef HAVE_NETINET_SCTP_H printf("Testing with SCTP\n"); test(KNET_TRANSPORT_SCTP); #endif return PASS; } diff --git a/libknet/tests/api_knet_strtoaddr.c b/libknet/tests/api_knet_strtoaddr.c index ec858b70..3e207ddb 100644 --- a/libknet/tests/api_knet_strtoaddr.c +++ b/libknet/tests/api_knet_strtoaddr.c @@ -1,115 +1,115 @@ /* * Copyright (C) 2017-2025 Red Hat, Inc. All rights reserved. * * Authors: Fabio M. Di Nitto * Federico Simoncelli * * This software licensed under GPL-2.0+ */ #include "config.h" #include #include #include #include #include #include #include #include "libknet.h" #include "test-common.h" static void test(void) { struct sockaddr_storage out_addr; struct sockaddr_in *out_addrv4 = (struct sockaddr_in *)&out_addr; struct sockaddr_in6 *out_addrv6 = (struct sockaddr_in6 *)&out_addr; struct sockaddr_in addrv4; struct sockaddr_in6 addrv6; memset(&out_addr, 0, sizeof(struct sockaddr_storage)); memset(&addrv4, 0, sizeof(struct sockaddr_in)); memset(&addrv6, 0, sizeof(struct sockaddr_in6)); printf("Checking knet_strtoaddr with invalid host\n"); if (!knet_strtoaddr(NULL, "50000", &out_addr, sizeof(struct sockaddr_storage)) || (errno != EINVAL)) { printf("knet_strtoaddr accepted invalid host or returned incorrect error: %s\n", strerror(errno)); exit(FAIL); } printf("Checking knet_strtoaddr with invalid port\n"); if (!knet_strtoaddr("127.0.0.1", NULL, &out_addr, sizeof(struct sockaddr_storage)) || (errno != EINVAL)) { printf("knet_strtoaddr accepted invalid port or returned incorrect error: %s\n", strerror(errno)); exit(FAIL); } printf("Checking knet_strtoaddr with invalid addr\n"); if (!knet_strtoaddr("127.0.0.1", "50000", NULL, sizeof(struct sockaddr_storage)) || (errno != EINVAL)) { printf("knet_strtoaddr accepted invalid addr or returned incorrect error: %s\n", strerror(errno)); exit(FAIL); } printf("Checking knet_strtoaddr with invalid size\n"); if (!knet_strtoaddr("127.0.0.1", "50000", &out_addr, 0) || (errno != EINVAL)) { printf("knet_strtoaddr accepted invalid size or returned incorrect error: %s\n", strerror(errno)); exit(FAIL); } addrv4.sin_family = AF_INET; addrv4.sin_addr.s_addr = htonl(0xc0a80001); /* 192.168.0.1 */ addrv4.sin_port = htons(50000); printf("Checking knet_strtoaddr with valid data (192.168.0.1:50000)\n"); if (knet_strtoaddr("192.168.0.1", "50000", &out_addr, sizeof(struct sockaddr_storage))) { printf("Unable to convert 192.168.0.1:50000\n"); exit(FAIL); } if (out_addrv4->sin_family != addrv4.sin_family || out_addrv4->sin_port != addrv4.sin_port || out_addrv4->sin_addr.s_addr != addrv4.sin_addr.s_addr) { printf("Check on 192.168.0.1:50000 failed\n"); exit(FAIL); } printf("Checking knet_strtoaddr with valid data ([fd00::1]:50000)\n"); memset(&out_addr, 0, sizeof(struct sockaddr_storage)); addrv6.sin6_family = AF_INET6; - addrv6.sin6_addr.s6_addr16[0] = htons(0xfd00); /* fd00::1 */ - addrv6.sin6_addr.s6_addr16[7] = htons(0x0001); + addrv6.sin6_addr.s6_addr32[0] = htonl(0xfd000000); /* fd00::1 */ + addrv6.sin6_addr.s6_addr32[3] = htonl(0x00000001); addrv6.sin6_port = htons(50000); if (knet_strtoaddr("fd00::1", "50000", &out_addr, sizeof(struct sockaddr_storage))) { printf("Unable to convert fd00::1:50000\n"); exit(FAIL); } if (out_addrv6->sin6_family != addrv6.sin6_family || out_addrv6->sin6_port != addrv6.sin6_port || memcmp(&out_addrv6->sin6_addr, &addrv6.sin6_addr, sizeof(struct in6_addr))) { printf("Check on fd00::1:50000 failed\n"); exit(FAIL); } } int main(int argc, char *argv[]) { test(); exit(PASS); } diff --git a/libknet/tests/fun_pmtud_crypto.c b/libknet/tests/fun_pmtud_crypto.c index b19e236a..79d3a6ed 100644 --- a/libknet/tests/fun_pmtud_crypto.c +++ b/libknet/tests/fun_pmtud_crypto.c @@ -1,243 +1,247 @@ /* * Copyright (C) 2016-2025 Red Hat, Inc. All rights reserved. * * Authors: Fabio M. Di Nitto * * This software licensed under GPL-2.0+ */ #include "config.h" #include #include #include #include #include #include #include +#ifdef KNET_SOLARIS +#include +#else #include +#endif #include #include #include "libknet.h" #include "compress.h" #include "internals.h" #include "netutils.h" #include "onwire.h" #include "test-common.h" static int private_data; static void sock_notify(void *pvt_data, int datafd, int8_t channel, uint8_t tx_rx, int error, int errorno) { return; } static int iface_fd = 0; static int default_mtu = 0; #ifdef KNET_LINUX const char *loopback = "lo"; #endif -#ifdef KNET_BSD +#if defined(KNET_BSD) || defined(KNET_SOLARIS) const char *loopback = "lo0"; #endif static int fd_init(void) { #ifdef KNET_LINUX return socket(AF_INET, SOCK_STREAM, 0); #endif #ifdef KNET_BSD return socket(AF_LOCAL, SOCK_DGRAM, 0); #endif return -1; } static int set_iface_mtu(uint32_t mtu) { int err = 0; struct ifreq ifr; memset(&ifr, 0, sizeof(struct ifreq)); strncpy(ifr.ifr_name, loopback, IFNAMSIZ - 1); ifr.ifr_mtu = mtu; err = ioctl(iface_fd, SIOCSIFMTU, &ifr); return err; } static int get_iface_mtu(void) { int err = 0, savederrno = 0; struct ifreq ifr; memset(&ifr, 0, sizeof(struct ifreq)); strncpy(ifr.ifr_name, loopback, IFNAMSIZ - 1); err = ioctl(iface_fd, SIOCGIFMTU, &ifr); if (err) { savederrno = errno; goto out_clean; } err = ifr.ifr_mtu; out_clean: errno = savederrno; return err; } static void exit_local(int exit_code) { set_iface_mtu(default_mtu); close(iface_fd); iface_fd = 0; exit(exit_code); } #define TESTNODES 1 static void test_mtu(const char *model, const char *crypto, const char *hash) { knet_handle_t knet_h[TESTNODES+1]; int logfds[2]; int datafd = 0; int8_t channel = 0; struct sockaddr_storage lo; struct knet_handle_crypto_cfg knet_handle_crypto_cfg; unsigned int data_mtu, expected_mtu; size_t calculated_iface_mtu = 0, detected_iface_mtu = 0; int res; setup_logpipes(logfds); knet_h[1] = knet_handle_start(logfds, KNET_LOG_DEBUG, knet_h); flush_logs(logfds[0], stdout); printf("Test knet_send with %s and valid data\n", model); memset(&knet_handle_crypto_cfg, 0, sizeof(struct knet_handle_crypto_cfg)); strncpy(knet_handle_crypto_cfg.crypto_model, model, sizeof(knet_handle_crypto_cfg.crypto_model) - 1); strncpy(knet_handle_crypto_cfg.crypto_cipher_type, crypto, sizeof(knet_handle_crypto_cfg.crypto_cipher_type) - 1); strncpy(knet_handle_crypto_cfg.crypto_hash_type, hash, sizeof(knet_handle_crypto_cfg.crypto_hash_type) - 1); knet_handle_crypto_cfg.private_key_len = 2000; FAIL_ON_ERR(knet_handle_crypto(knet_h[1], &knet_handle_crypto_cfg)); FAIL_ON_ERR(knet_handle_enable_sock_notify(knet_h[1], &private_data, sock_notify)); // CHECK cond was <0 not !=0 datafd = 0; channel = -1; FAIL_ON_ERR(knet_handle_add_datafd(knet_h[1], &datafd, &channel)); FAIL_ON_ERR(knet_host_add(knet_h[1], 1)); FAIL_ON_ERR(_knet_link_set_config(knet_h[1], 1, 0, KNET_TRANSPORT_UDP, 0, AF_INET, 0, &lo)); FAIL_ON_ERR(knet_link_set_pong_count(knet_h[1], 1, 0, 1)); FAIL_ON_ERR(knet_link_set_enable(knet_h[1], 1, 0, 1)); FAIL_ON_ERR(wait_for_host(knet_h[1], 1, 4, logfds[0], stdout)); flush_logs(logfds[0], stdout); FAIL_ON_ERR(knet_handle_pmtud_get(knet_h[1], &data_mtu)); calculated_iface_mtu = calc_data_outlen(knet_h[1], data_mtu + KNET_HEADER_ALL_SIZE) + 28; detected_iface_mtu = get_iface_mtu(); /* * 28 = 20 IP header + 8 UDP header */ expected_mtu = calc_max_data_outlen(knet_h[1], detected_iface_mtu - 28); if (expected_mtu != data_mtu) { printf("Wrong MTU detected! interface mtu: %zu knet mtu: %u expected mtu: %u\n", detected_iface_mtu, data_mtu, expected_mtu); clean_exit(knet_h, TESTNODES, logfds, FAIL); } if ((detected_iface_mtu - calculated_iface_mtu) >= knet_h[1]->sec_block_size) { printf("Wrong MTU detected! real iface mtu: %zu calculated: %zu\n", detected_iface_mtu, calculated_iface_mtu); clean_exit(knet_h, TESTNODES, logfds, FAIL); } knet_handle_stop_everything(knet_h, TESTNODES); close_logpipes(logfds); } static void test(const char *model, const char *crypto, const char *hash) { int i = 576; int max = 65535; while (i <= max) { printf("Setting interface MTU to: %i\n", i); set_iface_mtu(i); test_mtu(model, crypto, hash); if (i == max) { break; } i = i + 15; if (i > max) { i = max; } } } int main(int argc, char *argv[]) { struct knet_crypto_info crypto_list[16]; size_t crypto_list_entries; #ifdef KNET_BSD if (is_memcheck() || is_helgrind()) { printf("valgrind-freebsd cannot run this test properly. Skipping\n"); return SKIP; } #endif if (geteuid() != 0) { printf("This test requires root privileges\n"); return SKIP; } iface_fd = fd_init(); if (iface_fd < 0) { printf("fd_init failed: %s\n", strerror(errno)); return FAIL; } default_mtu = get_iface_mtu(); if (default_mtu < 0) { printf("get_iface_mtu failed: %s\n", strerror(errno)); return FAIL; } memset(crypto_list, 0, sizeof(crypto_list)); if (knet_get_crypto_list(crypto_list, &crypto_list_entries) < 0) { printf("knet_get_crypto_list failed: %s\n", strerror(errno)); return FAIL; } if (crypto_list_entries == 0) { printf("no crypto modules detected. Skipping\n"); return SKIP; } test(crypto_list[0].name, "aes128", "sha1"); test(crypto_list[0].name, "aes128", "sha256"); test(crypto_list[0].name, "aes256", "sha1"); test(crypto_list[0].name, "aes256", "sha256"); exit_local(PASS); } diff --git a/libknet/tests/knet_bench.c b/libknet/tests/knet_bench.c index 1fc6aae1..9aaa5fdc 100644 --- a/libknet/tests/knet_bench.c +++ b/libknet/tests/knet_bench.c @@ -1,1382 +1,1382 @@ /* * Copyright (C) 2016-2025 Red Hat, Inc. All rights reserved. * * Authors: Fabio M. Di Nitto * * This software licensed under GPL-2.0+ */ #include "config.h" #include #include #include #include #include #include #include #include #include #include "libknet.h" #include "compat.h" #include "internals.h" #include "netutils.h" #include "transport_common.h" #include "threads_common.h" #include "test-common.h" #define MAX_NODES 128 static int senderid = -1; static int thisnodeid = -1; static knet_handle_t knet_h; static int datafd = 0; static int8_t channel = 0; static int globallistener = 0; static int continous = 0; static int show_stats = 0; static struct sockaddr_storage allv4; static struct sockaddr_storage allv6; static int broadcast_test = 1; -static pthread_t rx_thread = (pthread_t)NULL; +static pthread_t rx_thread = {0}; static char *rx_buf[PCKT_FRAG_MAX]; static int wait_for_perf_rx = 0; static char *compresscfg = NULL; static char *cryptocfg = NULL; static int machine_output = 0; static int use_access_lists = 0; static int use_pckt_verification = 0; static int bench_shutdown_in_progress = 0; static pthread_mutex_t shutdown_mutex = PTHREAD_MUTEX_INITIALIZER; #define TEST_PING 0 #define TEST_PING_AND_DATA 1 #define TEST_PERF_BY_SIZE 2 #define TEST_PERF_BY_TIME 3 static int test_type = TEST_PING; #define TEST_START 2 #define TEST_STOP 4 #define TEST_COMPLETE 6 #define ONE_GIGABYTE 1073741824 static uint64_t perf_by_size_size = 1 * ONE_GIGABYTE; static uint64_t perf_by_time_secs = 10; static uint32_t force_packet_size = 0; struct node { int nodeid; int links; uint8_t transport[KNET_MAX_LINK]; struct sockaddr_storage address[KNET_MAX_LINK]; }; struct pckt_ver { uint32_t len; uint32_t chksum; }; static void print_help(void) { printf("knet_bench usage:\n"); printf(" -h print this help (no really)\n"); printf(" -d enable debug logs (default INFO)\n"); printf(" -f enable use of access lists (default: off)\n"); printf(" -c [implementation]:[crypto]:[hashing] crypto configuration. (default disabled)\n"); printf(" Example: -c nss:aes128:sha1\n"); printf(" -z [implementation]:[level]:[threshold] compress configuration. (default disabled)\n"); printf(" Example: -z zlib:5:100\n"); printf(" -p [active|passive|rr] (default: passive)\n"); printf(" -P [UDP|SCTP] (default: UDP) protocol (transport) to use for all links\n"); printf(" -t [nodeid] This nodeid (required)\n"); printf(" -n [nodeid],[proto]/[link1_ip],[link2_..] Other nodes information (at least one required)\n"); printf(" Example: -n 1,192.168.8.1,SCTP/3ffe::8:1,UDP/172...\n"); printf(" can be repeated up to %d and should contain also the localnode info\n", MAX_NODES); printf(" -b [port] baseport (default: 50000)\n"); printf(" -l enable global listener on 0.0.0.0/:: (default: off, incompatible with -o)\n"); printf(" -o enable baseport offset per nodeid\n"); printf(" -m change PMTUd interval in seconds (default: 60)\n"); printf(" -w dont wait for all nodes to be up before starting the test (default: wait)\n"); printf(" -T [ping|ping_data|perf-by-size|perf-by-time]\n"); printf(" test type (default: ping)\n"); printf(" ping: will wait for all hosts to join the knet network, sleep 5 seconds and quit\n"); printf(" ping_data: will wait for all hosts to join the knet network, sends some data to all nodes and quit\n"); printf(" perf-by-size: will wait for all hosts to join the knet network,\n"); printf(" perform a series of benchmarks by transmitting a known\n"); printf(" size/quantity of packets and measuring the time, then quit\n"); printf(" perf-by-time: will wait for all hosts to join the knet network,\n"); printf(" perform a series of benchmarks by transmitting a known\n"); printf(" size of packets for a given amount of time (10 seconds)\n"); printf(" and measuring the quantity of data transmitted, then quit\n"); printf(" -s nodeid that will generate traffic for benchmarks\n"); printf(" -S [size|seconds] when used in combination with -T perf-by-size it indicates how many GB of traffic to generate for the test. (default: 1GB)\n"); printf(" when used in combination with -T perf-by-time it indicates how many Seconds of traffic to generate for the test. (default: 10 seconds)\n"); printf(" -x force packet size for perf-by-time or perf-by-size\n"); printf(" -C repeat the test continously (default: off)\n"); printf(" -X[XX] show stats at the end of the run (default: 1)\n"); printf(" 1: show handle stats, 2: show summary link stats\n"); printf(" 3: show detailed link stats\n"); printf(" -a enable machine parsable output (default: off).\n"); printf(" -v enable packet verification for performance tests (default: off).\n"); } static void parse_nodes(char *nodesinfo[MAX_NODES], int onidx, int port, struct node nodes[MAX_NODES], int *thisidx) { int i; char *temp = NULL; char port_str[11]; memset(port_str, 0, sizeof(port_str)); snprintf(port_str, sizeof(port_str), "%d", port); for (i = 0; i < onidx; i++) { nodes[i].nodeid = atoi(strtok(nodesinfo[i], ",")); if ((nodes[i].nodeid < 0) || (nodes[i].nodeid > KNET_MAX_HOST)) { printf("Invalid nodeid: %d (0 - %d)\n", nodes[i].nodeid, KNET_MAX_HOST); exit(FAIL); } if (thisnodeid == nodes[i].nodeid) { *thisidx = i; } while((temp = strtok(NULL, ","))) { char *slash = NULL; uint8_t transport; if (nodes[i].links == KNET_MAX_LINK) { printf("Too many links configured. Max %d\n", KNET_MAX_LINK); exit(FAIL); } slash = strstr(temp, "/"); if (slash) { memset(slash, 0, 1); transport = knet_get_transport_id_by_name(temp); if (transport == KNET_MAX_TRANSPORTS) { printf("Unknown transport: %s\n", temp); exit(FAIL); } nodes[i].transport[nodes[i].links] = transport; temp = slash + 1; } else { nodes[i].transport[nodes[i].links] = KNET_TRANSPORT_UDP; } if (knet_strtoaddr(temp, port_str, &nodes[i].address[nodes[i].links], sizeof(struct sockaddr_storage)) < 0) { printf("Unable to convert %s to sockaddress\n", temp); exit(FAIL); } nodes[i].links++; } } if (knet_strtoaddr("0.0.0.0", port_str, &allv4, sizeof(struct sockaddr_storage)) < 0) { printf("Unable to convert 0.0.0.0 to sockaddress\n"); exit(FAIL); } if (knet_strtoaddr("::", port_str, &allv6, sizeof(struct sockaddr_storage)) < 0) { printf("Unable to convert :: to sockaddress\n"); exit(FAIL); } for (i = 1; i < onidx; i++) { if (nodes[0].links != nodes[i].links) { printf("knet_bench does not support unbalanced link configuration\n"); exit(FAIL); } } return; } static int private_data; static void sock_notify(void *pvt_data, int local_datafd, int8_t local_channel, uint8_t tx_rx, int error, int errorno) { printf("[info]: error (%d - %d - %s) from socket: %d\n", error, errorno, strerror(errno), local_datafd); return; } static int ping_dst_host_filter(void *pvt_data, const unsigned char *outdata, ssize_t outdata_len, uint8_t tx_rx, knet_node_id_t this_host_id, knet_node_id_t src_host_id, int8_t *dst_channel, knet_node_id_t *dst_host_ids, size_t *dst_host_ids_entries) { if (broadcast_test) { return 1; } if (tx_rx == KNET_NOTIFY_TX) { memmove(&dst_host_ids[0], outdata, 2); } else { dst_host_ids[0] = this_host_id; } *dst_host_ids_entries = 1; return 0; } static void setup_knet(int argc, char *argv[]) { int logfd = 0; int rv; char *policystr = NULL, *protostr = NULL; char *othernodeinfo[MAX_NODES]; struct node nodes[MAX_NODES]; int thisidx = -1; int onidx = 0; int debug = KNET_LOG_INFO; int port = 50000, portoffset = 0; int thisport = 0, otherport = 0; int thisnewport = 0, othernewport = 0; struct sockaddr_in *so_in; struct sockaddr_in6 *so_in6; struct sockaddr_storage *src; int i, link_idx, allnodesup = 0; int policy = KNET_LINK_POLICY_PASSIVE, policyfound = 0; int protocol = KNET_TRANSPORT_UDP, protofound = 0; int wait = 1; int pmtud_interval = 60; struct knet_handle_crypto_cfg knet_handle_crypto_cfg; char *cryptomodel = NULL, *cryptotype = NULL, *cryptohash = NULL; struct knet_handle_compress_cfg knet_handle_compress_cfg; memset(nodes, 0, sizeof(nodes)); while ((rv = getopt(argc, argv, "aCT:S:s:lvdfom:wb:t:n:c:p:x:X::P:z:h")) != EOF) { switch(rv) { case 'h': print_help(); exit(PASS); break; case 'a': machine_output = 1; break; case 'd': debug = KNET_LOG_DEBUG; break; case 'f': use_access_lists = 1; break; case 'c': if (cryptocfg) { printf("Error: -c can only be specified once\n"); exit(FAIL); } cryptocfg = optarg; break; case 'p': if (policystr) { printf("Error: -p can only be specified once\n"); exit(FAIL); } if (optarg) { policystr = optarg; if (!strcmp(policystr, "active")) { policy = KNET_LINK_POLICY_ACTIVE; policyfound = 1; } /* * we can't use rr because clangs can't compile * an array of 3 strings, one of which is 2 bytes long */ if (!strcmp(policystr, "round-robin")) { policy = KNET_LINK_POLICY_RR; policyfound = 1; } if (!strcmp(policystr, "passive")) { policy = KNET_LINK_POLICY_PASSIVE; policyfound = 1; } } if (!policyfound) { printf("Error: invalid policy %s specified. -p accepts active|passive|rr\n", policystr); exit(FAIL); } break; case 'P': if (protostr) { printf("Error: -P can only be specified once\n"); exit(FAIL); } if (optarg) { protostr = optarg; if (!strcmp(protostr, "UDP")) { protocol = KNET_TRANSPORT_UDP; protofound = 1; } if (!strcmp(protostr, "SCTP")) { protocol = KNET_TRANSPORT_SCTP; protofound = 1; } } if (!protofound) { printf("Error: invalid protocol %s specified. -P accepts udp|sctp\n", policystr); exit(FAIL); } break; case 't': if (thisnodeid >= 0) { printf("Error: -t can only be specified once\n"); exit(FAIL); } thisnodeid = atoi(optarg); if ((thisnodeid < 0) || (thisnodeid > 65536)) { printf("Error: -t nodeid out of range %d (1 - 65536)\n", thisnodeid); exit(FAIL); } break; case 'n': if (onidx == MAX_NODES) { printf("Error: too many other nodes. Max %d\n", MAX_NODES); exit(FAIL); } othernodeinfo[onidx] = optarg; onidx++; break; case 'b': port = atoi(optarg); if ((port < 1) || (port > 65536)) { printf("Error: port %d out of range (1 - 65536)\n", port); exit(FAIL); } break; case 'o': if (globallistener) { printf("Error: -l cannot be used with -o\n"); exit(FAIL); } portoffset = 1; break; case 'm': pmtud_interval = atoi(optarg); if (pmtud_interval < 1) { printf("Error: pmtud interval %d out of range (> 0)\n", pmtud_interval); exit(FAIL); } break; case 'l': if (portoffset) { printf("Error: -o cannot be used with -l\n"); exit(FAIL); } globallistener = 1; break; case 'w': wait = 0; break; case 's': if (senderid >= 0) { printf("Error: -s can only be specified once\n"); exit(FAIL); } senderid = atoi(optarg); if ((senderid < 0) || (senderid > 65536)) { printf("Error: -s nodeid out of range %d (1 - 65536)\n", senderid); exit(FAIL); } break; case 'T': if (optarg) { if (!strcmp("ping", optarg)) { test_type = TEST_PING; } if (!strcmp("ping_data", optarg)) { test_type = TEST_PING_AND_DATA; } if (!strcmp("perf-by-size", optarg)) { test_type = TEST_PERF_BY_SIZE; } if (!strcmp("perf-by-time", optarg)) { test_type = TEST_PERF_BY_TIME; } } else { printf("Error: -T requires an option\n"); exit(FAIL); } break; case 'S': perf_by_size_size = (uint64_t)atoi(optarg) * ONE_GIGABYTE; perf_by_time_secs = (uint64_t)atoi(optarg); break; case 'x': force_packet_size = (uint32_t)atoi(optarg); if ((force_packet_size < 64) || (force_packet_size > 65536)) { printf("Unsupported packet size %u (accepted 64 - 65536)\n", force_packet_size); exit(FAIL); } break; case 'v': use_pckt_verification = 1; break; case 'C': continous = 1; break; case 'X': if (optarg) { show_stats = atoi(optarg); } else { show_stats = 1; } break; case 'z': if (compresscfg) { printf("Error: -c can only be specified once\n"); exit(FAIL); } compresscfg = optarg; break; default: break; } } if (thisnodeid < 0) { printf("Who am I?!? missing -t from command line?\n"); exit(FAIL); } if (onidx < 1) { printf("no other nodes configured?!? missing -n from command line\n"); exit(FAIL); } parse_nodes(othernodeinfo, onidx, port, nodes, &thisidx); if (thisidx < 0) { printf("no config for this node found\n"); exit(FAIL); } if (senderid >= 0) { for (i=0; i < onidx; i++) { if (senderid == nodes[i].nodeid) { break; } } if (i == onidx) { printf("Unable to find senderid in nodelist\n"); exit(FAIL); } } if (((test_type == TEST_PERF_BY_SIZE) || (test_type == TEST_PERF_BY_TIME)) && (senderid < 0)) { printf("Error: performance test requires -s to be set (for now)\n"); exit(FAIL); } logfd = start_logging(stdout); knet_h = knet_handle_new(thisnodeid, logfd, debug); if (!knet_h) { printf("Unable to knet_handle_new: %s\n", strerror(errno)); exit(FAIL); } if (knet_handle_enable_access_lists(knet_h, use_access_lists) < 0) { printf("Unable to knet_handle_enable_access_lists: %s\n", strerror(errno)); exit(FAIL); } if (cryptocfg) { memset(&knet_handle_crypto_cfg, 0, sizeof(knet_handle_crypto_cfg)); cryptomodel = strtok(cryptocfg, ":"); cryptotype = strtok(NULL, ":"); cryptohash = strtok(NULL, ":"); if (cryptomodel) { strncpy(knet_handle_crypto_cfg.crypto_model, cryptomodel, sizeof(knet_handle_crypto_cfg.crypto_model) - 1); } if (cryptotype) { strncpy(knet_handle_crypto_cfg.crypto_cipher_type, cryptotype, sizeof(knet_handle_crypto_cfg.crypto_cipher_type) - 1); } if (cryptohash) { strncpy(knet_handle_crypto_cfg.crypto_hash_type, cryptohash, sizeof(knet_handle_crypto_cfg.crypto_hash_type) - 1); } knet_handle_crypto_cfg.private_key_len = KNET_MAX_KEY_LEN; if (knet_handle_crypto(knet_h, &knet_handle_crypto_cfg)) { printf("Unable to init crypto\n"); exit(FAIL); } } if (compresscfg) { memset(&knet_handle_compress_cfg, 0, sizeof(struct knet_handle_compress_cfg)); snprintf(knet_handle_compress_cfg.compress_model, 16, "%s", strtok(compresscfg, ":")); knet_handle_compress_cfg.compress_level = atoi(strtok(NULL, ":")); knet_handle_compress_cfg.compress_threshold = atoi(strtok(NULL, ":")); if (knet_handle_compress(knet_h, &knet_handle_compress_cfg)) { printf("Unable to configure compress\n"); exit(FAIL); } } if (knet_handle_enable_sock_notify(knet_h, &private_data, sock_notify) < 0) { printf("knet_handle_enable_sock_notify failed: %s\n", strerror(errno)); knet_handle_free(knet_h); exit(FAIL); } datafd = 0; channel = -1; if (knet_handle_add_datafd(knet_h, &datafd, &channel) < 0) { printf("knet_handle_add_datafd failed: %s\n", strerror(errno)); knet_handle_free(knet_h); exit(FAIL); } if (knet_handle_pmtud_setfreq(knet_h, pmtud_interval) < 0) { printf("knet_handle_pmtud_setfreq failed: %s\n", strerror(errno)); knet_handle_free(knet_h); exit(FAIL); } for (i=0; i < onidx; i++) { if (i == thisidx) { continue; } if (knet_host_add(knet_h, nodes[i].nodeid) < 0) { printf("knet_host_add failed: %s\n", strerror(errno)); exit(FAIL); } if (knet_host_set_policy(knet_h, nodes[i].nodeid, policy) < 0) { printf("knet_host_set_policy failed: %s\n", strerror(errno)); exit(FAIL); } for (link_idx = 0; link_idx < nodes[i].links; link_idx++) { if (portoffset) { if (nodes[thisidx].address[link_idx].ss_family == AF_INET) { so_in = (struct sockaddr_in *)&nodes[thisidx].address[link_idx]; thisport = ntohs(so_in->sin_port); thisnewport = thisport + nodes[i].nodeid; so_in->sin_port = (htons(thisnewport)); so_in = (struct sockaddr_in *)&nodes[i].address[link_idx]; otherport = ntohs(so_in->sin_port); othernewport = otherport + nodes[thisidx].nodeid; so_in->sin_port = (htons(othernewport)); } else { so_in6 = (struct sockaddr_in6 *)&nodes[thisidx].address[link_idx]; thisport = ntohs(so_in6->sin6_port); thisnewport = thisport + nodes[i].nodeid; so_in6->sin6_port = (htons(thisnewport)); so_in6 = (struct sockaddr_in6 *)&nodes[i].address[link_idx]; otherport = ntohs(so_in6->sin6_port); othernewport = otherport + nodes[thisidx].nodeid; so_in6->sin6_port = (htons(othernewport)); } } if (!globallistener) { src = &nodes[thisidx].address[link_idx]; } else { if (nodes[thisidx].address[link_idx].ss_family == AF_INET) { src = &allv4; } else { src = &allv6; } } /* * -P overrides per link protocol configuration */ if (protofound) { nodes[i].transport[link_idx] = protocol; } if (knet_link_set_config(knet_h, nodes[i].nodeid, link_idx, nodes[i].transport[link_idx], src, &nodes[i].address[link_idx], 0) < 0) { printf("Unable to configure link: %s\n", strerror(errno)); exit(FAIL); } if (portoffset) { if (nodes[thisidx].address[link_idx].ss_family == AF_INET) { so_in = (struct sockaddr_in *)&nodes[thisidx].address[link_idx]; so_in->sin_port = (htons(thisport)); so_in = (struct sockaddr_in *)&nodes[i].address[link_idx]; so_in->sin_port = (htons(otherport)); } else { so_in6 = (struct sockaddr_in6 *)&nodes[thisidx].address[link_idx]; so_in6->sin6_port = (htons(thisport)); so_in6 = (struct sockaddr_in6 *)&nodes[i].address[link_idx]; so_in6->sin6_port = (htons(otherport)); } } if (knet_link_set_enable(knet_h, nodes[i].nodeid, link_idx, 1) < 0) { printf("knet_link_set_enable failed: %s\n", strerror(errno)); exit(FAIL); } if (knet_link_set_ping_timers(knet_h, nodes[i].nodeid, link_idx, 1000, 10000, 2048) < 0) { printf("knet_link_set_ping_timers failed: %s\n", strerror(errno)); exit(FAIL); } if (knet_link_set_pong_count(knet_h, nodes[i].nodeid, link_idx, 2) < 0) { printf("knet_link_set_pong_count failed: %s\n", strerror(errno)); exit(FAIL); } } } if (knet_handle_enable_filter(knet_h, NULL, ping_dst_host_filter)) { printf("Unable to enable dst_host_filter: %s\n", strerror(errno)); exit(FAIL); } if (knet_handle_setfwd(knet_h, 1) < 0) { printf("knet_handle_setfwd failed: %s\n", strerror(errno)); exit(FAIL); } if (wait) { while(!allnodesup) { allnodesup = 1; for (i=0; i < onidx; i++) { if (i == thisidx) { continue; } if (knet_h->host_index[nodes[i].nodeid]->status.reachable != 1) { printf("[info]: waiting host %d to be reachable\n", nodes[i].nodeid); allnodesup = 0; } } if (!allnodesup) { sleep(1); } } sleep(1); } } /* * calculate weak chksum (stole from corosync for debugging purposes) */ static uint32_t compute_chsum(const unsigned char *data, uint32_t data_len) { unsigned int i; unsigned int checksum = 0; for (i = 0; i < data_len; i++) { if (checksum & 1) { checksum |= 0x10000; } checksum = ((checksum >> 1) + (unsigned char)data[i]) & 0xffff; } return (checksum); } static void *_rx_thread(void *args) { int rx_epoll; struct epoll_event ev; struct epoll_event events[KNET_EPOLL_MAX_EVENTS]; struct sockaddr_storage address[PCKT_FRAG_MAX]; struct knet_mmsghdr msg[PCKT_FRAG_MAX]; struct iovec iov_in[PCKT_FRAG_MAX]; int i, msg_recv; struct timespec clock_start, clock_end; unsigned long long time_diff = 0; uint64_t rx_pkts = 0; uint64_t rx_bytes = 0; unsigned int current_pckt_size = 0; - for (i = 0; i < PCKT_FRAG_MAX; i++) { + for (i = 0; (unsigned int)i < PCKT_FRAG_MAX; i++) { rx_buf[i] = malloc(KNET_MAX_PACKET_SIZE); if (!rx_buf[i]) { printf("RXT: Unable to malloc!\nHALTING RX THREAD!\n"); return NULL; } memset(rx_buf[i], 0, KNET_MAX_PACKET_SIZE); iov_in[i].iov_base = (void *)rx_buf[i]; iov_in[i].iov_len = KNET_MAX_PACKET_SIZE; memset(&msg[i].msg_hdr, 0, sizeof(struct msghdr)); msg[i].msg_hdr.msg_name = &address[i]; msg[i].msg_hdr.msg_namelen = sizeof(struct sockaddr_storage); msg[i].msg_hdr.msg_iov = &iov_in[i]; msg[i].msg_hdr.msg_iovlen = 1; } rx_epoll = epoll_create(KNET_EPOLL_MAX_EVENTS + 1); if (rx_epoll < 0) { printf("RXT: Unable to create epoll!\nHALTING RX THREAD!\n"); return NULL; } memset(&ev, 0, sizeof(struct epoll_event)); ev.events = EPOLLIN; ev.data.fd = datafd; if (epoll_ctl(rx_epoll, EPOLL_CTL_ADD, datafd, &ev)) { printf("RXT: Unable to add datafd to epoll\nHALTING RX THREAD!\n"); return NULL; } memset(&clock_start, 0, sizeof(clock_start)); memset(&clock_end, 0, sizeof(clock_start)); while (!bench_shutdown_in_progress) { if (epoll_wait(rx_epoll, events, KNET_EPOLL_MAX_EVENTS, 1) >= 1) { msg_recv = _recvmmsg(datafd, &msg[0], PCKT_FRAG_MAX, MSG_DONTWAIT | MSG_NOSIGNAL); if (msg_recv < 0) { printf("[info]: RXT: error from recvmmsg: %s\n", strerror(errno)); } switch(test_type) { case TEST_PING_AND_DATA: for (i = 0; i < msg_recv; i++) { if (msg[i].msg_len == 0) { printf("[info]: RXT: received 0 bytes message?\n"); } printf("[info]: received %u bytes message: %s\n", msg[i].msg_len, (char *)msg[i].msg_hdr.msg_iov->iov_base); } break; case TEST_PERF_BY_TIME: case TEST_PERF_BY_SIZE: for (i = 0; i < msg_recv; i++) { if (msg[i].msg_len < 64) { if (msg[i].msg_len == 0) { printf("[info]: RXT: received 0 bytes message?\n"); } if (msg[i].msg_len == TEST_START) { if (clock_gettime(CLOCK_MONOTONIC, &clock_start) != 0) { printf("[info]: unable to get start time!\n"); } } if (msg[i].msg_len == TEST_STOP) { double average_rx_mbytes; double average_rx_pkts; double time_diff_sec; if (clock_gettime(CLOCK_MONOTONIC, &clock_end) != 0) { printf("[info]: unable to get end time!\n"); } timespec_diff(clock_start, clock_end, &time_diff); /* * adjust for sleep(2) between sending the last data and TEST_STOP */ time_diff = time_diff - 2000000000llu; /* * convert to seconds */ time_diff_sec = (double)time_diff / 1000000000llu; average_rx_mbytes = (double)((rx_bytes / time_diff_sec) / (1024 * 1024)); average_rx_pkts = (double)(rx_pkts / time_diff_sec); if (!machine_output) { printf("[perf] execution time: %8.4f secs Average speed: %8.4f MB/sec %8.4f pckts/sec (size: %u total: %" PRIu64 ")\n", time_diff_sec, average_rx_mbytes, average_rx_pkts, current_pckt_size, rx_pkts); } else { printf("[perf],%.4f,%u,%" PRIu64 ",%.4f,%.4f\n", time_diff_sec, current_pckt_size, rx_pkts, average_rx_mbytes, average_rx_pkts); } rx_pkts = 0; rx_bytes = 0; current_pckt_size = 0; } if (msg[i].msg_len == TEST_COMPLETE) { wait_for_perf_rx = 1; } continue; } if (use_pckt_verification) { struct pckt_ver *recv_pckt = (struct pckt_ver *)msg[i].msg_hdr.msg_iov->iov_base; uint32_t chksum; if (msg[i].msg_len != recv_pckt->len) { printf("Wrong packet len received: %u expected: %u!\n", msg[i].msg_len, recv_pckt->len); exit(FAIL); } chksum = compute_chsum((const unsigned char *)msg[i].msg_hdr.msg_iov->iov_base + sizeof(struct pckt_ver), msg[i].msg_len - sizeof(struct pckt_ver)); if (recv_pckt->chksum != chksum){ printf("Wrong packet checksum received: %u expected: %u!\n", recv_pckt->chksum, chksum); exit(FAIL); } } rx_pkts++; rx_bytes = rx_bytes + msg[i].msg_len; current_pckt_size = msg[i].msg_len; } break; } } } epoll_ctl(rx_epoll, EPOLL_CTL_DEL, datafd, &ev); close(rx_epoll); return NULL; } static void setup_data_txrx_common(void) { if (!rx_thread) { if (knet_handle_enable_filter(knet_h, NULL, ping_dst_host_filter)) { printf("Unable to enable dst_host_filter: %s\n", strerror(errno)); exit(FAIL); } printf("[info]: setting up rx thread\n"); if (pthread_create(&rx_thread, 0, _rx_thread, NULL)) { printf("Unable to start rx thread\n"); exit(FAIL); } } } static void stop_rx_thread(void) { void *retval; - int i; + unsigned int i; if (rx_thread) { printf("[info]: shutting down rx thread\n"); sleep(2); pthread_cancel(rx_thread); pthread_join(rx_thread, &retval); for (i = 0; i < PCKT_FRAG_MAX; i ++) { free(rx_buf[i]); } } } static void send_ping_data(void) { char buf[65535]; ssize_t len; memset(&buf, 0, sizeof(buf)); snprintf(buf, sizeof(buf), "Hello world!"); if (compresscfg) { len = sizeof(buf); } else { len = strlen(buf); } if (knet_send(knet_h, buf, len, channel) != len) { printf("[info]: Error sending hello world: %s\n", strerror(errno)); } sleep(1); } static int send_messages(struct knet_mmsghdr *msg, int msgs_to_send) { int sent_msgs, prev_sent, progress, total_sent; total_sent = 0; sent_msgs = 0; prev_sent = 0; progress = 1; retry: errno = 0; sent_msgs = _sendmmsg(datafd, 0, &msg[0], msgs_to_send, MSG_NOSIGNAL); if (sent_msgs < 0) { if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) { usleep(KNET_THREADS_TIMERES / 16); goto retry; } printf("[info]: Unable to send messages: %s\n", strerror(errno)); return -1; } total_sent = total_sent + sent_msgs; if ((sent_msgs >= 0) && (sent_msgs < msgs_to_send)) { if ((sent_msgs) || (progress)) { msgs_to_send = msgs_to_send - sent_msgs; prev_sent = prev_sent + sent_msgs; if (sent_msgs) { progress = 1; } else { progress = 0; } goto retry; } if (!progress) { printf("[info]: Unable to send more messages after retry\n"); } } return total_sent; } static int setup_send_buffers_common(struct knet_mmsghdr *msg, struct iovec *iov_out, char *tx_buf[]) { - int i; + unsigned int i; for (i = 0; i < PCKT_FRAG_MAX; i++) { tx_buf[i] = malloc(KNET_MAX_PACKET_SIZE); if (!tx_buf[i]) { printf("TXT: Unable to malloc!\n"); return -1; } memset(tx_buf[i], i, KNET_MAX_PACKET_SIZE); iov_out[i].iov_base = (void *)tx_buf[i]; memset(&msg[i].msg_hdr, 0, sizeof(struct msghdr)); msg[i].msg_hdr.msg_iov = &iov_out[i]; msg[i].msg_hdr.msg_iovlen = 1; } return 0; } static void send_perf_data_by_size(void) { char *tx_buf[PCKT_FRAG_MAX]; struct knet_mmsghdr msg[PCKT_FRAG_MAX]; struct iovec iov_out[PCKT_FRAG_MAX]; char ctrl_message[16]; int sent_msgs; - int i; + unsigned int i; uint64_t total_pkts_to_tx; uint64_t packets_to_send; uint32_t packetsize = 64; setup_send_buffers_common(msg, iov_out, tx_buf); while (packetsize <= KNET_MAX_PACKET_SIZE) { if (force_packet_size) { packetsize = force_packet_size; } for (i = 0; i < PCKT_FRAG_MAX; i++) { iov_out[i].iov_len = packetsize; if (use_pckt_verification) { struct pckt_ver *tx_pckt = (struct pckt_ver *)&iov_out[i].iov_base; tx_pckt->len = iov_out[i].iov_len; tx_pckt->chksum = compute_chsum((const unsigned char *)iov_out[i].iov_base + sizeof(struct pckt_ver), iov_out[i].iov_len - sizeof(struct pckt_ver)); } } total_pkts_to_tx = perf_by_size_size / packetsize; printf("[info]: testing with %u packet size. total bytes to transfer: %" PRIu64 " (%" PRIu64 " packets)\n", packetsize, perf_by_size_size, total_pkts_to_tx); memset(ctrl_message, 0, sizeof(ctrl_message)); knet_send(knet_h, ctrl_message, TEST_START, channel); while (total_pkts_to_tx > 0) { if (total_pkts_to_tx >= PCKT_FRAG_MAX) { packets_to_send = PCKT_FRAG_MAX; } else { packets_to_send = total_pkts_to_tx; } sent_msgs = send_messages(&msg[0], packets_to_send); if (sent_msgs < 0) { printf("Something went wrong, aborting\n"); exit(FAIL); } total_pkts_to_tx = total_pkts_to_tx - sent_msgs; } sleep(2); knet_send(knet_h, ctrl_message, TEST_STOP, channel); if ((packetsize == KNET_MAX_PACKET_SIZE) || (force_packet_size)) { break; } /* * Use a multiplier that can always divide properly a GB * into smaller chunks without worry about boundaries */ packetsize *= 4; if (packetsize > KNET_MAX_PACKET_SIZE) { packetsize = KNET_MAX_PACKET_SIZE; } } knet_send(knet_h, ctrl_message, TEST_COMPLETE, channel); for (i = 0; i < PCKT_FRAG_MAX; i++) { free(tx_buf[i]); } } /* For sorting the node list into order */ static int node_compare(const void *aptr, const void *bptr) { uint16_t a,b; a = *(uint16_t *)aptr; b = *(uint16_t *)bptr; return a > b; } static void display_stats(int level) { struct knet_handle_stats handle_stats; struct knet_link_status link_status; struct knet_link_stats total_link_stats; knet_node_id_t host_list[KNET_MAX_HOST]; uint8_t link_list[KNET_MAX_LINK]; unsigned int i,j; size_t num_hosts, num_links; if (knet_handle_get_stats(knet_h, &handle_stats, sizeof(handle_stats)) < 0) { perror("[info]: failed to get knet handle stats"); return; } if (compresscfg || cryptocfg) { printf("\n"); printf("[stat]: handle stats\n"); printf("[stat]: ------------\n"); if (compresscfg) { printf("[stat]: tx_uncompressed_packets: %" PRIu64 "\n", handle_stats.tx_uncompressed_packets); printf("[stat]: tx_compressed_packets: %" PRIu64 "\n", handle_stats.tx_compressed_packets); printf("[stat]: tx_compressed_original_bytes: %" PRIu64 "\n", handle_stats.tx_compressed_original_bytes); printf("[stat]: tx_compressed_size_bytes: %" PRIu64 "\n", handle_stats.tx_compressed_size_bytes ); printf("[stat]: tx_compress_time_ave: %" PRIu64 "\n", handle_stats.tx_compress_time_ave); printf("[stat]: tx_compress_time_min: %" PRIu64 "\n", handle_stats.tx_compress_time_min); printf("[stat]: tx_compress_time_max: %" PRIu64 "\n", handle_stats.tx_compress_time_max); printf("[stat]: rx_compressed_packets: %" PRIu64 "\n", handle_stats.rx_compressed_packets); printf("[stat]: rx_compressed_original_bytes: %" PRIu64 "\n", handle_stats.rx_compressed_original_bytes); printf("[stat]: rx_compressed_size_bytes: %" PRIu64 "\n", handle_stats.rx_compressed_size_bytes); printf("[stat]: rx_compress_time_ave: %" PRIu64 "\n", handle_stats.rx_compress_time_ave); printf("[stat]: rx_compress_time_min: %" PRIu64 "\n", handle_stats.rx_compress_time_min); printf("[stat]: rx_compress_time_max: %" PRIu64 "\n", handle_stats.rx_compress_time_max); printf("\n"); } if (cryptocfg) { printf("[stat]: tx_crypt_packets: %" PRIu64 "\n", handle_stats.tx_crypt_packets); printf("[stat]: tx_crypt_byte_overhead: %" PRIu64 "\n", handle_stats.tx_crypt_byte_overhead); printf("[stat]: tx_crypt_time_ave: %" PRIu64 "\n", handle_stats.tx_crypt_time_ave); printf("[stat]: tx_crypt_time_min: %" PRIu64 "\n", handle_stats.tx_crypt_time_min); printf("[stat]: tx_crypt_time_max: %" PRIu64 "\n", handle_stats.tx_crypt_time_max); printf("[stat]: rx_crypt_packets: %" PRIu64 "\n", handle_stats.rx_crypt_packets); printf("[stat]: rx_crypt_time_ave: %" PRIu64 "\n", handle_stats.rx_crypt_time_ave); printf("[stat]: rx_crypt_time_min: %" PRIu64 "\n", handle_stats.rx_crypt_time_min); printf("[stat]: rx_crypt_time_max: %" PRIu64 "\n", handle_stats.rx_crypt_time_max); printf("\n"); } } if (level < 2) { return; } memset(&total_link_stats, 0, sizeof(struct knet_link_stats)); if (knet_host_get_host_list(knet_h, host_list, &num_hosts) < 0) { perror("[info]: cannot get host list for stats"); return; } /* Print in host ID order */ qsort(host_list, num_hosts, sizeof(uint16_t), node_compare); for (j=0; j 2) { printf("\n"); printf("[stat]: Node %d Link %d\n", host_list[j], link_list[i]); printf("[stat]: tx_data_packets: %" PRIu64 "\n", link_status.stats.tx_data_packets); printf("[stat]: rx_data_packets: %" PRIu64 "\n", link_status.stats.rx_data_packets); printf("[stat]: tx_data_bytes: %" PRIu64 "\n", link_status.stats.tx_data_bytes); printf("[stat]: rx_data_bytes: %" PRIu64 "\n", link_status.stats.rx_data_bytes); printf("[stat]: rx_ping_packets: %" PRIu64 "\n", link_status.stats.rx_ping_packets); printf("[stat]: tx_ping_packets: %" PRIu64 "\n", link_status.stats.tx_ping_packets); printf("[stat]: rx_ping_bytes: %" PRIu64 "\n", link_status.stats.rx_ping_bytes); printf("[stat]: tx_ping_bytes: %" PRIu64 "\n", link_status.stats.tx_ping_bytes); printf("[stat]: rx_pong_packets: %" PRIu64 "\n", link_status.stats.rx_pong_packets); printf("[stat]: tx_pong_packets: %" PRIu64 "\n", link_status.stats.tx_pong_packets); printf("[stat]: rx_pong_bytes: %" PRIu64 "\n", link_status.stats.rx_pong_bytes); printf("[stat]: tx_pong_bytes: %" PRIu64 "\n", link_status.stats.tx_pong_bytes); printf("[stat]: rx_pmtu_packets: %" PRIu64 "\n", link_status.stats.rx_pmtu_packets); printf("[stat]: tx_pmtu_packets: %" PRIu64 "\n", link_status.stats.tx_pmtu_packets); printf("[stat]: rx_pmtu_bytes: %" PRIu64 "\n", link_status.stats.rx_pmtu_bytes); printf("[stat]: tx_pmtu_bytes: %" PRIu64 "\n", link_status.stats.tx_pmtu_bytes); printf("[stat]: tx_total_packets: %" PRIu64 "\n", link_status.stats.tx_total_packets); printf("[stat]: rx_total_packets: %" PRIu64 "\n", link_status.stats.rx_total_packets); printf("[stat]: tx_total_bytes: %" PRIu64 "\n", link_status.stats.tx_total_bytes); printf("[stat]: rx_total_bytes: %" PRIu64 "\n", link_status.stats.rx_total_bytes); printf("[stat]: tx_total_errors: %" PRIu64 "\n", link_status.stats.tx_total_errors); printf("[stat]: tx_total_retries: %" PRIu64 "\n", link_status.stats.tx_total_retries); printf("[stat]: tx_pmtu_errors: %" PRIu32 "\n", link_status.stats.tx_pmtu_errors); printf("[stat]: tx_pmtu_retries: %" PRIu32 "\n", link_status.stats.tx_pmtu_retries); printf("[stat]: tx_ping_errors: %" PRIu32 "\n", link_status.stats.tx_ping_errors); printf("[stat]: tx_ping_retries: %" PRIu32 "\n", link_status.stats.tx_ping_retries); printf("[stat]: tx_pong_errors: %" PRIu32 "\n", link_status.stats.tx_pong_errors); printf("[stat]: tx_pong_retries: %" PRIu32 "\n", link_status.stats.tx_pong_retries); printf("[stat]: tx_data_errors: %" PRIu32 "\n", link_status.stats.tx_data_errors); printf("[stat]: tx_data_retries: %" PRIu32 "\n", link_status.stats.tx_data_retries); printf("[stat]: latency_min: %" PRIu32 "\n", link_status.stats.latency_min); printf("[stat]: latency_max: %" PRIu32 "\n", link_status.stats.latency_max); printf("[stat]: latency_ave: %" PRIu32 "\n", link_status.stats.latency_ave); printf("[stat]: latency_samples: %" PRIu32 "\n", link_status.stats.latency_samples); printf("[stat]: down_count: %" PRIu32 "\n", link_status.stats.down_count); printf("[stat]: up_count: %" PRIu32 "\n", link_status.stats.up_count); } } } printf("\n"); printf("[stat]: Total link stats\n"); printf("[stat]: ----------------\n"); printf("[stat]: tx_data_packets: %" PRIu64 "\n", total_link_stats.tx_data_packets); printf("[stat]: rx_data_packets: %" PRIu64 "\n", total_link_stats.rx_data_packets); printf("[stat]: tx_data_bytes: %" PRIu64 "\n", total_link_stats.tx_data_bytes); printf("[stat]: rx_data_bytes: %" PRIu64 "\n", total_link_stats.rx_data_bytes); printf("[stat]: rx_ping_packets: %" PRIu64 "\n", total_link_stats.rx_ping_packets); printf("[stat]: tx_ping_packets: %" PRIu64 "\n", total_link_stats.tx_ping_packets); printf("[stat]: rx_ping_bytes: %" PRIu64 "\n", total_link_stats.rx_ping_bytes); printf("[stat]: tx_ping_bytes: %" PRIu64 "\n", total_link_stats.tx_ping_bytes); printf("[stat]: rx_pong_packets: %" PRIu64 "\n", total_link_stats.rx_pong_packets); printf("[stat]: tx_pong_packets: %" PRIu64 "\n", total_link_stats.tx_pong_packets); printf("[stat]: rx_pong_bytes: %" PRIu64 "\n", total_link_stats.rx_pong_bytes); printf("[stat]: tx_pong_bytes: %" PRIu64 "\n", total_link_stats.tx_pong_bytes); printf("[stat]: rx_pmtu_packets: %" PRIu64 "\n", total_link_stats.rx_pmtu_packets); printf("[stat]: tx_pmtu_packets: %" PRIu64 "\n", total_link_stats.tx_pmtu_packets); printf("[stat]: rx_pmtu_bytes: %" PRIu64 "\n", total_link_stats.rx_pmtu_bytes); printf("[stat]: tx_pmtu_bytes: %" PRIu64 "\n", total_link_stats.tx_pmtu_bytes); printf("[stat]: tx_total_packets: %" PRIu64 "\n", total_link_stats.tx_total_packets); printf("[stat]: rx_total_packets: %" PRIu64 "\n", total_link_stats.rx_total_packets); printf("[stat]: tx_total_bytes: %" PRIu64 "\n", total_link_stats.tx_total_bytes); printf("[stat]: rx_total_bytes: %" PRIu64 "\n", total_link_stats.rx_total_bytes); printf("[stat]: tx_total_errors: %" PRIu64 "\n", total_link_stats.tx_total_errors); printf("[stat]: tx_total_retries: %" PRIu64 "\n", total_link_stats.tx_total_retries); printf("[stat]: tx_pmtu_errors: %" PRIu32 "\n", total_link_stats.tx_pmtu_errors); printf("[stat]: tx_pmtu_retries: %" PRIu32 "\n", total_link_stats.tx_pmtu_retries); printf("[stat]: tx_ping_errors: %" PRIu32 "\n", total_link_stats.tx_ping_errors); printf("[stat]: tx_ping_retries: %" PRIu32 "\n", total_link_stats.tx_ping_retries); printf("[stat]: tx_pong_errors: %" PRIu32 "\n", total_link_stats.tx_pong_errors); printf("[stat]: tx_pong_retries: %" PRIu32 "\n", total_link_stats.tx_pong_retries); printf("[stat]: tx_data_errors: %" PRIu32 "\n", total_link_stats.tx_data_errors); printf("[stat]: tx_data_retries: %" PRIu32 "\n", total_link_stats.tx_data_retries); printf("[stat]: down_count: %" PRIu32 "\n", total_link_stats.down_count); printf("[stat]: up_count: %" PRIu32 "\n", total_link_stats.up_count); } static void send_perf_data_by_time(void) { char *tx_buf[PCKT_FRAG_MAX]; struct knet_mmsghdr msg[PCKT_FRAG_MAX]; struct iovec iov_out[PCKT_FRAG_MAX]; char ctrl_message[16]; int sent_msgs; - int i; + unsigned int i; uint32_t packetsize = 64; struct timespec clock_start, clock_end; unsigned long long time_diff = 0; setup_send_buffers_common(msg, iov_out, tx_buf); memset(&clock_start, 0, sizeof(clock_start)); memset(&clock_end, 0, sizeof(clock_start)); while (packetsize <= KNET_MAX_PACKET_SIZE) { if (force_packet_size) { packetsize = force_packet_size; } for (i = 0; i < PCKT_FRAG_MAX; i++) { iov_out[i].iov_len = packetsize; if (use_pckt_verification) { struct pckt_ver *tx_pckt = (struct pckt_ver *)iov_out[i].iov_base; tx_pckt->len = iov_out[i].iov_len; tx_pckt->chksum = compute_chsum((const unsigned char *)iov_out[i].iov_base + sizeof(struct pckt_ver), iov_out[i].iov_len - sizeof(struct pckt_ver)); } } printf("[info]: testing with %u bytes packet size for %" PRIu64 " seconds.\n", packetsize, perf_by_time_secs); memset(ctrl_message, 0, sizeof(ctrl_message)); knet_send(knet_h, ctrl_message, TEST_START, channel); if (clock_gettime(CLOCK_MONOTONIC, &clock_start) != 0) { printf("[info]: unable to get start time!\n"); } time_diff = 0; while (time_diff < (perf_by_time_secs * 1000000000llu)) { sent_msgs = send_messages(&msg[0], PCKT_FRAG_MAX); if (sent_msgs < 0) { printf("Something went wrong, aborting\n"); exit(FAIL); } if (clock_gettime(CLOCK_MONOTONIC, &clock_end) != 0) { printf("[info]: unable to get end time!\n"); } timespec_diff(clock_start, clock_end, &time_diff); } sleep(2); knet_send(knet_h, ctrl_message, TEST_STOP, channel); if ((packetsize == KNET_MAX_PACKET_SIZE) || (force_packet_size)) { break; } /* * Use a multiplier that can always divide properly a GB * into smaller chunks without worry about boundaries */ packetsize *= 4; if (packetsize > KNET_MAX_PACKET_SIZE) { packetsize = KNET_MAX_PACKET_SIZE; } } knet_send(knet_h, ctrl_message, TEST_COMPLETE, channel); for (i = 0; i < PCKT_FRAG_MAX; i++) { free(tx_buf[i]); } } static void cleanup_all(void) { knet_handle_t knet_h_tmp[2]; if (pthread_mutex_lock(&shutdown_mutex)) { return; } if (bench_shutdown_in_progress) { pthread_mutex_unlock(&shutdown_mutex); return; } bench_shutdown_in_progress = 1; pthread_mutex_unlock(&shutdown_mutex); if (rx_thread) { stop_rx_thread(); } knet_h_tmp[1] = knet_h; knet_handle_stop_everything(knet_h_tmp, 1); } static void sigint_handler(int signum) { printf("[info]: cleaning up... got signal: %d\n", signum); cleanup_all(); exit(PASS); } int main(int argc, char *argv[]) { if (signal(SIGINT, sigint_handler) == SIG_ERR) { printf("Unable to configure SIGINT handler\n"); exit(FAIL); } setup_knet(argc, argv); setup_data_txrx_common(); sleep(5); restart: switch(test_type) { default: case TEST_PING: /* basic ping, no data */ sleep(5); break; case TEST_PING_AND_DATA: send_ping_data(); break; case TEST_PERF_BY_SIZE: if (senderid == thisnodeid) { send_perf_data_by_size(); } else { printf("[info]: waiting for perf rx thread to finish\n"); while(!wait_for_perf_rx) { sleep(1); } } break; case TEST_PERF_BY_TIME: if (senderid == thisnodeid) { send_perf_data_by_time(); } else { printf("[info]: waiting for perf rx thread to finish\n"); while(!wait_for_perf_rx) { sleep(1); } } break; } if (continous) { goto restart; } if (show_stats) { display_stats(show_stats); } cleanup_all(); return PASS; } diff --git a/libknet/tests/test-common.h b/libknet/tests/test-common.h index 3587394f..ffa323db 100644 --- a/libknet/tests/test-common.h +++ b/libknet/tests/test-common.h @@ -1,150 +1,152 @@ /* * Copyright (C) 2016-2025 Red Hat, Inc. All rights reserved. * * Authors: Fabio M. Di Nitto * * This software licensed under GPL-2.0+ */ #ifndef __KNET_TEST_COMMON_H__ #define __KNET_TEST_COMMON_H__ #include "internals.h" #include /* * error codes from automake test-driver */ #define PASS 0 #define SKIP 77 #define ERROR 99 #define FAIL -1 /* Extra for us to continue while still using the cleanup code */ #define CONTINUE 101 /* For *BSD compatibility */ -#ifndef s6_addr16 -#define s6_addr8 __u6_addr.__u6_addr8 -#define s6_addr16 __u6_addr.__u6_addr16 -#define s6_addr32 __u6_addr.__u6_addr32 +#ifndef s6_addr32 +# ifdef KNET_SOLARIS +# define s6_addr32 _S6_un._S6_u32 +# else +# define s6_addr32 __u6_addr.__u6_addr32 +# endif #endif /* * common facilities */ #define TESTNODES 1 #define FAIL_ON_ERR(fn) \ printf("FOE: %s\n", #fn); \ if ((res = fn) != 0) { \ int savederrno = errno; \ knet_handle_stop_everything(knet_h, TESTNODES); \ stop_logthread(); \ flush_logs(logfds[0], stdout); \ close_logpipes(logfds); \ if (res == -2) { \ exit(SKIP); \ } else { \ printf("*** FAIL on line %d. %s failed: %s\n", __LINE__ , #fn, strerror(savederrno)); \ exit(FAIL); \ } \ } else { \ flush_logs(logfds[0], stdout); \ } /* As above but allow a SKIP to continue */ #define FAIL_ON_ERR_ONLY(fn) \ printf("FOEO: %s\n", #fn); \ if ((res = fn) == -1) { \ int savederrno = errno; \ knet_handle_stop_everything(knet_h, TESTNODES); \ stop_logthread(); \ flush_logs(logfds[0], stdout); \ close_logpipes(logfds); \ printf("*** FAIL on line %d. %s failed: %s\n", __LINE__ , #fn, strerror(savederrno)); \ exit(FAIL); \ } else { \ flush_logs(logfds[0], stdout); \ } /* Voted "Best macro name of 2022" */ #define FAIL_ON_SUCCESS(fn, errcode) \ printf("FOS: %s\n", #fn); \ if (((res = fn) == 0) || \ ((res == -1) && (errno != errcode))) { \ int savederrno = errno; \ knet_handle_stop_everything(knet_h, TESTNODES); \ stop_logthread(); \ flush_logs(logfds[0], stdout); \ close_logpipes(logfds); \ if (res == -2) { \ exit(SKIP); \ } else { \ printf("*** FAIL on line %d. %s did not return correct error: %s\n", __LINE__ , #fn, strerror(savederrno)); \ exit(FAIL); \ } \ } else { \ flush_logs(logfds[0], stdout); \ } #define CLEAN_EXIT(r) \ clean_exit(knet_h, TESTNODES, logfds, r) void clean_exit(knet_handle_t *knet_h, int testnodes, int *logfds, int exit_status); int execute_shell(const char *command, char **error_string); int is_memcheck(void); int is_helgrind(void); void set_scheduler(int policy); knet_handle_t knet_handle_start(int logfds[2], uint8_t log_level, knet_handle_t knet_h_array[]); /* * knet_link_set_config wrapper required to find a free port */ int _knet_link_set_config(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id, uint8_t transport, uint64_t flags, int family, int dynamic, struct sockaddr_storage *lo); /* * functional test helpers */ void knet_handle_stop_everything(knet_handle_t knet_h[], uint8_t numnodes); void knet_handle_start_nodes(knet_handle_t knet_h[], uint8_t numnodes, int logfds[2], uint8_t log_level); void knet_handle_join_nodes(knet_handle_t knet_h[], uint8_t numnodes, uint8_t numlinks, int family, uint8_t transport); /* * high level logging function. * automatically setup logpipes and start/stop logging thread. * * start_logging exit(FAIL) on error or fd to pass to knet_handle_new * and it will install an atexit handle to close logging properly * * WARNING: DO NOT use start_logging for api_ or int_ testing. * while start_logging would work just fine, the output * of the logs is more complex to read because of the way * the thread would interleave the output of printf from api_/int_ testing * with knet logs. Functionally speaking you get the exact same logs, * but a lot harder to read due to the thread latency in printing logs. */ int start_logging(FILE *std); int setup_logpipes(int *logfds); void close_logpipes(int *logfds); void flush_logs(int logfd, FILE *std); int start_logthread(int logfd, FILE *std); int stop_logthread(void); int make_local_sockaddr(struct sockaddr_storage *lo, int offset); int make_local_sockaddr6(struct sockaddr_storage *lo, int offset); int wait_for_host(knet_handle_t knet_h, uint16_t host_id, int seconds, int logfd, FILE *std); int wait_for_packet(knet_handle_t knet_h, int seconds, int datafd, int logfd, FILE *std); void test_sleep(knet_handle_t knet_h, int seconds); int wait_for_nodes_state(knet_handle_t knet_h, size_t numnodes, uint8_t state, uint32_t timeout, int logfd, FILE *std); #endif diff --git a/libknet/threads_tx.c b/libknet/threads_tx.c index 9bf5dee6..20869d1b 100644 --- a/libknet/threads_tx.c +++ b/libknet/threads_tx.c @@ -1,888 +1,888 @@ /* * Copyright (C) 2012-2025 Red Hat, Inc. All rights reserved. * * Authors: Fabio M. Di Nitto * Federico Simoncelli * * This software licensed under LGPL-2.0+ */ #include "config.h" #include #include #include #include #include #include #include "compat.h" #include "compress.h" #include "crypto.h" #include "host.h" #include "link.h" #include "logging.h" #include "transports.h" #include "transport_common.h" #include "threads_common.h" #include "threads_heartbeat.h" #include "threads_tx.h" #include "netutils.h" /* * SEND */ static int _dispatch_to_links(knet_handle_t knet_h, struct knet_host *dst_host, struct knet_mmsghdr *msg, int msgs_to_send) { int link_idx, msg_idx, sent_msgs, prev_sent, progress; int err = 0, savederrno = 0, locked = 0; unsigned int i; struct knet_mmsghdr *cur; struct knet_link *cur_link; for (link_idx = 0; link_idx < dst_host->active_link_entries; link_idx++) { prev_sent = 0; progress = 1; locked = 0; cur_link = &dst_host->link[dst_host->active_links[link_idx]]; if (cur_link->transport == KNET_TRANSPORT_LOOPBACK) { continue; } savederrno = pthread_mutex_lock(&cur_link->link_stats_mutex); if (savederrno) { log_err(knet_h, KNET_SUB_TX, "Unable to get stats mutex lock for host %u link %u: %s", dst_host->host_id, cur_link->link_id, strerror(savederrno)); continue; } locked = 1; msg_idx = 0; while (msg_idx < msgs_to_send) { msg[msg_idx].msg_hdr.msg_name = &cur_link->dst_addr; msg[msg_idx].msg_hdr.msg_namelen = knet_h->knet_transport_fd_tracker[cur_link->outsock].sockaddr_len; /* Cast for Linux/BSD compatibility */ for (i=0; i<(unsigned int)msg[msg_idx].msg_hdr.msg_iovlen; i++) { cur_link->status.stats.tx_data_bytes += msg[msg_idx].msg_hdr.msg_iov[i].iov_len; } cur_link->status.stats.tx_data_packets++; msg_idx++; } retry: cur = &msg[prev_sent]; sent_msgs = _sendmmsg(dst_host->link[dst_host->active_links[link_idx]].outsock, transport_get_connection_oriented(knet_h, dst_host->link[dst_host->active_links[link_idx]].transport), &cur[0], msgs_to_send - prev_sent, MSG_DONTWAIT | MSG_NOSIGNAL); savederrno = errno; err = transport_tx_sock_error(knet_h, dst_host->link[dst_host->active_links[link_idx]].transport, dst_host->link[dst_host->active_links[link_idx]].outsock, KNET_SUB_TX, sent_msgs, savederrno); switch(err) { case -1: /* unrecoverable error */ cur_link->status.stats.tx_data_errors++; goto out_unlock; break; case 0: /* ignore error and continue */ break; case 1: /* retry to send those same data */ cur_link->status.stats.tx_data_retries++; goto retry; break; } prev_sent = prev_sent + sent_msgs; if ((sent_msgs >= 0) && (prev_sent < msgs_to_send)) { if ((sent_msgs) || (progress)) { if (sent_msgs) { progress = 1; } else { progress = 0; } log_trace(knet_h, KNET_SUB_TX, "Unable to send all (%d/%d) data packets to host %s (%u) link %s:%s (%u)", sent_msgs, msg_idx, dst_host->name, dst_host->host_id, dst_host->link[dst_host->active_links[link_idx]].status.dst_ipaddr, dst_host->link[dst_host->active_links[link_idx]].status.dst_port, dst_host->link[dst_host->active_links[link_idx]].link_id); goto retry; } if (!progress) { savederrno = EAGAIN; err = -1; goto out_unlock; } } if ((dst_host->link_handler_policy == KNET_LINK_POLICY_RR) && (dst_host->active_link_entries > 1)) { uint8_t cur_link_id = dst_host->active_links[0]; memmove(&dst_host->active_links[0], &dst_host->active_links[1], KNET_MAX_LINK - 1); dst_host->active_links[dst_host->active_link_entries - 1] = cur_link_id; break; } pthread_mutex_unlock(&cur_link->link_stats_mutex); locked = 0; } out_unlock: if (locked) { pthread_mutex_unlock(&cur_link->link_stats_mutex); } errno = savederrno; return err; } static int _parse_recv_from_sock(knet_handle_t knet_h, size_t inlen, int8_t channel, int is_sync) { size_t outlen, frag_len; struct knet_host *dst_host; knet_node_id_t dst_host_ids_temp[KNET_MAX_HOST]; size_t dst_host_ids_entries_temp = 0; knet_node_id_t dst_host_ids[KNET_MAX_HOST]; size_t dst_host_ids_entries = 0; int bcast = 1; struct iovec iov_out[PCKT_FRAG_MAX][2]; int iovcnt_out = 2; uint8_t frag_idx; unsigned int temp_data_mtu; size_t host_idx; int send_mcast = 0; struct knet_header *inbuf; int savederrno = 0; int err = 0; seq_num_t tx_seq_num; struct knet_mmsghdr msg[PCKT_FRAG_MAX]; int msgs_to_send, msg_idx; unsigned int i; int j; int send_local = 0; int data_compressed = 0; size_t uncrypted_frag_size; int stats_locked = 0, stats_err = 0; inbuf = knet_h->recv_from_sock_buf; if (knet_h->enabled != 1) { log_debug(knet_h, KNET_SUB_TX, "Received data packet but forwarding is disabled"); savederrno = ECANCELED; err = -1; goto out_unlock; } /* * move this into a separate function to expand on * extra switching rules */ switch(inbuf->kh_type) { case KNET_HEADER_TYPE_DATA: if (knet_h->dst_host_filter_fn) { bcast = knet_h->dst_host_filter_fn( knet_h->dst_host_filter_fn_private_data, (const unsigned char *)inbuf->khp_data_userdata, inlen, KNET_NOTIFY_TX, knet_h->host_id, knet_h->host_id, &channel, dst_host_ids_temp, &dst_host_ids_entries_temp); if (bcast < 0) { log_debug(knet_h, KNET_SUB_TX, "Error from dst_host_filter_fn: %d", bcast); savederrno = EFAULT; err = -1; goto out_unlock; } if ((!bcast) && (!dst_host_ids_entries_temp)) { log_debug(knet_h, KNET_SUB_TX, "Message is unicast but no dst_host_ids_entries"); savederrno = EINVAL; err = -1; goto out_unlock; } if ((!bcast) && (dst_host_ids_entries_temp > KNET_MAX_HOST)) { log_debug(knet_h, KNET_SUB_TX, "dst_host_filter_fn returned too many destinations"); savederrno = EINVAL; err = -1; goto out_unlock; } } /* Send to localhost if appropriate and enabled */ if (knet_h->has_loop_link) { send_local = 0; if (bcast) { send_local = 1; } else { for (i=0; i< dst_host_ids_entries_temp; i++) { if (dst_host_ids_temp[i] == knet_h->host_id) { send_local = 1; } } } if (send_local) { const unsigned char *buf = inbuf->khp_data_userdata; ssize_t buflen = inlen; struct knet_link *local_link; local_link = knet_h->host_index[knet_h->host_id]->link; local_retry: err = write(knet_h->sockfd[channel].sockfd[knet_h->sockfd[channel].is_created], buf, buflen); if (err < 0) { log_err(knet_h, KNET_SUB_TRANSP_LOOPBACK, "send local failed. error=%s\n", strerror(errno)); local_link->status.stats.tx_data_errors++; } if (err > 0 && err < buflen) { log_debug(knet_h, KNET_SUB_TRANSP_LOOPBACK, "send local incomplete=%d bytes of %zu\n", err, inlen); local_link->status.stats.tx_data_retries++; buf += err; buflen -= err; goto local_retry; } if (err == buflen) { local_link->status.stats.tx_data_packets++; local_link->status.stats.tx_data_bytes += inlen; } } } break; default: log_warn(knet_h, KNET_SUB_TX, "Receiving unknown messages from socket"); savederrno = ENOMSG; err = -1; goto out_unlock; break; } if (is_sync) { if ((bcast) || ((!bcast) && (dst_host_ids_entries_temp > 1))) { log_debug(knet_h, KNET_SUB_TX, "knet_send_sync is only supported with unicast packets for one destination"); savederrno = E2BIG; err = -1; goto out_unlock; } } /* * check destinations hosts before spending time * in fragmenting/encrypting packets to save * time processing data for unreachable hosts. * for unicast, also remap the destination data * to skip unreachable hosts. */ if (!bcast) { dst_host_ids_entries = 0; for (host_idx = 0; host_idx < dst_host_ids_entries_temp; host_idx++) { dst_host = knet_h->host_index[dst_host_ids_temp[host_idx]]; if (!dst_host) { continue; } if (!(dst_host->host_id == knet_h->host_id && knet_h->has_loop_link) && dst_host->status.reachable) { dst_host_ids[dst_host_ids_entries] = dst_host_ids_temp[host_idx]; dst_host_ids_entries++; } } if (!dst_host_ids_entries) { savederrno = EHOSTDOWN; err = -1; goto out_unlock; } } else { send_mcast = 0; for (dst_host = knet_h->host_head; dst_host != NULL; dst_host = dst_host->next) { if (!(dst_host->host_id == knet_h->host_id && knet_h->has_loop_link) && dst_host->status.reachable) { send_mcast = 1; break; } } if (!send_mcast) { savederrno = EHOSTDOWN; err = -1; goto out_unlock; } } if (!knet_h->data_mtu) { /* * using MIN_MTU_V4 for data mtu is not completely accurate but safe enough */ log_debug(knet_h, KNET_SUB_TX, "Received data packet but data MTU is still unknown." " Packet might not be delivered." " Assuming minimum IPv4 MTU (%d)", KNET_PMTUD_MIN_MTU_V4); temp_data_mtu = KNET_PMTUD_MIN_MTU_V4; } else { /* * take a copy of the mtu to avoid value changing under * our feet while we are sending a fragmented pckt */ temp_data_mtu = knet_h->data_mtu; } /* * compress data */ if ((knet_h->compress_model > 0) && (inlen > knet_h->compress_threshold)) { size_t cmp_outlen = KNET_DATABUFSIZE_COMPRESS; struct timespec start_time; struct timespec end_time; uint64_t compress_time; clock_gettime(CLOCK_MONOTONIC, &start_time); err = compress(knet_h, (const unsigned char *)inbuf->khp_data_userdata, inlen, knet_h->send_to_links_buf_compress, (ssize_t *)&cmp_outlen); savederrno = errno; stats_err = pthread_mutex_lock(&knet_h->handle_stats_mutex); if (stats_err < 0) { log_err(knet_h, KNET_SUB_TX, "Unable to get mutex lock: %s", strerror(stats_err)); err = -1; savederrno = stats_err; goto out_unlock; } stats_locked = 1; /* Collect stats */ clock_gettime(CLOCK_MONOTONIC, &end_time); timespec_diff(start_time, end_time, &compress_time); if (compress_time < knet_h->stats.tx_compress_time_min) { knet_h->stats.tx_compress_time_min = compress_time; } if (compress_time > knet_h->stats.tx_compress_time_max) { knet_h->stats.tx_compress_time_max = compress_time; } knet_h->stats.tx_compress_time_ave = (unsigned long long)(knet_h->stats.tx_compress_time_ave * knet_h->stats.tx_compressed_packets + compress_time) / (knet_h->stats.tx_compressed_packets+1); if (err < 0) { log_warn(knet_h, KNET_SUB_COMPRESS, "Compression failed (%d): %s", err, strerror(savederrno)); } else { knet_h->stats.tx_compressed_packets++; knet_h->stats.tx_compressed_original_bytes += inlen; knet_h->stats.tx_compressed_size_bytes += cmp_outlen; if (cmp_outlen < inlen) { memmove(inbuf->khp_data_userdata, knet_h->send_to_links_buf_compress, cmp_outlen); inlen = cmp_outlen; data_compressed = 1; } } } if (!stats_locked) { stats_err = pthread_mutex_lock(&knet_h->handle_stats_mutex); if (stats_err < 0) { log_err(knet_h, KNET_SUB_TX, "Unable to get mutex lock: %s", strerror(stats_err)); err = -1; savederrno = stats_err; goto out_unlock; } } if (knet_h->compress_model > 0 && !data_compressed) { knet_h->stats.tx_uncompressed_packets++; } pthread_mutex_unlock(&knet_h->handle_stats_mutex); stats_locked = 0; /* * prepare the outgoing buffers */ frag_len = inlen; frag_idx = 0; inbuf->khp_data_bcast = bcast; inbuf->khp_data_frag_num = ceil((float)inlen / temp_data_mtu); inbuf->khp_data_channel = channel; if (data_compressed) { inbuf->khp_data_compress = knet_h->compress_model; } else { inbuf->khp_data_compress = 0; } if (pthread_mutex_lock(&knet_h->tx_seq_num_mutex)) { log_debug(knet_h, KNET_SUB_TX, "Unable to get seq mutex lock"); goto out_unlock; } knet_h->tx_seq_num++; /* * force seq_num 0 to detect a node that has crashed and rejoining * the knet instance. seq_num 0 will clear the buffers in the RX * thread */ if (knet_h->tx_seq_num == 0) { knet_h->tx_seq_num++; } /* * cache the value in locked context */ tx_seq_num = knet_h->tx_seq_num; inbuf->khp_data_seq_num = htons(knet_h->tx_seq_num); pthread_mutex_unlock(&knet_h->tx_seq_num_mutex); /* * forcefully broadcast a ping to all nodes every SEQ_MAX / 8 * pckts. * this solves 2 problems: * 1) on TX socket overloads we generate extra pings to keep links alive * 2) in 3+ nodes setup, where all the traffic is flowing between node 1 and 2, * node 3+ will be able to keep in sync on the TX seq_num even without * receiving traffic or pings in betweens. This avoids issues with * rollover of the circular buffer */ if (tx_seq_num % (SEQ_MAX / 8) == 0) { _send_pings(knet_h, 0); } if (inbuf->khp_data_frag_num > 1) { while (frag_idx < inbuf->khp_data_frag_num) { /* * set the iov_base */ iov_out[frag_idx][0].iov_base = (void *)knet_h->send_to_links_buf[frag_idx]; iov_out[frag_idx][0].iov_len = KNET_HEADER_DATA_SIZE; iov_out[frag_idx][1].iov_base = inbuf->khp_data_userdata + (temp_data_mtu * frag_idx); /* * set the len */ if (frag_len > temp_data_mtu) { iov_out[frag_idx][1].iov_len = temp_data_mtu; } else { iov_out[frag_idx][1].iov_len = frag_len; } /* * copy the frag info on all buffers */ knet_h->send_to_links_buf[frag_idx]->kh_type = inbuf->kh_type; knet_h->send_to_links_buf[frag_idx]->khp_data_seq_num = inbuf->khp_data_seq_num; knet_h->send_to_links_buf[frag_idx]->khp_data_frag_num = inbuf->khp_data_frag_num; knet_h->send_to_links_buf[frag_idx]->khp_data_bcast = inbuf->khp_data_bcast; knet_h->send_to_links_buf[frag_idx]->khp_data_channel = inbuf->khp_data_channel; knet_h->send_to_links_buf[frag_idx]->khp_data_compress = inbuf->khp_data_compress; frag_len = frag_len - temp_data_mtu; frag_idx++; } iovcnt_out = 2; } else { iov_out[frag_idx][0].iov_base = (void *)inbuf; iov_out[frag_idx][0].iov_len = frag_len + KNET_HEADER_DATA_SIZE; iovcnt_out = 1; } if (knet_h->crypto_in_use_config) { struct timespec start_time; struct timespec end_time; uint64_t crypt_time; frag_idx = 0; while (frag_idx < inbuf->khp_data_frag_num) { clock_gettime(CLOCK_MONOTONIC, &start_time); if (crypto_encrypt_and_signv( knet_h, iov_out[frag_idx], iovcnt_out, knet_h->send_to_links_buf_crypt[frag_idx], (ssize_t *)&outlen) < 0) { log_debug(knet_h, KNET_SUB_TX, "Unable to encrypt packet"); savederrno = ECHILD; err = -1; goto out_unlock; } clock_gettime(CLOCK_MONOTONIC, &end_time); timespec_diff(start_time, end_time, &crypt_time); stats_err = pthread_mutex_lock(&knet_h->handle_stats_mutex); if (stats_err < 0) { log_err(knet_h, KNET_SUB_TX, "Unable to get mutex lock: %s", strerror(stats_err)); err = -1; savederrno = stats_err; goto out_unlock; } if (crypt_time < knet_h->stats.tx_crypt_time_min) { knet_h->stats.tx_crypt_time_min = crypt_time; } if (crypt_time > knet_h->stats.tx_crypt_time_max) { knet_h->stats.tx_crypt_time_max = crypt_time; } knet_h->stats.tx_crypt_time_ave = (knet_h->stats.tx_crypt_time_ave * knet_h->stats.tx_crypt_packets + crypt_time) / (knet_h->stats.tx_crypt_packets+1); uncrypted_frag_size = 0; for (j=0; j < iovcnt_out; j++) { uncrypted_frag_size += iov_out[frag_idx][j].iov_len; } knet_h->stats.tx_crypt_byte_overhead += (outlen - uncrypted_frag_size); knet_h->stats.tx_crypt_packets++; pthread_mutex_unlock(&knet_h->handle_stats_mutex); iov_out[frag_idx][0].iov_base = knet_h->send_to_links_buf_crypt[frag_idx]; iov_out[frag_idx][0].iov_len = outlen; frag_idx++; } iovcnt_out = 1; } memset(&msg, 0, sizeof(msg)); msgs_to_send = inbuf->khp_data_frag_num; msg_idx = 0; while (msg_idx < msgs_to_send) { msg[msg_idx].msg_hdr.msg_namelen = sizeof(struct sockaddr_storage); /* this will set properly in _dispatch_to_links() */ msg[msg_idx].msg_hdr.msg_iov = &iov_out[msg_idx][0]; msg[msg_idx].msg_hdr.msg_iovlen = iovcnt_out; msg_idx++; } if (!bcast) { for (host_idx = 0; host_idx < dst_host_ids_entries; host_idx++) { dst_host = knet_h->host_index[dst_host_ids[host_idx]]; err = _dispatch_to_links(knet_h, dst_host, &msg[0], msgs_to_send); savederrno = errno; if (err) { goto out_unlock; } } } else { for (dst_host = knet_h->host_head; dst_host != NULL; dst_host = dst_host->next) { if (dst_host->status.reachable) { err = _dispatch_to_links(knet_h, dst_host, &msg[0], msgs_to_send); savederrno = errno; if (err) { goto out_unlock; } } } } out_unlock: errno = savederrno; return err; } static void _handle_send_to_links(knet_handle_t knet_h, struct msghdr *msg, int sockfd, int8_t channel, int type) { ssize_t inlen = 0; int savederrno = 0, docallback = 0; /* * make sure BSD gets the right size */ msg->msg_namelen = knet_h->knet_transport_fd_tracker[sockfd].sockaddr_len; if ((channel >= 0) && (channel < KNET_DATAFD_MAX) && (!knet_h->sockfd[channel].is_socket)) { inlen = readv(sockfd, msg->msg_iov, 1); } else { inlen = recvmsg(sockfd, msg, MSG_DONTWAIT | MSG_NOSIGNAL); if (msg->msg_flags & MSG_TRUNC) { log_warn(knet_h, KNET_SUB_TX, "Received truncated message from sock %d. Discarding", sockfd); return; } } if (inlen == 0) { savederrno = 0; docallback = 1; } else if (inlen < 0) { struct epoll_event ev; savederrno = errno; docallback = 1; memset(&ev, 0, sizeof(struct epoll_event)); if (epoll_ctl(knet_h->send_to_links_epollfd, EPOLL_CTL_DEL, knet_h->sockfd[channel].sockfd[knet_h->sockfd[channel].is_created], &ev)) { log_err(knet_h, KNET_SUB_TX, "Unable to del datafd %d from linkfd epoll pool: %s", knet_h->sockfd[channel].sockfd[0], strerror(savederrno)); } else { knet_h->sockfd[channel].has_error = 1; } } else { knet_h->recv_from_sock_buf->kh_type = type; _parse_recv_from_sock(knet_h, inlen, channel, 0); } if (docallback) { knet_h->sock_notify_fn(knet_h->sock_notify_fn_private_data, knet_h->sockfd[channel].sockfd[0], channel, KNET_NOTIFY_TX, inlen, savederrno); } } void *_handle_send_to_links_thread(void *data) { knet_handle_t knet_h = (knet_handle_t) data; struct epoll_event events[KNET_EPOLL_MAX_EVENTS + 1]; /* see _init_epolls for + 1 */ int i, nev, type; int flush, flush_queue_limit; int8_t channel; struct iovec iov_in; struct msghdr msg; struct sockaddr_storage address; set_thread_status(knet_h, KNET_THREAD_TX, KNET_THREAD_STARTED); memset(&events, 0, sizeof(events)); memset(&iov_in, 0, sizeof(iov_in)); iov_in.iov_base = (void *)knet_h->recv_from_sock_buf->khp_data_userdata; iov_in.iov_len = KNET_MAX_PACKET_SIZE; memset(&msg, 0, sizeof(struct msghdr)); msg.msg_name = &address; msg.msg_namelen = sizeof(struct sockaddr_storage); msg.msg_iov = &iov_in; msg.msg_iovlen = 1; knet_h->recv_from_sock_buf->kh_version = KNET_HEADER_VERSION; knet_h->recv_from_sock_buf->khp_data_frag_seq = 0; knet_h->recv_from_sock_buf->kh_node = htons(knet_h->host_id); - for (i = 0; i < PCKT_FRAG_MAX; i++) { + for (i = 0; i < (int)PCKT_FRAG_MAX; i++) { knet_h->send_to_links_buf[i]->kh_version = KNET_HEADER_VERSION; knet_h->send_to_links_buf[i]->khp_data_frag_seq = i + 1; knet_h->send_to_links_buf[i]->kh_node = htons(knet_h->host_id); } flush_queue_limit = 0; while (!shutdown_in_progress(knet_h)) { nev = epoll_wait(knet_h->send_to_links_epollfd, events, KNET_EPOLL_MAX_EVENTS + 1, KNET_THREADS_TIMERES / 1000); flush = get_thread_flush_queue(knet_h, KNET_THREAD_TX); /* * we use timeout to detect if thread is shutting down */ if (nev == 0) { /* * ideally we want to communicate that we are done flushing * the queue when we have an epoll timeout event */ if (flush == KNET_THREAD_QUEUE_FLUSH) { set_thread_flush_queue(knet_h, KNET_THREAD_TX, KNET_THREAD_QUEUE_FLUSHED); flush_queue_limit = 0; } continue; } /* * fall back in case the TX sockets will continue receive traffic * and we do not hit an epoll timeout. * * allow up to a 100 loops to flush queues, then we give up. * there might be more clean ways to do it by checking the buffer queue * on each socket, but we have tons of sockets and calculations can go wrong. * Also, why would you disable data forwarding and still send packets? */ if (flush == KNET_THREAD_QUEUE_FLUSH) { if (flush_queue_limit >= 100) { log_debug(knet_h, KNET_SUB_TX, "Timeout flushing the TX queue, expect packet loss"); set_thread_flush_queue(knet_h, KNET_THREAD_TX, KNET_THREAD_QUEUE_FLUSHED); flush_queue_limit = 0; } else { flush_queue_limit++; } } else { flush_queue_limit = 0; } if (pthread_rwlock_rdlock(&knet_h->global_rwlock) != 0) { log_debug(knet_h, KNET_SUB_TX, "Unable to get read lock"); continue; } for (i = 0; i < nev; i++) { type = KNET_HEADER_TYPE_DATA; for (channel = 0; channel < KNET_DATAFD_MAX; channel++) { if ((knet_h->sockfd[channel].in_use) && (knet_h->sockfd[channel].sockfd[knet_h->sockfd[channel].is_created] == events[i].data.fd)) { break; } } if (channel >= KNET_DATAFD_MAX) { log_debug(knet_h, KNET_SUB_TX, "No available channels"); continue; /* channel not found */ } if (pthread_mutex_lock(&knet_h->tx_mutex) != 0) { log_debug(knet_h, KNET_SUB_TX, "Unable to get mutex lock"); continue; } _handle_send_to_links(knet_h, &msg, events[i].data.fd, channel, type); pthread_mutex_unlock(&knet_h->tx_mutex); } pthread_rwlock_unlock(&knet_h->global_rwlock); } set_thread_status(knet_h, KNET_THREAD_TX, KNET_THREAD_STOPPED); return NULL; } int knet_send_sync(knet_handle_t knet_h, const char *buff, const size_t buff_len, const int8_t channel) { int savederrno = 0, err = 0; if (!_is_valid_handle(knet_h)) { return -1; } if (buff == NULL) { errno = EINVAL; return -1; } if (buff_len <= 0) { errno = EINVAL; return -1; } if (buff_len > KNET_MAX_PACKET_SIZE) { errno = EINVAL; return -1; } if (channel < 0) { errno = EINVAL; return -1; } if (channel >= KNET_DATAFD_MAX) { errno = EINVAL; return -1; } savederrno = pthread_rwlock_rdlock(&knet_h->global_rwlock); if (savederrno) { log_err(knet_h, KNET_SUB_TX, "Unable to get read lock: %s", strerror(savederrno)); errno = savederrno; return -1; } if (!knet_h->dst_host_filter_fn) { savederrno = ENETDOWN; err = -1; goto out; } if (!knet_h->sockfd[channel].in_use) { savederrno = EINVAL; err = -1; goto out; } savederrno = pthread_mutex_lock(&knet_h->tx_mutex); if (savederrno) { log_err(knet_h, KNET_SUB_TX, "Unable to get TX mutex lock: %s", strerror(savederrno)); err = -1; goto out; } knet_h->recv_from_sock_buf->kh_type = KNET_HEADER_TYPE_DATA; memmove(knet_h->recv_from_sock_buf->khp_data_userdata, buff, buff_len); err = _parse_recv_from_sock(knet_h, buff_len, channel, 1); savederrno = errno; pthread_mutex_unlock(&knet_h->tx_mutex); out: pthread_rwlock_unlock(&knet_h->global_rwlock); errno = err ? savederrno : 0; return err; } ssize_t knet_send(knet_handle_t knet_h, const char *buff, const size_t buff_len, const int8_t channel) { int savederrno = 0; ssize_t err = 0; struct iovec iov_out[1]; if (!_is_valid_handle(knet_h)) { return -1; } if (buff == NULL) { errno = EINVAL; return -1; } if (buff_len <= 0) { errno = EINVAL; return -1; } if (buff_len > KNET_MAX_PACKET_SIZE) { errno = EINVAL; return -1; } if (channel < 0) { errno = EINVAL; return -1; } if (channel >= KNET_DATAFD_MAX) { errno = EINVAL; return -1; } savederrno = pthread_rwlock_rdlock(&knet_h->global_rwlock); if (savederrno) { log_err(knet_h, KNET_SUB_HANDLE, "Unable to get read lock: %s", strerror(savederrno)); errno = savederrno; return -1; } if (!knet_h->sockfd[channel].in_use) { savederrno = EINVAL; err = -1; goto out_unlock; } memset(iov_out, 0, sizeof(iov_out)); iov_out[0].iov_base = (void *)buff; iov_out[0].iov_len = buff_len; err = writev(knet_h->sockfd[channel].sockfd[0], iov_out, 1); savederrno = errno; out_unlock: pthread_rwlock_unlock(&knet_h->global_rwlock); errno = err ? savederrno : 0; return err; } diff --git a/libknet/transport_common.c b/libknet/transport_common.c index afe938ca..7e379be4 100644 --- a/libknet/transport_common.c +++ b/libknet/transport_common.c @@ -1,465 +1,465 @@ /* * Copyright (C) 2016-2025 Red Hat, Inc. All rights reserved. * * Author: Fabio M. Di Nitto * * This software licensed under LGPL-2.0+ */ #include "config.h" #include #include #include #include #include #include #include #include #include "libknet.h" #include "compat.h" #include "host.h" #include "link.h" #include "logging.h" #include "common.h" #include "transport_common.h" /* * reuse Jan Friesse's compat layer as wrapper to drop usage of sendmmsg * * TODO: kill those wrappers once we work on packet delivery guarantees */ int _recvmmsg(int sockfd, struct knet_mmsghdr *msgvec, unsigned int vlen, unsigned int flags) { int savederrno = 0, err = 0; unsigned int i; for (i = 0; i < vlen; i++) { err = recvmsg(sockfd, &msgvec[i].msg_hdr, flags); savederrno = errno; if (err >= 0) { msgvec[i].msg_len = err; if (err == 0) { /* No point in reading anything more until we know this has been dealt with or we'll just get a vector full of them. Several in fact */ i++; break; } } else { if ((i > 0) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) { savederrno = 0; } break; } } errno = savederrno; return ((i > 0) ? (int)i : err); } int _sendmmsg(int sockfd, int connection_oriented, struct knet_mmsghdr *msgvec, unsigned int vlen, unsigned int flags) { int savederrno = 0, err = 0; unsigned int i; struct msghdr temp_msg; struct msghdr *use_msghdr; for (i = 0; i < vlen; i++) { if (connection_oriented == TRANSPORT_PROTO_IS_CONNECTION_ORIENTED) { memcpy(&temp_msg, &msgvec[i].msg_hdr, sizeof(struct msghdr)); temp_msg.msg_name = NULL; temp_msg.msg_namelen = 0; use_msghdr = &temp_msg; } else { use_msghdr = &msgvec[i].msg_hdr; } err = sendmsg(sockfd, use_msghdr, flags); savederrno = errno; if (err < 0) { break; } } errno = savederrno; return ((i > 0) ? (int)i : err); } /* Assume neither of these constants can ever be zero */ #ifndef SO_RCVBUFFORCE #define SO_RCVBUFFORCE 0 #endif #ifndef SO_SNDBUFFORCE #define SO_SNDBUFFORCE 0 #endif static int _configure_sockbuf(knet_handle_t knet_h, int sock, int option, int force, int target) { int savederrno = 0; int new_value; socklen_t value_len = sizeof new_value; if (setsockopt(sock, SOL_SOCKET, option, &target, sizeof target) != 0) { savederrno = errno; log_err(knet_h, KNET_SUB_TRANSPORT, "Error setting socket buffer via option %d to value %d: %s\n", option, target, strerror(savederrno)); errno = savederrno; return -1; } if (getsockopt(sock, SOL_SOCKET, option, &new_value, &value_len) != 0) { savederrno = errno; log_err(knet_h, KNET_SUB_TRANSPORT, "Error getting socket buffer via option %d: %s\n", option, strerror(savederrno)); errno = savederrno; return -1; } if (value_len != sizeof new_value) { log_err(knet_h, KNET_SUB_TRANSPORT, "Socket option %d returned unexpected size %u\n", option, value_len); errno = ERANGE; return -1; } if (target <= new_value) { return 0; } if (!force || !(knet_h->flags & KNET_HANDLE_FLAG_PRIVILEGED)) { log_err(knet_h, KNET_SUB_TRANSPORT, "Failed to set socket buffer via option %d to value %d: capped at %d", option, target, new_value); if (!(knet_h->flags & KNET_HANDLE_FLAG_PRIVILEGED)) { log_err(knet_h, KNET_SUB_TRANSPORT, "Continuing regardless, as the handle is not privileged." " Expect poor performance!"); return 0; } else { errno = ENAMETOOLONG; return -1; } } if (setsockopt(sock, SOL_SOCKET, force, &target, sizeof target) < 0) { savederrno = errno; log_err(knet_h, KNET_SUB_TRANSPORT, "Failed to set socket buffer via force option %d: %s", force, strerror(savederrno)); if (savederrno == EPERM) { errno = ENAMETOOLONG; } else { errno = savederrno; } return -1; } return 0; } int _configure_common_socket(knet_handle_t knet_h, int sock, uint64_t flags, const char *type) { int err = 0, savederrno = 0; if (_fdset_cloexec(sock)) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_TRANSPORT, "Unable to set %s CLOEXEC socket opts: %s", type, strerror(savederrno)); goto exit_error; } if (_fdset_nonblock(sock)) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_TRANSPORT, "Unable to set %s NONBLOCK socket opts: %s", type, strerror(savederrno)); goto exit_error; } if (_configure_sockbuf(knet_h, sock, SO_RCVBUF, SO_RCVBUFFORCE, KNET_RING_RCVBUFF)) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_TRANSPORT, "Unable to set %s receive buffer: %s", type, strerror(savederrno)); goto exit_error; } if (_configure_sockbuf(knet_h, sock, SO_SNDBUF, SO_SNDBUFFORCE, KNET_RING_RCVBUFF)) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_TRANSPORT, "Unable to set %s send buffer: %s", type, strerror(savederrno)); goto exit_error; } if (flags & KNET_LINK_FLAG_TRAFFICHIPRIO) { #ifdef KNET_LINUX #ifdef SO_PRIORITY int value = 6; /* TC_PRIO_INTERACTIVE */ if (setsockopt(sock, SOL_SOCKET, SO_PRIORITY, &value, sizeof(value)) < 0) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_TRANSPORT, "Unable to set %s priority: %s", type, strerror(savederrno)); goto exit_error; } log_debug(knet_h, KNET_SUB_TRANSPORT, "TC_PRIO_INTERACTIVE enabled on socket: %i", sock); #else log_debug(knet_h, KNET_SUB_TRANSPORT, "TC_PRIO_INTERACTIVE not available in this build/platform"); #endif #endif #if defined(IP_TOS) if (knet_h->prio_dscp) { /* dscp is the 6 highest bits of TOS IP header field, RFC 2474 */ int value = (knet_h->prio_dscp & 0x3f) << 2; if (setsockopt(sock, IPPROTO_IP, IP_TOS, &value, sizeof(value)) < 0) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_TRANSPORT, "Unable to set %s priority: %s", type, strerror(savederrno)); goto exit_error; } log_debug(knet_h, KNET_SUB_TRANSPORT, "dscp %d set on socket: %i", knet_h->prio_dscp, sock); } else { #if defined(IPTOS_LOWDELAY) int value = IPTOS_LOWDELAY; if (setsockopt(sock, IPPROTO_IP, IP_TOS, &value, sizeof(value)) < 0) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_TRANSPORT, "Unable to set %s priority: %s", type, strerror(savederrno)); goto exit_error; } log_debug(knet_h, KNET_SUB_TRANSPORT, "IPTOS_LOWDELAY enabled on socket: %i", sock); #else log_debug(knet_h, KNET_SUB_TRANSPORT, "IPTOS_LOWDELAY not available in this build/platform"); #endif } #endif } exit_error: errno = savederrno; return err; } int _configure_transport_socket(knet_handle_t knet_h, int sock, struct sockaddr_storage *address, uint64_t flags, const char *type) { int err = 0, savederrno = 0; int value; if (_configure_common_socket(knet_h, sock, flags, type) < 0) { savederrno = errno; err = -1; goto exit_error; } #ifdef KNET_LINUX #ifdef IP_FREEBIND value = 1; if (setsockopt(sock, SOL_IP, IP_FREEBIND, &value, sizeof(value)) <0) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_TRANSPORT, "Unable to set FREEBIND on %s socket: %s", type, strerror(savederrno)); goto exit_error; } log_debug(knet_h, KNET_SUB_TRANSPORT, "FREEBIND enabled on socket: %i", sock); #else log_debug(knet_h, KNET_SUB_TRANSPORT, "FREEBIND not available in this build/platform"); #endif #endif #ifdef KNET_BSD #ifdef IP_BINDANY /* BSD */ value = 1; if (setsockopt(sock, IPPROTO_IP, IP_BINDANY, &value, sizeof(value)) <0) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_TRANSPORT, "Unable to set BINDANY on %s socket: %s", type, strerror(savederrno)); goto exit_error; } log_debug(knet_h, KNET_SUB_TRANSPORT, "BINDANY enabled on socket: %i", sock); #else log_debug(knet_h, KNET_SUB_TRANSPORT, "BINDANY not available in this build/platform"); #endif #endif if (address->ss_family == AF_INET6) { value = 1; if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &value, sizeof(value)) < 0) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_TRANSPORT, "Unable to set %s IPv6 only: %s", type, strerror(savederrno)); goto exit_error; } #ifdef KNET_LINUX #ifdef IPV6_MTU_DISCOVER value = IPV6_PMTUDISC_PROBE; if (setsockopt(sock, SOL_IPV6, IPV6_MTU_DISCOVER, &value, sizeof(value)) <0) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_TRANSPORT, "Unable to set PMTUDISC on %s socket: %s", type, strerror(savederrno)); goto exit_error; } log_debug(knet_h, KNET_SUB_TRANSPORT, "IPV6_MTU_DISCOVER enabled on socket: %i", sock); #else log_debug(knet_h, KNET_SUB_TRANSPORT, "IPV6_MTU_DISCOVER not available in this build/platform"); #endif #endif #ifdef IPV6_DONTFRAG value = 1; if (setsockopt(sock, IPPROTO_IPV6, IPV6_DONTFRAG, &value, sizeof(value)) <0) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_TRANSPORT, "Unable to set DONTFRAG on %s socket: %s", type, strerror(savederrno)); goto exit_error; } log_debug(knet_h, KNET_SUB_TRANSPORT, "IPV6_DONTFRAG enabled on socket: %i", sock); #else log_debug(knet_h, KNET_SUB_TRANSPORT, "IPV6_DONTFRAG not available in this build/platform"); #endif } else { #ifdef KNET_LINUX #ifdef IP_MTU_DISCOVER value = IP_PMTUDISC_PROBE; if (setsockopt(sock, SOL_IP, IP_MTU_DISCOVER, &value, sizeof(value)) <0) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_TRANSPORT, "Unable to set PMTUDISC on %s socket: %s", type, strerror(savederrno)); goto exit_error; } log_debug(knet_h, KNET_SUB_TRANSPORT, "PMTUDISC enabled on socket: %i", sock); #else log_debug(knet_h, KNET_SUB_TRANSPORT, "PMTUDISC not available in this build/platform"); #endif #endif -#ifdef KNET_BSD +#if defined(KNET_BSD) || defined(KNET_SOLARIS) #ifdef IP_DONTFRAG value = 1; if (setsockopt(sock, IPPROTO_IP, IP_DONTFRAG, &value, sizeof(value)) <0) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_TRANSPORT, "Unable to set DONTFRAG on %s socket: %s", type, strerror(savederrno)); goto exit_error; } log_debug(knet_h, KNET_SUB_TRANSPORT, "DONTFRAG enabled on socket: %i", sock); #else log_debug(knet_h, KNET_SUB_TRANSPORT, "DONTFRAG not available in this build/platform"); #endif #endif } exit_error: errno = savederrno; return err; } int _init_socketpair(knet_handle_t knet_h, int *sock) { int err = 0, savederrno = 0; int i; if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sock) != 0) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_HANDLE, "Unable to initialize socketpair: %s", strerror(savederrno)); goto exit_fail; } for (i = 0; i < 2; i++) { if (_configure_common_socket(knet_h, sock[i], 0, "local socketpair") < 0) { savederrno = errno; err = -1; goto exit_fail; } } exit_fail: errno = savederrno; return err; } void _close_socketpair(knet_handle_t knet_h, int *sock) { int i; for (i = 0; i < 2; i++) { if (sock[i]) { close(sock[i]); sock[i] = 0; } } } /* * must be called with global read lock * * return -1 on error * return 0 if fd is invalid * return 1 if fd is valid */ int _is_valid_fd(knet_handle_t knet_h, int sockfd) { int ret = 0; if (sockfd < 0) { errno = EINVAL; return -1; } if (sockfd >= KNET_MAX_FDS) { errno = EINVAL; return -1; } if (knet_h->knet_transport_fd_tracker[sockfd].transport >= KNET_MAX_TRANSPORTS) { ret = 0; } else { ret = 1; } return ret; } /* * must be called with global write lock */ int _set_fd_tracker(knet_handle_t knet_h, int sockfd, uint8_t transport, uint8_t data_type, socklen_t socklen, void *data, int ifindex) { if (sockfd < 0) { errno = EINVAL; return -1; } if (sockfd >= KNET_MAX_FDS) { errno = EINVAL; return -1; } knet_h->knet_transport_fd_tracker[sockfd].transport = transport; knet_h->knet_transport_fd_tracker[sockfd].data_type = data_type; knet_h->knet_transport_fd_tracker[sockfd].sockaddr_len = socklen; knet_h->knet_transport_fd_tracker[sockfd].data = data; knet_h->knet_transport_fd_tracker[sockfd].ifindex = ifindex; return 0; } diff --git a/libknet/transport_loopback.c b/libknet/transport_loopback.c index 8b53ffce..d36b3eb5 100644 --- a/libknet/transport_loopback.c +++ b/libknet/transport_loopback.c @@ -1,80 +1,81 @@ /* * Copyright (C) 2017-2025 Red Hat, Inc. All rights reserved. * * Author: Christine Caulfield * * This software licensed under LGPL-2.0+ */ #include "config.h" #include #include #include #include #include #include #include #include +#include #include #include "libknet.h" #include "compat.h" #include "host.h" #include "link.h" #include "logging.h" #include "common.h" #include "transports.h" #include "transport_loopback.h" #include "threads_common.h" /* This is just a file of empty calls as the actual loopback is in threads_tx.c as a special case when receiving a packet from the localhost */ int loopback_transport_link_set_config(knet_handle_t knet_h, struct knet_link *kn_link) { kn_link->transport_connected = 1; kn_link->status.connected = 1; return 0; } int loopback_transport_link_clear_config(knet_handle_t knet_h, struct knet_link *kn_link) { return 0; } int loopback_transport_free(knet_handle_t knet_h) { return 0; } int loopback_transport_init(knet_handle_t knet_h) { return 0; } int loopback_transport_rx_sock_error(knet_handle_t knet_h, int sockfd, int recv_err, int recv_errno) { return 0; } int loopback_transport_tx_sock_error(knet_handle_t knet_h, int sockfd, int subsys, int recv_err, int recv_errno) { return 0; } int loopback_transport_rx_is_data(knet_handle_t knet_h, int sockfd, struct knet_mmsghdr *msg) { return 0; } int loopback_transport_link_dyn_connect(knet_handle_t knet_h, int sockfd, struct knet_link *kn_link) { return 0; } int loopback_transport_link_is_down(knet_handle_t knet_h, struct knet_link *kn_link) { return 0; } diff --git a/libknet/transport_udp.c b/libknet/transport_udp.c index f7b9b2bd..83a00293 100644 --- a/libknet/transport_udp.c +++ b/libknet/transport_udp.c @@ -1,571 +1,572 @@ /* * Copyright (C) 2016-2025 Red Hat, Inc. All rights reserved. * * Author: Christine Caulfield * * This software licensed under LGPL-2.0+ */ #include "config.h" #include #include #include #include #include #include #include #include +#include #include #include #if defined (IP_RECVERR) || defined (IPV6_RECVERR) #include #endif #include "libknet.h" #include "compat.h" #include "host.h" #include "link.h" #include "logging.h" #include "common.h" #include "netutils.h" #include "transport_common.h" #include "transport_udp.h" #include "transports.h" #include "threads_common.h" typedef struct udp_handle_info { struct qb_list_head links_list; } udp_handle_info_t; typedef struct udp_link_info { struct qb_list_head list; struct sockaddr_storage local_address; int socket_fd; int on_epoll; } udp_link_info_t; int udp_transport_link_set_config(knet_handle_t knet_h, struct knet_link *kn_link) { int err = 0, savederrno = 0; int sock = -1; struct epoll_event ev; udp_link_info_t *info; udp_handle_info_t *handle_info = knet_h->transports[KNET_TRANSPORT_UDP]; /* * Only allocate a new link if the local address is different */ qb_list_for_each_entry(info, &handle_info->links_list, list) { if (memcmp(&info->local_address, &kn_link->src_addr, sizeof(struct sockaddr_storage)) == 0) { log_debug(knet_h, KNET_SUB_TRANSP_UDP, "Re-using existing UDP socket for new link"); kn_link->outsock = info->socket_fd; kn_link->transport_link = info; kn_link->transport_connected = 1; return 0; } } info = malloc(sizeof(udp_link_info_t)); if (!info) { err = -1; goto exit_error; } memset(info, 0, sizeof(udp_link_info_t)); sock = socket(kn_link->src_addr.ss_family, SOCK_DGRAM, 0); if (sock < 0) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_TRANSP_UDP, "Unable to create listener socket: %s", strerror(savederrno)); goto exit_error; } if (_configure_transport_socket(knet_h, sock, &kn_link->src_addr, kn_link->flags, "UDP") < 0) { savederrno = errno; err = -1; goto exit_error; } #ifdef IP_RECVERR if (kn_link->src_addr.ss_family == AF_INET) { int value = 1; if (setsockopt(sock, SOL_IP, IP_RECVERR, &value, sizeof(value)) <0) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_TRANSP_UDP, "Unable to set RECVERR on socket: %s", strerror(savederrno)); goto exit_error; } log_debug(knet_h, KNET_SUB_TRANSP_UDP, "IP_RECVERR enabled on socket: %i", sock); } #else log_debug(knet_h, KNET_SUB_TRANSP_UDP, "IP_RECVERR not available in this build/platform"); #endif #ifdef IP_PKTINFO if (kn_link->src_addr.ss_family == AF_INET) { int value = 1; if (setsockopt(sock, SOL_IP, IP_PKTINFO, &value, sizeof(value)) <0) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_TRANSP_UDP, "Unable to set PKTINFO on socket: %s", strerror(savederrno)); goto exit_error; } log_debug(knet_h, KNET_SUB_TRANSP_UDP, "IP_PKTINFO enabled on socket: %i", sock); } #endif #ifdef IPV6_RECVPKTINFO if (kn_link->src_addr.ss_family == AF_INET6) { int value = 1; if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &value, sizeof(value)) <0) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_TRANSP_UDP, "Unable to set RECVPKTINFO on socket: %s", strerror(savederrno)); goto exit_error; } log_debug(knet_h, KNET_SUB_TRANSP_UDP, "IPV6_RECVPKTINFO enabled on socket: %i", sock); } #endif #ifdef IPV6_RECVERR if (kn_link->src_addr.ss_family == AF_INET6) { int value = 1; if (setsockopt(sock, SOL_IPV6, IPV6_RECVERR, &value, sizeof(value)) <0) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_TRANSP_UDP, "Unable to set RECVERR on socket: %s", strerror(savederrno)); goto exit_error; } log_debug(knet_h, KNET_SUB_TRANSP_UDP, "IPV6_RECVERR enabled on socket: %i", sock); } #else log_debug(knet_h, KNET_SUB_TRANSP_UDP, "IPV6_RECVERR not available in this build/platform"); #endif if (bind(sock, (struct sockaddr *)&kn_link->src_addr, sockaddr_len(&kn_link->src_addr))) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_TRANSP_UDP, "Unable to bind listener socket: %s", strerror(savederrno)); goto exit_error; } memset(&ev, 0, sizeof(struct epoll_event)); ev.events = EPOLLIN; ev.data.fd = sock; if (epoll_ctl(knet_h->recv_from_links_epollfd, EPOLL_CTL_ADD, sock, &ev)) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_TRANSP_UDP, "Unable to add listener to epoll pool: %s", strerror(savederrno)); goto exit_error; } info->on_epoll = 1; if (_set_fd_tracker(knet_h, sock, KNET_TRANSPORT_UDP, 0, sockaddr_len(&kn_link->src_addr), info, -1) < 0) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_TRANSP_UDP, "Unable to set fd tracker: %s", strerror(savederrno)); goto exit_error; } memmove(&info->local_address, &kn_link->src_addr, sizeof(struct sockaddr_storage)); info->socket_fd = sock; qb_list_add(&info->list, &handle_info->links_list); kn_link->outsock = sock; kn_link->transport_link = info; kn_link->transport_connected = 1; exit_error: if (err) { if (info) { if (info->on_epoll) { epoll_ctl(knet_h->recv_from_links_epollfd, EPOLL_CTL_DEL, sock, &ev); } free(info); } if (sock >= 0) { close(sock); } } errno = savederrno; return err; } int udp_transport_link_clear_config(knet_handle_t knet_h, struct knet_link *kn_link) { int err = 0, savederrno = 0; int found = 0; struct knet_host *host; int link_idx; udp_link_info_t *info = kn_link->transport_link; struct epoll_event ev; for (host = knet_h->host_head; host != NULL; host = host->next) { for (link_idx = 0; link_idx < KNET_MAX_LINK; link_idx++) { if (&host->link[link_idx] == kn_link) continue; if (host->link[link_idx].transport_link == info) { found = 1; break; } } } if (found) { log_debug(knet_h, KNET_SUB_TRANSP_UDP, "UDP socket %d still in use", info->socket_fd); savederrno = EBUSY; err = -1; goto exit_error; } if (info->on_epoll) { memset(&ev, 0, sizeof(struct epoll_event)); ev.events = EPOLLIN; ev.data.fd = info->socket_fd; if (epoll_ctl(knet_h->recv_from_links_epollfd, EPOLL_CTL_DEL, info->socket_fd, &ev) < 0) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_TRANSP_UDP, "Unable to remove UDP socket from epoll poll: %s", strerror(errno)); goto exit_error; } info->on_epoll = 0; } if (_set_fd_tracker(knet_h, info->socket_fd, KNET_MAX_TRANSPORTS, 0, sockaddr_len(&kn_link->src_addr), NULL, -1) < 0) { savederrno = errno; err = -1; log_err(knet_h, KNET_SUB_TRANSP_UDP, "Unable to set fd tracker: %s", strerror(savederrno)); goto exit_error; } close(info->socket_fd); qb_list_del(&info->list); free(kn_link->transport_link); exit_error: errno = savederrno; return err; } int udp_transport_free(knet_handle_t knet_h) { udp_handle_info_t *handle_info; if (!knet_h->transports[KNET_TRANSPORT_UDP]) { errno = EINVAL; return -1; } handle_info = knet_h->transports[KNET_TRANSPORT_UDP]; /* * keep it here while we debug list usage and such */ if (!qb_list_empty(&handle_info->links_list)) { log_err(knet_h, KNET_SUB_TRANSP_UDP, "Internal error. handle list is not empty"); return -1; } free(handle_info); knet_h->transports[KNET_TRANSPORT_UDP] = NULL; return 0; } int udp_transport_init(knet_handle_t knet_h) { udp_handle_info_t *handle_info; if (knet_h->transports[KNET_TRANSPORT_UDP]) { errno = EEXIST; return -1; } handle_info = malloc(sizeof(udp_handle_info_t)); if (!handle_info) { return -1; } memset(handle_info, 0, sizeof(udp_handle_info_t)); knet_h->transports[KNET_TRANSPORT_UDP] = handle_info; qb_list_init(&handle_info->links_list); return 0; } #if defined (IP_RECVERR) || defined (IPV6_RECVERR) static int read_errs_from_sock(knet_handle_t knet_h, int sockfd) { int err = 0, savederrno = 0; int got_err = 0; char buffer[1024]; struct iovec iov; struct msghdr msg; struct cmsghdr *cmsg; struct sock_extended_err *sock_err; struct icmphdr icmph; struct sockaddr_storage remote; struct sockaddr_storage *origin; char addr_str[KNET_MAX_HOST_LEN]; char port_str[KNET_MAX_PORT_LEN]; char addr_remote_str[KNET_MAX_HOST_LEN]; char port_remote_str[KNET_MAX_PORT_LEN]; iov.iov_base = &icmph; iov.iov_len = sizeof(icmph); msg.msg_name = (void*)&remote; msg.msg_namelen = sizeof(remote); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_flags = 0; msg.msg_control = buffer; msg.msg_controllen = sizeof(buffer); for (;;) { err = recvmsg(sockfd, &msg, MSG_ERRQUEUE); savederrno = errno; if (err < 0) { if (!got_err) { errno = savederrno; return -1; } else { return 0; } } got_err = 1; for (cmsg = CMSG_FIRSTHDR(&msg);cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (((cmsg->cmsg_level == SOL_IP) && (cmsg->cmsg_type == IP_RECVERR)) || ((cmsg->cmsg_level == SOL_IPV6 && (cmsg->cmsg_type == IPV6_RECVERR)))) { sock_err = (struct sock_extended_err*)(void *)CMSG_DATA(cmsg); if (sock_err) { switch (sock_err->ee_origin) { case SO_EE_ORIGIN_NONE: /* no origin */ case SO_EE_ORIGIN_LOCAL: /* local source (EMSGSIZE) */ if (sock_err->ee_errno == EMSGSIZE || sock_err->ee_errno == EPERM) { if (pthread_mutex_lock(&knet_h->kmtu_mutex) != 0) { log_debug(knet_h, KNET_SUB_TRANSP_UDP, "Unable to get mutex lock"); knet_h->kernel_mtu = 0; break; } else { knet_h->kernel_mtu = sock_err->ee_info; log_debug(knet_h, KNET_SUB_TRANSP_UDP, "detected kernel MTU: %u", knet_h->kernel_mtu); pthread_mutex_unlock(&knet_h->kmtu_mutex); } force_pmtud_run(knet_h, KNET_SUB_TRANSP_UDP, 0, 0); } /* * those errors are way too noisy */ break; case SO_EE_ORIGIN_ICMP: /* ICMP */ case SO_EE_ORIGIN_ICMP6: /* ICMP6 */ origin = (struct sockaddr_storage *)(void *)SO_EE_OFFENDER(sock_err); if (knet_addrtostr(origin, sizeof(*origin), addr_str, KNET_MAX_HOST_LEN, port_str, KNET_MAX_PORT_LEN) < 0) { log_debug(knet_h, KNET_SUB_TRANSP_UDP, "Received ICMP error from unknown source: %s", strerror(sock_err->ee_errno)); } else { if (knet_addrtostr(&remote, sizeof(remote), addr_remote_str, KNET_MAX_HOST_LEN, port_remote_str, KNET_MAX_PORT_LEN) < 0) { log_debug(knet_h, KNET_SUB_TRANSP_UDP, "Received ICMP error from %s: %s destination unknown", addr_str, strerror(sock_err->ee_errno)); } else { log_debug(knet_h, KNET_SUB_TRANSP_UDP, "Received ICMP error from %s: %s %s", addr_str, strerror(sock_err->ee_errno), addr_remote_str); if ((sock_err->ee_errno == ECONNREFUSED) || /* knet is not running on the other node */ (sock_err->ee_errno == ECONNABORTED) || /* local kernel closed the socket */ (sock_err->ee_errno == ENONET) || /* network does not exist */ (sock_err->ee_errno == ENETUNREACH) || /* network unreachable */ (sock_err->ee_errno == EHOSTUNREACH) || /* host unreachable */ (sock_err->ee_errno == EHOSTDOWN) || /* host down (from kernel/net/ipv4/icmp.c */ (sock_err->ee_errno == ENETDOWN)) { /* network down */ struct knet_host *host = NULL; struct knet_link *kn_link = NULL; int link_idx, found = 0; for (host = knet_h->host_head; host != NULL; host = host->next) { for (link_idx = 0; link_idx < KNET_MAX_LINK; link_idx++) { kn_link = &host->link[link_idx]; if (kn_link->outsock == sockfd) { if (!cmpaddr(&remote, &kn_link->dst_addr)) { found = 1; break; } } } if (found) { break; } } if ((host) && (kn_link) && (kn_link->status.connected)) { log_debug(knet_h, KNET_SUB_TRANSP_UDP, "Setting down host %u link %i", host->host_id, kn_link->link_id); /* * setting transport_connected = 0 will trigger * thread_heartbeat link_down process. * * the process terminates calling into transport_link_down * below that will set transport_connected = 1 */ kn_link->transport_connected = 0; } } } } break; } } else { log_debug(knet_h, KNET_SUB_TRANSP_UDP, "No data in MSG_ERRQUEUE"); } } } } } #else static int read_errs_from_sock(knet_handle_t knet_h, int sockfd) { return 0; } #endif int udp_transport_rx_sock_error(knet_handle_t knet_h, int sockfd, int recv_err, int recv_errno) { if (recv_errno == EAGAIN) { read_errs_from_sock(knet_h, sockfd); } return 0; } int udp_transport_tx_sock_error(knet_handle_t knet_h, int sockfd, int subsys, int recv_err, int recv_errno) { if (recv_err < 0) { log_trace(knet_h, KNET_SUB_TRANSP_UDP, "tx_sock_error, subsys=%s, recv_err=%d: %s", knet_log_get_subsystem_name(subsys), recv_err, strerror(recv_errno)); if ((recv_errno == EMSGSIZE) || ((recv_errno == EPERM) && ((subsys == KNET_SUB_TX) || (subsys == KNET_SUB_PMTUD)))) { read_errs_from_sock(knet_h, sockfd); return 0; } if ((recv_errno == EINVAL) || (recv_errno == EPERM) || (recv_errno == ENETUNREACH) || (recv_errno == ENETDOWN) || (recv_errno == EHOSTUNREACH)) { if ((recv_errno == ENETUNREACH) || (recv_errno == ENETDOWN)) { log_trace(knet_h, KNET_SUB_TRANSP_UDP, "Sock: %d is unreachable.", sockfd); } return -1; } if ((recv_errno == ENOBUFS) || (recv_errno == EAGAIN)) { log_trace(knet_h, KNET_SUB_TRANSP_UDP, "Sock: %d is overloaded. Slowing TX down", sockfd); usleep(KNET_THREADS_TIMERES / 16); } else { read_errs_from_sock(knet_h, sockfd); } return 1; } return 0; } /* * If the received IP addr doesn't match the destination IP * then weird routing is going on. */ static void check_dst_addr_is_valid(knet_handle_t knet_h, int sockfd, struct msghdr *msg) { #if defined(IP_PKTINFO) || defined(IPV6_PKTINFO) struct cmsghdr *cmsg; for (cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL; cmsg = CMSG_NXTHDR(msg, cmsg)) { int pkt_ifindex = -1; int ifindex = knet_h->knet_transport_fd_tracker[sockfd].ifindex; struct sockaddr_storage dstaddr; #ifdef IP_PKTINFO if (cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_PKTINFO) { struct in_pktinfo *pi = (void*)CMSG_DATA(cmsg); struct sockaddr_in *dstaddr4 = (struct sockaddr_in *)&dstaddr; pkt_ifindex = pi->ipi_ifindex; dstaddr4->sin_family = AF_INET; dstaddr4->sin_port = 0; /* unknown to PKTINFO */ dstaddr4->sin_addr.s_addr = pi->ipi_addr.s_addr; } #endif #ifdef IPV6_PKTINFO if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) { struct in6_pktinfo *pi = (void*)CMSG_DATA(cmsg); struct sockaddr_in6 *dstaddr6 = (struct sockaddr_in6 *)&dstaddr; memset(dstaddr6, 0, sizeof(struct sockaddr_in6)); pkt_ifindex = pi->ipi6_ifindex; dstaddr6->sin6_family = AF_INET6; dstaddr6->sin6_port = 0; /* unknown to PKTINFO */ memcpy(&dstaddr6->sin6_addr, (char *)&pi->ipi6_addr, sizeof(pi->ipi6_addr)); } #endif if (ifindex != -1 && pkt_ifindex != -1 && ifindex != pkt_ifindex) { char srcaddr_s[KNET_MAX_HOST_LEN]; char srcport_s[KNET_MAX_PORT_LEN]; char dstaddr_s[KNET_MAX_HOST_LEN]; char dstport_s[KNET_MAX_PORT_LEN]; char expected_ifname[IF_NAMESIZE]; char used_ifname[IF_NAMESIZE]; /* Make as detailed a message as we can */ if ((if_indextoname(pkt_ifindex, used_ifname) == NULL) || (if_indextoname(ifindex, expected_ifname) == NULL)) { log_trace(knet_h, KNET_SUB_TRANSP_UDP, "Received packet on ifindex %d when expected ifindex %d", pkt_ifindex, ifindex); } else if (knet_addrtostr(msg->msg_name, msg->msg_namelen, srcaddr_s, sizeof(srcaddr_s), srcport_s, sizeof(srcport_s)) != 0) { log_trace(knet_h, KNET_SUB_TRANSP_UDP, "Received packet on i/f %s when expected i/f %s", used_ifname, expected_ifname); } else if (knet_addrtostr((struct sockaddr_storage *)&dstaddr, sizeof(dstaddr), dstaddr_s, sizeof(dstaddr_s), dstport_s, sizeof(dstport_s)) != 0) { log_trace(knet_h, KNET_SUB_TRANSP_UDP, "Received packet from %s on i/f %s when expected %s", srcaddr_s, used_ifname, expected_ifname); } else { log_trace(knet_h, KNET_SUB_TRANSP_UDP, "Received packet from %s to %s on i/f %s when expected %s", srcaddr_s, dstaddr_s, used_ifname, expected_ifname); } } } #endif } int udp_transport_rx_is_data(knet_handle_t knet_h, int sockfd, struct knet_mmsghdr *msg) { if (msg->msg_len == 0) return KNET_TRANSPORT_RX_NOT_DATA_CONTINUE; check_dst_addr_is_valid(knet_h, sockfd, &msg->msg_hdr); return KNET_TRANSPORT_RX_IS_DATA; } int udp_transport_link_dyn_connect(knet_handle_t knet_h, int sockfd, struct knet_link *kn_link) { kn_link->status.dynconnected = 1; return 0; } int udp_transport_link_is_down(knet_handle_t knet_h, struct knet_link *kn_link) { /* * see comments about handling ICMP error messages */ kn_link->transport_connected = 1; return 0; } diff --git a/libnozzle/Makefile.am b/libnozzle/Makefile.am index 86ebcbfa..c910dfb3 100644 --- a/libnozzle/Makefile.am +++ b/libnozzle/Makefile.am @@ -1,51 +1,56 @@ # # Copyright (C) 2010-2025 Red Hat, Inc. All rights reserved. # # Author: Fabio M. Di Nitto # # This software licensed under GPL-2.0+ # MAINTAINERCLEANFILES = Makefile.in include $(top_srcdir)/build-aux/check.mk SYMFILE = libnozzle_exported_syms EXTRA_DIST = $(SYMFILE) if BUILD_LIBNOZZLE SUBDIRS = . tests -libversion = 1:0:0 +libnozzleversion = 1:0:0 sources = libnozzle.c \ internals.c include_HEADERS = libnozzle.h pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = libnozzle.pc noinst_HEADERS = \ internals.h lib_LTLIBRARIES = libnozzle.la libnozzle_la_SOURCES = $(sources) libnozzle_la_CFLAGS = $(AM_CFLAGS) $(PTHREAD_CFLAGS) $(libnl_CFLAGS) $(libnlroute_CFLAGS) EXTRA_libnozzle_la_DEPENDENCIES = $(SYMFILE) +if BUILD_FOR_SOLARIS libnozzle_la_LDFLAGS = $(AM_LDFLAGS) \ - -Wl,-version-script,$(srcdir)/$(SYMFILE) \ - -version-info $(libversion) + -Wl,-M$(abs_srcdir)/$(SYMFILE) \ + -version-info $(libnozzleversion) +else +libnozzle_la_LDFLAGS = $(AM_LDFLAGS) \ + -Wl,-version-script,$(abs_srcdir)/$(SYMFILE) \ + -version-info $(libnozzleversion) +endif -libnozzle_la_LIBADD = $(PTHREAD_LIBS) $(libnl_LIBS) $(libnlroute_LIBS) +libnozzle_la_LIBADD = $(PTHREAD_LIBS) $(socket_LIBS) $(libnl_LIBS) $(libnlroute_LIBS) $(dlpi_LIBS) check-local: check-annocheck-libs - endif diff --git a/libnozzle/internals.h b/libnozzle/internals.h index 251ec58c..1b4dc81c 100644 --- a/libnozzle/internals.h +++ b/libnozzle/internals.h @@ -1,69 +1,85 @@ /* * Copyright (C) 2017-2025 Red Hat, Inc. All rights reserved. * * Author: Fabio M. Di Nitto * * This software licensed under LGPL-2.0+ */ #ifndef __NOZZLE_INTERNALS_H__ #define __NOZZLE_INTERNALS_H__ #include "config.h" #ifdef KNET_LINUX #include #endif +#ifdef KNET_SOLARIS +#include +#endif #include #include "libnozzle.h" struct nozzle_lib_config { struct nozzle_iface *head; int ioctlfd; #ifdef KNET_LINUX struct nl_sock *nlsock; #endif }; #define MACADDR_CHAR_MAX 18 /* * 11 = post-down.d * 1 = / */ #define UPDOWN_PATH_MAX PATH_MAX - 11 - 1 - IFNAMSIZ struct nozzle_iface { char name[IFNAMSIZ]; /* interface name */ int fd; /* interface fd */ int up; /* interface status 0 is down, 1 is up */ /* * extra data */ struct nozzle_ip *ip; /* configured ip addresses */ /* * default MAC address assigned by the kernel at creation time */ char default_mac[MACADDR_CHAR_MAX + 1]; int default_mtu; /* MTU assigned by the kernel at creation time */ int current_mtu; /* MTU configured by libnozzle user */ int hasupdown; /* interface has up/down path to scripts configured */ char updownpath[UPDOWN_PATH_MAX]; /* path to up/down scripts if configured */ struct nozzle_iface *next; + +#ifdef KNET_SOLARIS + int ip_fd; + int ip6_fd; +#endif }; +#ifdef KNET_SOLARIS +#define ifname ifr.lifr_name +#define ifmtu ifr.lifr_mtu +#define ifflags ifr.lifr_flags +#else #define ifname ifr.ifr_name +#define ifmtu ifr.ifr_mtu +#define ifflags ifr.ifr_flags +#endif int execute_bin_sh_command(const char *command, char **error_string); int find_ip(nozzle_t nozzle, const char *ipaddr, const char *prefix, struct nozzle_ip **ip, struct nozzle_ip **ip_prev); char *generate_v4_broadcast(const char *ipaddr, const char *prefix); #endif diff --git a/libnozzle/libnozzle.c b/libnozzle/libnozzle.c index 01878bd9..65fc8e26 100644 --- a/libnozzle/libnozzle.c +++ b/libnozzle/libnozzle.c @@ -1,1231 +1,1459 @@ /* * Copyright (C) 2010-2025 Red Hat, Inc. All rights reserved. * * Author: Fabio M. Di Nitto * * This software licensed under LGPL-2.0+ */ #include "config.h" #include #include #include #include #include #include #include #include #include #include +#ifdef KNET_SOLARIS +#include +#include +#include +#include +#include +#include +#else #include +#endif #include #include #include #include #include #include #ifdef KNET_LINUX #include /* * libnl3 < 3.3 includes kernel headers directly * causing conflicts with net/if.h included above */ #ifdef LIBNL3_WORKAROUND #define _LINUX_IF_H 1 #endif #include #include #include #include #endif #ifdef KNET_BSD #include #include #endif #include "libnozzle.h" #include "internals.h" /* * internal functions are all _unlocked_ * locking should be handled at external API functions */ static int lib_init = 0; static struct nozzle_lib_config lib_cfg; static pthread_mutex_t config_mutex = PTHREAD_MUTEX_INITIALIZER; /* * internal helpers */ static void lib_fini(void) { if (lib_cfg.head == NULL) { #ifdef KNET_LINUX nl_close(lib_cfg.nlsock); nl_socket_free(lib_cfg.nlsock); #endif close(lib_cfg.ioctlfd); lib_init = 0; } } static int is_valid_nozzle(const nozzle_t nozzle) { nozzle_t temp; if (!nozzle) { return 0; } if (!lib_init) { return 0; } temp = lib_cfg.head; while (temp != NULL) { if (nozzle == temp) return 1; temp = temp->next; } return 0; } static void destroy_iface(nozzle_t nozzle) { #ifdef KNET_BSD struct ifreq ifr; #endif if (!nozzle) return; if (nozzle->fd >= 0) close(nozzle->fd); #ifdef KNET_BSD memset(&ifr, 0, sizeof(struct ifreq)); memmove(ifname, nozzle->name, IFNAMSIZ); ioctl(lib_cfg.ioctlfd, SIOCIFDESTROY, &ifr); ioctl(lib_cfg.ioctlfd, SIOCGIFFLAGS, &ifr); #endif free(nozzle); lib_fini(); return; } static int get_iface_mtu(const nozzle_t nozzle) { int err = 0, savederrno = 0; +#ifdef KNET_SOLARIS + struct lifreq ifr; +#else struct ifreq ifr; - - memset(&ifr, 0, sizeof(struct ifreq)); +#endif + memset(&ifr, 0, sizeof(ifr)); memmove(ifname, nozzle->name, IFNAMSIZ); +#ifdef KNET_SOLARIS + err = ioctl(lib_cfg.ioctlfd, SIOCGLIFMTU, &ifr); +#else err = ioctl(lib_cfg.ioctlfd, SIOCGIFMTU, &ifr); +#endif if (err) { savederrno = errno; goto out_clean; } - err = ifr.ifr_mtu; - + err = ifmtu; out_clean: errno = savederrno; return err; } static int get_iface_mac(const nozzle_t nozzle, char **ether_addr) { int err = 0, savederrno = 0; +#ifdef KNET_SOLARIS + struct lifreq ifr; + dlpi_handle_t dlpi_handle; + dlpi_info_t dlpi_if_info; +#else struct ifreq ifr; +#endif char mac[MACADDR_CHAR_MAX]; #ifdef KNET_BSD struct ifaddrs *ifap = NULL; struct ifaddrs *ifa; int found = 0; #endif memset(&mac, 0, MACADDR_CHAR_MAX); memset(&ifr, 0, sizeof(struct ifreq)); memmove(ifname, nozzle->name, IFNAMSIZ); #ifdef KNET_LINUX err = ioctl(lib_cfg.ioctlfd, SIOCGIFHWADDR, &ifr); if (err) { savederrno = errno; goto out_clean; } ether_ntoa_r((struct ether_addr *)ifr.ifr_hwaddr.sa_data, mac); #endif +#ifdef KNET_SOLARIS + err = dlpi_open(nozzle->name, &dlpi_handle, 0); + if (err != DLPI_SUCCESS) { + err = -1; + goto out_clean; + + } + err = dlpi_info(dlpi_handle, &dlpi_if_info, 0); + + if (err != DLPI_SUCCESS) { + err = -1; + dlpi_close(dlpi_handle); + goto out_clean; + } + dlpi_close(dlpi_handle); + ether_ntoa_r((struct ether_addr *)dlpi_if_info.di_physaddr, mac); +#endif #ifdef KNET_BSD /* * there is no ioctl to get the ether address of an interface on FreeBSD * (not to be confused with hwaddr). Use workaround described here: * https://lists.freebsd.org/pipermail/freebsd-hackers/2004-June/007394.html */ err = getifaddrs(&ifap); if (err < 0) { savederrno = errno; goto out_clean; } ifa = ifap; while (ifa) { if (!strncmp(nozzle->name, ifa->ifa_name, IFNAMSIZ)) { found = 1; break; } ifa=ifa->ifa_next; } if (found) { ether_ntoa_r((struct ether_addr *)LLADDR((struct sockaddr_dl *)ifa->ifa_addr), mac); } else { errno = EINVAL; err = -1; } freeifaddrs(ifap); if (err) { goto out_clean; } #endif *ether_addr = strdup(mac); if (!*ether_addr) { savederrno = errno; err = -1; } - out_clean: errno = savederrno; return err; } #define IP_ADD 1 #define IP_DEL 2 static int _set_ip(nozzle_t nozzle, int command, const char *ipaddr, const char *prefix, int secondary) { int fam; - char *broadcast = NULL; int err = 0; + char *broadcast = NULL; #ifdef KNET_LINUX struct rtnl_addr *addr = NULL; struct nl_addr *local_addr = NULL; struct nl_addr *bcast_addr = NULL; struct nl_cache *cache = NULL; int ifindex; #endif -#ifdef KNET_BSD +#if defined(KNET_BSD) || defined(KNET_SOLARIS) char cmdline[4096]; char proto[6]; char *error_string = NULL; #endif if (!strchr(ipaddr, ':')) { fam = AF_INET; broadcast = generate_v4_broadcast(ipaddr, prefix); if (!broadcast) { errno = EINVAL; return -1; } } else { fam = AF_INET6; } #ifdef KNET_LINUX addr = rtnl_addr_alloc(); if (!addr) { errno = ENOMEM; err = -1; goto out; } if (rtnl_link_alloc_cache(lib_cfg.nlsock, AF_UNSPEC, &cache) < 0) { errno = ENOMEM; err = -1; goto out; } ifindex = rtnl_link_name2i(cache, nozzle->name); if (ifindex == 0) { errno = ENOENT; err = -1; goto out; } rtnl_addr_set_ifindex(addr, ifindex); if (nl_addr_parse(ipaddr, fam, &local_addr) < 0) { errno = EINVAL; err = -1; goto out; } if (rtnl_addr_set_local(addr, local_addr) < 0) { errno = EINVAL; err = -1; goto out; } if (broadcast) { if (nl_addr_parse(broadcast, fam, &bcast_addr) < 0) { errno = EINVAL; err = -1; goto out; } if (rtnl_addr_set_broadcast(addr, bcast_addr) < 0) { errno = EINVAL; err = -1; goto out; } } rtnl_addr_set_prefixlen(addr, atoi(prefix)); if (command == IP_ADD) { if (rtnl_addr_add(lib_cfg.nlsock, addr, 0) < 0) { errno = EINVAL; err = -1; goto out; } } else { if (rtnl_addr_delete(lib_cfg.nlsock, addr, 0) < 0) { errno = EINVAL; err = -1; goto out; } } out: if (addr) { rtnl_addr_put(addr); } if (local_addr) { nl_addr_put(local_addr); } if (bcast_addr) { nl_addr_put(bcast_addr); } if (cache) { nl_cache_put(cache); } if (broadcast) { free(broadcast); } return err; #endif -#ifdef KNET_BSD +#if defined(KNET_BSD) || defined(KNET_SOLARIS) /* * TODO: port to use ioctl and such, drop shell forking here */ memset(cmdline, 0, sizeof(cmdline)); if (fam == AF_INET) { snprintf(proto, sizeof(proto), "inet"); } else { snprintf(proto, sizeof(proto), "inet6"); } if (command == IP_ADD) { + char *addif = ""; +#ifdef KNET_SOLARIS + // Solaris needs a new logical i/f for each address + if (secondary) { + addif = "addif"; + } +#endif snprintf(cmdline, sizeof(cmdline)-1, - "ifconfig %s %s %s/%s", - nozzle->name, proto, ipaddr, prefix); + "ifconfig %s %s %s %s/%s", + nozzle->name, proto, addif, ipaddr, prefix); if (broadcast) { snprintf(cmdline + strlen(cmdline), sizeof(cmdline) - strlen(cmdline) -1, " broadcast %s", broadcast); } +#ifdef KNET_BSD if ((secondary) && (fam == AF_INET)) { snprintf(cmdline + strlen(cmdline), sizeof(cmdline) - strlen(cmdline) -1, " alias"); } +#endif } else { +#ifdef KNET_BSD snprintf(cmdline, sizeof(cmdline)-1, "ifconfig %s %s %s/%s delete", nozzle->name, proto, ipaddr, prefix); +#elif KNET_SOLARIS + /* If this is the first IP (on tap0) then we can't use removeif, + * we need to set the IP to 0 + */ + char *removeif = "removeif"; + if ((!secondary) && (fam == AF_INET)) { + ipaddr="0.0.0.0"; + removeif = ""; + } + snprintf(cmdline, sizeof(cmdline)-1, + "ifconfig %s %s %s %s", + nozzle->name, proto, removeif, ipaddr); +#endif } if (broadcast) { free(broadcast); } /* * temporary workaround as we port libnozzle to BSD ioctl * for IP address management */ err = execute_bin_sh_command(cmdline, &error_string); if (error_string) { free(error_string); error_string = NULL; } return err; #endif } + +#ifdef KNET_SOLARIS +// Most of this taken from openconnect +static int link_proto(nozzle_t nozzle, int unit_nr, + const char *devname, uint64_t flags) +{ + int ip_fd, mux_id, tap2_fd; + struct lifreq ifr; + + tap2_fd = open("/dev/tap", O_RDWR); + if (tap2_fd < 0) { + return -EIO; + } + if (ioctl(tap2_fd, I_PUSH, "ip") < 0) { + close(tap2_fd); + return -EIO; + } + + sprintf(ifr.lifr_name, "tap%d", unit_nr); + ifr.lifr_ppa = unit_nr; + ifr.lifr_flags = flags; + + // We need to do this, but it is allowed to fail. + // No I don't understand it. + (void)ioctl(tap2_fd, SIOCSLIFNAME, &ifr); + + ip_fd = open(devname, O_RDWR); + if (ip_fd < 0) { + close(tap2_fd); + return -1; + } + + mux_id = ioctl(ip_fd, I_LINK, tap2_fd); + if (mux_id < 0) { + close(tap2_fd); + close(ip_fd); + return -1; + } + + close(tap2_fd); + + return ip_fd; +} + +int solaris_setup_tap(nozzle_t nozzle, char *devname, int namelen) +{ + int tap_fd = -1; + static char tap_name[80]; + int unit_nr; + + tap_fd = open("/dev/tap", O_RDWR); + if (tap_fd < 0) { + return -EIO; + } + + unit_nr = ioctl(tap_fd, TUNNEWPPA, -1); + if (unit_nr < 0) { + close(tap_fd); + return -EIO; + } + + if (ioctl(tap_fd, I_SRDOPT, RMSGD) < 0) { + close(tap_fd); + return -EIO; + } + + if (strlen(devname) == 0) { + sprintf(tap_name, "tap%d", unit_nr); + strncpy(devname, tap_name, namelen); + } else { + if (sscanf(devname, "tap%d", &unit_nr) == 1) { + struct strioctl strioc_ppa; + int ppa = unit_nr; + int newppa; + memset(&strioc_ppa, 0, sizeof(strioc_ppa)); + + strioc_ppa.ic_cmd = TUNNEWPPA; + strioc_ppa.ic_timout = 0; + strioc_ppa.ic_len = sizeof(ppa); + strioc_ppa.ic_dp = (char *)&ppa; + if ((newppa = ioctl(tap_fd, I_STR, &strioc_ppa)) < 0) { + return -errno; + } + } else { + return -EIO; + } + } + + nozzle->ip_fd = link_proto(nozzle, unit_nr, "/dev/udp", IFF_IPV4); + if (nozzle->ip_fd < 0) { + close(tap_fd); + return -EIO; + } + + nozzle->ip6_fd = link_proto(nozzle, unit_nr, "/dev/udp6", IFF_IPV6); + if (nozzle->ip6_fd < 0) { + close(tap_fd); + close(nozzle->ip_fd); + nozzle->ip_fd = -1; + return -EIO; + } else { + nozzle->ip6_fd = -1; + } + + return tap_fd; +} + +#endif + /* * Exported public API */ nozzle_t nozzle_open(char *devname, size_t devname_size, const char *updownpath) { int savederrno = 0; nozzle_t nozzle = NULL; char *temp_mac = NULL; #ifdef KNET_LINUX struct ifreq ifr; #endif #ifdef KNET_BSD uint16_t i; long int nozzlenum = 0; char curnozzle[IFNAMSIZ]; struct ifreq ifr; #endif if (devname == NULL) { errno = EINVAL; return NULL; } if (devname_size < IFNAMSIZ) { errno = EINVAL; return NULL; } /* Need to allow space for trailing NUL */ if (strlen(devname) >= IFNAMSIZ) { errno = E2BIG; return NULL; } #ifdef KNET_BSD /* * BSD does not support named devices like Linux * but it is possible to force a nozzleX device number * where X is 0 to 255. */ if (strlen(devname)) { if (strncmp(devname, "tap", 3)) { errno = EINVAL; return NULL; } errno = 0; nozzlenum = strtol(devname+3, NULL, 10); if (errno) { errno = EINVAL; return NULL; } if ((nozzlenum < 0) || (nozzlenum > 255)) { errno = EINVAL; return NULL; } } #endif if (updownpath) { /* only absolute paths */ if (updownpath[0] != '/') { errno = EINVAL; return NULL; } if (strlen(updownpath) >= UPDOWN_PATH_MAX) { errno = E2BIG; return NULL; } } savederrno = pthread_mutex_lock(&config_mutex); if (savederrno) { errno = savederrno; return NULL; } if (!lib_init) { lib_cfg.head = NULL; #ifdef KNET_LINUX lib_cfg.nlsock = nl_socket_alloc(); if (!lib_cfg.nlsock) { savederrno = errno; goto out_error; } if (nl_connect(lib_cfg.nlsock, NETLINK_ROUTE) < 0) { savederrno = EBUSY; goto out_error; } lib_cfg.ioctlfd = socket(AF_INET, SOCK_STREAM, 0); #endif #ifdef KNET_BSD lib_cfg.ioctlfd = socket(AF_LOCAL, SOCK_DGRAM, 0); +#endif +#ifdef KNET_SOLARIS + lib_cfg.ioctlfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); #endif if (lib_cfg.ioctlfd < 0) { savederrno = errno; goto out_error; } lib_init = 1; } nozzle = malloc(sizeof(struct nozzle_iface)); if (!nozzle) { savederrno = ENOMEM; goto out_error; } memset(nozzle, 0, sizeof(struct nozzle_iface)); #ifdef KNET_BSD if (!strlen(devname)) { for (i = 0; i < 256; i++) { - memset(&ifr, 0, sizeof(struct ifreq)); + memset(&ifr, 0, sizeof(ifr)); snprintf(curnozzle, sizeof(curnozzle) - 1, "tap%u", i); memmove(ifr.ifr_name, curnozzle, IFNAMSIZ); if (ioctl(lib_cfg.ioctlfd, SIOCIFCREATE2, &ifr) < 0) { continue; } - snprintf(curnozzle, sizeof(curnozzle) - 1, "/dev/tap%u", i); nozzle->fd = open(curnozzle, O_RDWR); savederrno = errno; if (nozzle->fd > 0) { break; } /* For some reason we can't open that device, keep trying but don't leave debris */ (void)ioctl(lib_cfg.ioctlfd, SIOCIFDESTROY, &ifr); (void)ioctl(lib_cfg.ioctlfd, SIOCGIFFLAGS, &ifr); } snprintf(curnozzle, sizeof(curnozzle) -1 , "tap%u", i); } else { memmove(ifr.ifr_name, devname, IFNAMSIZ); if (ioctl(lib_cfg.ioctlfd, SIOCIFCREATE2, &ifr) < 0) { goto out_error; } - snprintf(curnozzle, sizeof(curnozzle) - 1, "/dev/%s", devname); nozzle->fd = open(curnozzle, O_RDWR); savederrno = errno; snprintf(curnozzle, sizeof(curnozzle) - 1, "%s", devname); } if (nozzle->fd < 0) { savederrno = EBUSY; goto out_error; } memmove(devname, curnozzle, IFNAMSIZ); memmove(nozzle->name, curnozzle, IFNAMSIZ); #endif +#ifdef KNET_SOLARIS + nozzle->fd = solaris_setup_tap(nozzle, devname, devname_size); + memmove(nozzle->name, devname, IFNAMSIZ); +#endif + #ifdef KNET_LINUX if ((nozzle->fd = open("/dev/net/tun", O_RDWR)) < 0) { savederrno = errno; goto out_error; } memset(&ifr, 0, sizeof(struct ifreq)); memmove(ifname, devname, IFNAMSIZ); - ifr.ifr_flags = IFF_TAP | IFF_NO_PI; + ifflags = IFF_TAP | IFF_NO_PI; if (ioctl(nozzle->fd, TUNSETIFF, &ifr) < 0) { savederrno = errno; goto out_error; } if ((strlen(devname) > 0) && (strcmp(devname, ifname) != 0)) { savederrno = EBUSY; goto out_error; } memmove(devname, ifname, IFNAMSIZ); memmove(nozzle->name, ifname, IFNAMSIZ); #endif - nozzle->default_mtu = get_iface_mtu(nozzle); if (nozzle->default_mtu < 0) { savederrno = errno; goto out_error; } if (get_iface_mac(nozzle, &temp_mac) < 0) { savederrno = errno; goto out_error; } strncpy(nozzle->default_mac, temp_mac, 18); free(temp_mac); if (updownpath) { int len = strlen(updownpath); strcpy(nozzle->updownpath, updownpath); if (nozzle->updownpath[len-1] != '/') { nozzle->updownpath[len] = '/'; } nozzle->hasupdown = 1; } nozzle->next = lib_cfg.head; lib_cfg.head = nozzle; pthread_mutex_unlock(&config_mutex); errno = savederrno; return nozzle; out_error: destroy_iface(nozzle); pthread_mutex_unlock(&config_mutex); errno = savederrno; return NULL; } int nozzle_close(nozzle_t nozzle) { int err = 0, savederrno = 0; nozzle_t temp; nozzle_t prev; struct nozzle_ip *ip, *ip_next; savederrno = pthread_mutex_lock(&config_mutex); if (savederrno) { errno = savederrno; return -1; } if (!is_valid_nozzle(nozzle)) { savederrno = EINVAL; err = -1; goto out_clean; } temp = lib_cfg.head; prev = lib_cfg.head; while ((temp) && (temp != nozzle)) { prev = temp; temp = temp->next; } if (nozzle == prev) { lib_cfg.head = nozzle->next; } else { prev->next = nozzle->next; } ip = nozzle->ip; while (ip) { ip_next = ip->next; free(ip); ip = ip_next; } +#ifdef KNET_SOLARIS + close(nozzle->ip_fd); + close(nozzle->ip6_fd); +#endif destroy_iface(nozzle); out_clean: pthread_mutex_unlock(&config_mutex); errno = savederrno; return err; } int nozzle_run_updown(const nozzle_t nozzle, uint8_t action, char **exec_string) { int err = 0, savederrno = 0; char command[PATH_MAX]; const char *action_str = NULL; struct stat sb; if (action > NOZZLE_POSTDOWN) { errno = EINVAL; return -1; } if (!exec_string) { errno = EINVAL; return -1; } savederrno = pthread_mutex_lock(&config_mutex); if (savederrno) { errno = savederrno; return -1; } if (!is_valid_nozzle(nozzle)) { savederrno = EINVAL; err = -1; goto out_clean; } if (!nozzle->hasupdown) { savederrno = EINVAL; err = -1; goto out_clean; } switch(action) { case NOZZLE_PREUP: action_str = "pre-up.d"; break; case NOZZLE_UP: action_str = "up.d"; break; case NOZZLE_DOWN: action_str = "down.d"; break; case NOZZLE_POSTDOWN: action_str = "post-down.d"; break; } memset(command, 0, PATH_MAX); snprintf(command, PATH_MAX, "%s/%s/%s", nozzle->updownpath, action_str, nozzle->name); err = stat(command, &sb); if (err) { savederrno = errno; goto out_clean; } /* * clear errno from previous calls as there is no errno * returned from execute_bin_sh_command */ savederrno = 0; err = execute_bin_sh_command(command, exec_string); if (err) { err = -2; } out_clean: pthread_mutex_unlock(&config_mutex); errno = savederrno; return err; } int nozzle_set_up(nozzle_t nozzle) { int err = 0, savederrno = 0; +#ifdef KNET_SOLARIS + struct lifreq ifr; +#else struct ifreq ifr; +#endif savederrno = pthread_mutex_lock(&config_mutex); if (savederrno) { errno = savederrno; return -1; } if (!is_valid_nozzle(nozzle)) { savederrno = EINVAL; err = -1; goto out_clean; } if (nozzle->up) { goto out_clean; } memset(&ifr, 0, sizeof(struct ifreq)); memmove(ifname, nozzle->name, IFNAMSIZ); err = ioctl(lib_cfg.ioctlfd, SIOCGIFFLAGS, &ifr); if (err) { savederrno = errno; goto out_clean; } - ifr.ifr_flags |= IFF_UP | IFF_RUNNING; + ifflags |= IFF_UP | IFF_RUNNING; err = ioctl(lib_cfg.ioctlfd, SIOCSIFFLAGS, &ifr); if (err) { savederrno = errno; goto out_clean; } nozzle->up = 1; out_clean: pthread_mutex_unlock(&config_mutex); errno = savederrno; return err; } int nozzle_set_down(nozzle_t nozzle) { int err = 0, savederrno = 0; +#ifdef KNET_SOLARIS + struct lifreq ifr; +#else struct ifreq ifr; +#endif savederrno = pthread_mutex_lock(&config_mutex); if (savederrno) { errno = savederrno; return -1; } if (!is_valid_nozzle(nozzle)) { savederrno = EINVAL; err = -1; goto out_clean; } if (!nozzle->up) { goto out_clean; } memset(&ifr, 0, sizeof(struct ifreq)); memmove(ifname, nozzle->name, IFNAMSIZ); err = ioctl(lib_cfg.ioctlfd, SIOCGIFFLAGS, &ifr); if (err) { savederrno = errno; goto out_clean; } - ifr.ifr_flags &= ~IFF_UP; + ifflags &= ~IFF_UP; err = ioctl(lib_cfg.ioctlfd, SIOCSIFFLAGS, &ifr); if (err) { savederrno = errno; goto out_clean; } nozzle->up = 0; out_clean: pthread_mutex_unlock(&config_mutex); errno = savederrno; return err; } int nozzle_get_mtu(const nozzle_t nozzle) { int err = 0, savederrno = 0; savederrno = pthread_mutex_lock(&config_mutex); if (savederrno) { errno = savederrno; return -1; } if (!is_valid_nozzle(nozzle)) { savederrno = EINVAL; err = -1; goto out_clean; } err = get_iface_mtu(nozzle); savederrno = errno; out_clean: pthread_mutex_unlock(&config_mutex); errno = savederrno; return err; } int nozzle_get_mac(const nozzle_t nozzle, char **ether_addr) { int err = 0, savederrno = 0; if (!ether_addr) { errno = EINVAL; return -1; } savederrno = pthread_mutex_lock(&config_mutex); if (savederrno) { errno = savederrno; return -1; } if (!is_valid_nozzle(nozzle)) { savederrno = EINVAL; err = -1; goto out_clean; } err = get_iface_mac(nozzle, ether_addr); out_clean: pthread_mutex_unlock(&config_mutex); errno = savederrno; return err; } int nozzle_set_mac(nozzle_t nozzle, const char *ether_addr) { int err = 0, savederrno = 0; +#ifdef KNET_SOLARIS + dlpi_handle_t dlpi_handle; +#else struct ifreq ifr; +#endif if (!ether_addr) { errno = EINVAL; return -1; } savederrno = pthread_mutex_lock(&config_mutex); if (savederrno) { errno = savederrno; return -1; } if (!is_valid_nozzle(nozzle)) { savederrno = EINVAL; err = -1; goto out_clean; } +#ifdef KNET_LINUX memset(&ifr, 0, sizeof(struct ifreq)); memmove(ifname, nozzle->name, IFNAMSIZ); -#ifdef KNET_LINUX err = ioctl(lib_cfg.ioctlfd, SIOCGIFHWADDR, &ifr); if (err) { savederrno = errno; goto out_clean; } memmove(ifr.ifr_hwaddr.sa_data, ether_aton(ether_addr), ETH_ALEN); err = ioctl(lib_cfg.ioctlfd, SIOCSIFHWADDR, &ifr); savederrno = errno; #endif #ifdef KNET_BSD + memset(&ifr, 0, sizeof(struct ifreq)); + memmove(ifname, nozzle->name, IFNAMSIZ); err = ioctl(lib_cfg.ioctlfd, SIOCGIFADDR, &ifr); if (err) { savederrno = errno; goto out_clean; } memmove(ifr.ifr_addr.sa_data, ether_aton(ether_addr), ETHER_ADDR_LEN); ifr.ifr_addr.sa_len = ETHER_ADDR_LEN; err = ioctl(lib_cfg.ioctlfd, SIOCSIFLLADDR, &ifr); savederrno = errno; #endif +#ifdef KNET_SOLARIS + err = dlpi_open(nozzle->name, &dlpi_handle, 0); + if (err != DLPI_SUCCESS) { + err = -1; + goto out_clean; + } + err = dlpi_set_physaddr(dlpi_handle, DL_CURR_PHYS_ADDR, + ether_aton(ether_addr), ETHERADDRL); + + if (err != DLPI_SUCCESS) { + err = -1; + dlpi_close(dlpi_handle); + goto out_clean; + } + dlpi_close(dlpi_handle); +#endif out_clean: pthread_mutex_unlock(&config_mutex); errno = savederrno; return err; } int nozzle_reset_mac(nozzle_t nozzle) { return nozzle_set_mac(nozzle, nozzle->default_mac); } nozzle_t nozzle_get_handle_by_name(const char *devname) { int savederrno = 0; nozzle_t nozzle; if ((devname == NULL) || (strlen(devname) > IFNAMSIZ)) { errno = EINVAL; return NULL; } savederrno = pthread_mutex_lock(&config_mutex); if (savederrno) { errno = savederrno; return NULL; } nozzle = lib_cfg.head; while (nozzle != NULL) { if (!strcmp(devname, nozzle->name)) break; nozzle = nozzle->next; } if (!nozzle) { savederrno = ENOENT; } pthread_mutex_unlock(&config_mutex); errno = savederrno; return nozzle; } const char *nozzle_get_name_by_handle(const nozzle_t nozzle) { int savederrno = 0; char *name = NULL; savederrno = pthread_mutex_lock(&config_mutex); if (savederrno) { errno = savederrno; return NULL; } if (!is_valid_nozzle(nozzle)) { savederrno = ENOENT; goto out_clean; } name = nozzle->name; out_clean: pthread_mutex_unlock(&config_mutex); errno = savederrno; return name; } int nozzle_get_fd(const nozzle_t nozzle) { int fd = -1, savederrno = 0; savederrno = pthread_mutex_lock(&config_mutex); if (savederrno) { errno = savederrno; return -1; } if (!is_valid_nozzle(nozzle)) { savederrno = ENOENT; fd = -1; goto out_clean; } fd = nozzle->fd; out_clean: pthread_mutex_unlock(&config_mutex); errno = savederrno; return fd; } int nozzle_set_mtu(nozzle_t nozzle, const int mtu) { int err = 0, savederrno = 0; struct nozzle_ip *tmp_ip; +#ifdef KNET_SOLARIS + struct lifreq ifr; +#else struct ifreq ifr; +#endif if (!mtu) { errno = EINVAL; return -1; } savederrno = pthread_mutex_lock(&config_mutex); if (savederrno) { errno = savederrno; return -1; } if (!is_valid_nozzle(nozzle)) { savederrno = EINVAL; err = -1; goto out_clean; } err = nozzle->current_mtu = get_iface_mtu(nozzle); if (err < 0) { savederrno = errno; goto out_clean; } - memset(&ifr, 0, sizeof(struct ifreq)); + memset(&ifr, 0, sizeof(ifr)); memmove(ifname, nozzle->name, IFNAMSIZ); - ifr.ifr_mtu = mtu; + ifmtu = mtu; +#ifdef KNET_SOLARIS + err = ioctl(nozzle->ip_fd, SIOCSLIFMTU, &ifr); +#else err = ioctl(lib_cfg.ioctlfd, SIOCSIFMTU, &ifr); +#endif if (err) { savederrno = errno; goto out_clean; } if ((nozzle->current_mtu < 1280) && (mtu >= 1280)) { +#ifdef KNET_SOLARIS + int secondary = 1; +#else + int secondary = 0; +#endif tmp_ip = nozzle->ip; while(tmp_ip) { if (tmp_ip->domain == AF_INET6) { - err = _set_ip(nozzle, IP_ADD, tmp_ip->ipaddr, tmp_ip->prefix, 0); + err = _set_ip(nozzle, IP_ADD, tmp_ip->ipaddr, tmp_ip->prefix, secondary); if (err) { savederrno = errno; err = -1; goto out_clean; } } tmp_ip = tmp_ip->next; } } out_clean: pthread_mutex_unlock(&config_mutex); errno = savederrno; return err; } int nozzle_reset_mtu(nozzle_t nozzle) { return nozzle_set_mtu(nozzle, nozzle->default_mtu); } int nozzle_add_ip(nozzle_t nozzle, const char *ipaddr, const char *prefix) { int err = 0, savederrno = 0; int found = 0; struct nozzle_ip *ip = NULL, *ip_prev = NULL, *ip_last = NULL; int secondary = 0; if ((!ipaddr) || (!prefix)) { errno = EINVAL; return -1; } savederrno = pthread_mutex_lock(&config_mutex); if (savederrno) { errno = savederrno; return -1; } if (!is_valid_nozzle(nozzle)) { savederrno = EINVAL; err = -1; goto out_clean; } found = find_ip(nozzle, ipaddr, prefix, &ip, &ip_prev); if (found) { goto out_clean; } ip = malloc(sizeof(struct nozzle_ip)); if (!ip) { savederrno = errno; err = -1 ; goto out_clean; } memset(ip, 0, sizeof(struct nozzle_ip)); strncpy(ip->ipaddr, ipaddr, IPADDR_CHAR_MAX); strncpy(ip->prefix, prefix, PREFIX_CHAR_MAX); if (!strchr(ip->ipaddr, ':')) { ip->domain = AF_INET; } else { ip->domain = AF_INET6; } /* * if user asks for an IPv6 address, but MTU < 1280 * store the IP and bring it up later if and when MTU > 1280 */ if ((ip->domain == AF_INET6) && (get_iface_mtu(nozzle) < 1280)) { err = 0; } else { - if (nozzle->ip) { + /* We make all Solaris IP6 addresses secondary's (using addif) + * otherwise we can't remove the last one + */ + if (nozzle->ip +#ifdef KNET_SOLARIS + || ip->domain == AF_INET6 +#endif + ) { secondary = 1; } err = _set_ip(nozzle, IP_ADD, ipaddr, prefix, secondary); savederrno = errno; } if (err) { free(ip); goto out_clean; } if (nozzle->ip) { ip_last = nozzle->ip; while (ip_last->next != NULL) { ip_last = ip_last->next; } ip_last->next = ip; } else { nozzle->ip = ip; } out_clean: pthread_mutex_unlock(&config_mutex); errno = savederrno; return err; } int nozzle_del_ip(nozzle_t nozzle, const char *ipaddr, const char *prefix) { int err = 0, savederrno = 0; int found = 0; struct nozzle_ip *ip = NULL, *ip_prev = NULL; if ((!ipaddr) || (!prefix)) { errno = EINVAL; return -1; } savederrno = pthread_mutex_lock(&config_mutex); if (savederrno) { errno = savederrno; return -1; } if (!is_valid_nozzle(nozzle)) { savederrno = EINVAL; err = -1; goto out_clean; } found = find_ip(nozzle, ipaddr, prefix, &ip, &ip_prev); if (!found) { goto out_clean; } /* * if user asks for an IPv6 address, but MTU < 1280 * the IP might not be configured on the interface and we only need to * remove it from our internal database */ if ((ip->domain == AF_INET6) && (get_iface_mtu(nozzle) < 1280)) { err = 0; } else { err = _set_ip(nozzle, IP_DEL, ipaddr, prefix, 0); savederrno = errno; } if (!err) { if (ip == ip_prev) { nozzle->ip = ip->next; } else { ip_prev->next = ip->next; } free(ip); } out_clean: pthread_mutex_unlock(&config_mutex); errno = savederrno; return err; } int nozzle_get_ips(const nozzle_t nozzle, struct nozzle_ip **nozzle_ip) { int err = 0, savederrno = 0; if (!nozzle_ip) { errno = EINVAL; return -1; } savederrno = pthread_mutex_lock(&config_mutex); if (savederrno) { errno = savederrno; return -1; } if (!is_valid_nozzle(nozzle)) { err = -1; savederrno = EINVAL; goto out_clean; } *nozzle_ip = nozzle->ip; out_clean: pthread_mutex_unlock(&config_mutex); errno = savederrno; return err; } diff --git a/libnozzle/libnozzle_exported_syms b/libnozzle/libnozzle_exported_syms index 11a8a84d..f441b6d3 100644 --- a/libnozzle/libnozzle_exported_syms +++ b/libnozzle/libnozzle_exported_syms @@ -1,15 +1,31 @@ # Version and symbol export for libnozzle.so # # Copyright (C) 2011-2025 Red Hat, Inc. All rights reserved. # # Author: Fabio M. Di Nitto # # This software licensed under LGPL-2.0+ # LIBNOZZLE { global: - nozzle_*; + nozzle_add_ip; + nozzle_close; + nozzle_del_ip; + nozzle_get_fd; + nozzle_get_handle_by_name; + nozzle_get_ips; + nozzle_get_mac; + nozzle_get_mtu; + nozzle_get_name_by_handle; + nozzle_open; + nozzle_reset_mac; + nozzle_reset_mtu; + nozzle_run_updown; + nozzle_set_down; + nozzle_set_mac; + nozzle_set_mtu; + nozzle_set_up; local: *; }; diff --git a/libnozzle/tests/api-test-coverage b/libnozzle/tests/api-test-coverage index 35405ab5..ef81daa1 100755 --- a/libnozzle/tests/api-test-coverage +++ b/libnozzle/tests/api-test-coverage @@ -1,93 +1,95 @@ #!/bin/sh # # Copyright (C) 2016-2025 Red Hat, Inc. All rights reserved. # # Author: Fabio M. Di Nitto # # This software licensed under GPL-2.0+ # srcdir="$1"/libnozzle/tests builddir="$2"/libnozzle/tests headerapicalls="$(grep nozzle_ "$srcdir"/../libnozzle.h | grep -v "^ \*" | grep -v ^struct | grep -v "^[[:space:]]" | grep -v typedef | sed -e 's/(.*//g' -e 's/^const //g' -e 's/\*//g' | awk '{print $2}')" # The PowerPC64 ELFv1 ABI defines the address of a function as that of a # function descriptor defined in .opd, a data (D) section. Other ABIs # use the entry address of the function itself in the text (T) section. -exportedapicalls="$(nm -B -D "$builddir"/../.libs/libnozzle.so | grep ' [DT] ' | awk '{print $3}' | sed -e 's#@@LIBNOZZLE##g')" +# Filter additional symbols exported by ln(1) on Solaris. +exportedapicalls="$(nm -B -D "$builddir"/../.libs/libnozzle.so | grep ' [DT] ' | awk '{if ($3 != "_edata" && $3 != "_GLOBAL_OFFSET_TABLE_" && $3 != "_PROCEDURE_LINKAGE_TABLE_") {print $3}}' | sed -e 's#@@LIBNOZZLE##g')" +#exportedapicalls="$(nm -B -D "$builddir"/../.libs/libnozzle.so | grep ' [DT] ' | awk '{print $3}' | sed -e 's#@@LIBNOZZLE##g')" echo "Checking for exported symbols NOT available in header file" for i in $exportedapicalls; do found=0 for x in $headerapicalls; do if [ "$x" = "$i" ]; then found=1 break; fi done if [ "$found" = 0 ]; then echo "Symbol $i not found in header file" exit 1 fi done echo "Checking for symbols in header file NOT exported by binary lib" for i in $headerapicalls; do found=0 for x in $exportedapicalls; do if [ "$x" = "$i" ]; then found=1 break; fi done if [ "$found" = 0 ]; then echo "Symbol $i not found in binary lib" exit 1 fi done echo "Checking for tests with memcheck exceptions" for i in $(grep -l is_memcheck "$srcdir"/*.c | grep -v test-common); do echo "WARNING: $(basename $i) - has memcheck exception enabled" done echo "Checking for tests with helgrind exceptions" for i in $(grep -l is_helgrind "$srcdir"/*.c | grep -v test-common); do echo "WARNING: $(basename $i) has helgrind exception enabled" done echo "Checking for api test coverage" numapicalls=0 found=0 missing=0 for i in $headerapicalls; do [ "$i" = nozzle_reset_mtu ] && i=nozzle_set_mtu # tested together [ "$i" = nozzle_reset_mac ] && i=nozzle_set_mac # tested together numapicalls=$((numapicalls + 1)) if [ -f $srcdir/api_${i}.c ]; then found=$((found + 1)) else missing=$((missing + 1)) echo "MISSING: $i" fi done echo "Summary" echo "-------" echo "Found : $found" echo "Missing : $missing" echo "Total : $numapicalls" which bc > /dev/null 2>&1 && { coverage=$(echo "scale=3; $found / $numapicalls * 100" | bc -l) echo "Coverage: $coverage%" } exit 0 exit 0 diff --git a/libnozzle/tests/api_nozzle_add_ip.c b/libnozzle/tests/api_nozzle_add_ip.c index b611d7c9..8fafa867 100644 --- a/libnozzle/tests/api_nozzle_add_ip.c +++ b/libnozzle/tests/api_nozzle_add_ip.c @@ -1,294 +1,306 @@ /* * Copyright (C) 2010-2025 Red Hat, Inc. All rights reserved. * * Author: Fabio M. Di Nitto * * This software licensed under GPL-2.0+ */ #include "config.h" #include #include #include #include #include #include #include #include #include "test-common.h" char testipv4_1[IPBUFSIZE]; char testipv4_2[IPBUFSIZE]; char testipv6_1[IPBUFSIZE]; char testipv6_2[IPBUFSIZE]; static int test(void) { char device_name[IFNAMSIZ]; size_t size = IFNAMSIZ; char verifycmd[2048]; int err = 0; nozzle_t nozzle; char *error_string = NULL; printf("Testing interface add ip\n"); memset(device_name, 0, size); nozzle = nozzle_open(device_name, size, NULL); if (!nozzle) { printf("Unable to init %s\n", device_name); return -1; } printf("Testing error conditions\n"); printf("Testing invalid nozzle handle\n"); err = nozzle_add_ip(NULL, testipv4_1, "24"); if ((!err) || (errno != EINVAL)) { printf("nozzle_add_ip accepted invalid nozzle handle\n"); err = -1; goto out_clean; } printf("Testing empty ip address\n"); err = nozzle_add_ip(nozzle, NULL, "24"); if ((!err) || (errno != EINVAL)) { printf("nozzle_add_ip accepted invalid ip address\n"); err = -1; goto out_clean; } printf("Testing empty netmask\n"); err = nozzle_add_ip(nozzle, testipv4_1, NULL); if ((!err) || (errno != EINVAL)) { printf("nozzle_add_ip accepted invalid netmask\n"); err = -1; goto out_clean; } printf("Adding ip: %s/24\n", testipv4_1); err = nozzle_add_ip(nozzle, testipv4_1, "24"); if (err < 0) { printf("Unable to assign IP address\n"); err = -1; goto out_clean; } printf("Adding ip: %s/24\n", testipv4_2); err = nozzle_add_ip(nozzle, testipv4_2, "24"); if (err < 0) { printf("Unable to assign IP address\n"); err = -1; goto out_clean; } printf("Adding duplicate ip: %s/24\n", testipv4_1); err = nozzle_add_ip(nozzle, testipv4_1, "24"); if (err < 0) { printf("Unable to find IP address in libnozzle db\n"); err = -1; goto out_clean; } printf("Checking ip: %s/24\n", testipv4_1); memset(verifycmd, 0, sizeof(verifycmd)); snprintf(verifycmd, sizeof(verifycmd)-1, #ifdef KNET_LINUX "ip addr show dev %s | grep -q %s/24", nozzle->name, testipv4_1); #endif #ifdef KNET_BSD "ifconfig %s | grep -q %s", nozzle->name, testipv4_1); +#endif +#ifdef KNET_SOLARIS + "ifconfig %s | grep -q %s", nozzle->name, testipv4_1); #endif err = execute_bin_sh_command(verifycmd, &error_string); if (error_string) { printf("Error string: %s\n", error_string); free(error_string); error_string = NULL; } if (err) { printf("Unable to verify IP address\n"); err = -1; goto out_clean; } printf("Checking ip: %s/24\n", testipv4_2); memset(verifycmd, 0, sizeof(verifycmd)); snprintf(verifycmd, sizeof(verifycmd)-1, #ifdef KNET_LINUX "ip addr show dev %s | grep -q %s/24", nozzle->name, testipv4_2); #endif #ifdef KNET_BSD "ifconfig %s | grep -q %s", nozzle->name, testipv4_2); +#endif +#ifdef KNET_SOLARIS + "ifconfig %s:1 | grep -q %s", nozzle->name, testipv4_2); #endif err = execute_bin_sh_command(verifycmd, &error_string); if (error_string) { printf("Error string: %s\n", error_string); free(error_string); error_string = NULL; } if (err) { printf("Unable to verify IP address\n"); err = -1; goto out_clean; } printf("Deleting ip: %s/24\n", testipv4_1); - err = nozzle_del_ip(nozzle, testipv4_1, "24"); + err = nozzle_del_ip(nozzle, testipv4_2, "24"); if (err < 0) { printf("Unable to delete IP address\n"); err = -1; goto out_clean; } printf("Deleting ip: %s/24\n", testipv4_2); - err = nozzle_del_ip(nozzle, testipv4_2, "24"); + err = nozzle_del_ip(nozzle, testipv4_1, "24"); if (err < 0) { printf("Unable to delete IP address\n"); err = -1; goto out_clean; } printf("Adding ip: %s/64\n", testipv6_1); err = nozzle_add_ip(nozzle, testipv6_1, "64"); if (err < 0) { printf("Unable to assign IP address\n"); err = -1; goto out_clean; } memset(verifycmd, 0, sizeof(verifycmd)); snprintf(verifycmd, sizeof(verifycmd)-1, #ifdef KNET_LINUX "ip addr show dev %s | grep -q %s/64", nozzle->name, testipv6_1); #endif +#ifdef KNET_SOLARIS + "ifconfig %s:1 inet6| grep -q %s", nozzle->name, testipv6_1); +#endif #ifdef KNET_BSD "ifconfig %s | grep -q %s", nozzle->name, testipv6_1); #endif err = execute_bin_sh_command(verifycmd, &error_string); if (error_string) { printf("Error string: %s\n", error_string); free(error_string); error_string = NULL; } if (err) { printf("Unable to verify IP address\n"); err = -1; goto out_clean; } printf("Deleting ip: %s/64\n", testipv6_1); err = nozzle_del_ip(nozzle, testipv6_1, "64"); if (err) { printf("Unable to delete IP address\n"); err = -1; goto out_clean; } printf("Testing adding an IPv6 address with mtu < 1280 and restore\n"); printf("Lowering interface MTU\n"); err = nozzle_set_mtu(nozzle, 1200); if (err) { printf("Unable to set MTU to 1200\n"); err = -1; goto out_clean; } printf("Adding ip: %s/64\n", testipv6_1); err = nozzle_add_ip(nozzle, testipv6_1, "64"); if (err < 0) { printf("Unable to assign IP address\n"); err = -1; goto out_clean; } memset(verifycmd, 0, sizeof(verifycmd)); snprintf(verifycmd, sizeof(verifycmd)-1, #ifdef KNET_LINUX "ip addr show dev %s | grep -q %s/64", nozzle->name, testipv6_1); #endif -#ifdef KNET_BSD +#if defined(KNET_BSD) || defined(KNET_SOLARIS) "ifconfig %s | grep -q %s", nozzle->name, testipv6_1); #endif err = execute_bin_sh_command(verifycmd, &error_string); if (error_string) { printf("Error string: %s\n", error_string); free(error_string); error_string = NULL; } if (!err) { printf("Unable to verify IP address\n"); err = -1; goto out_clean; } printf("Resetting MTU\n"); err = nozzle_reset_mtu(nozzle); if (err) { printf("Unable to set reset MTU\n"); err = -1; goto out_clean; } memset(verifycmd, 0, sizeof(verifycmd)); snprintf(verifycmd, sizeof(verifycmd)-1, #ifdef KNET_LINUX "ip addr show dev %s | grep -q %s/64", nozzle->name, testipv6_1); #endif #ifdef KNET_BSD "ifconfig %s | grep -q %s", nozzle->name, testipv6_1); +#endif +#ifdef KNET_SOLARIS + "ifconfig %s:1 inet6 | grep -q %s", nozzle->name, testipv6_1); #endif err = execute_bin_sh_command(verifycmd, &error_string); if (error_string) { printf("Error string: %s\n", error_string); free(error_string); error_string = NULL; } if (err) { printf("Unable to verify IP address\n"); err = -1; goto out_clean; } printf("Deleting ip: %s/64\n", testipv6_1); err = nozzle_del_ip(nozzle, testipv6_1, "64"); if (err) { printf("Unable to delete IP address\n"); err = -1; goto out_clean; } out_clean: nozzle_close(nozzle); return err; } int main(void) { need_root(); need_tun(); make_local_ips(testipv4_1, testipv4_2, testipv6_1, testipv6_2); if (test() < 0) return FAIL; return PASS; } diff --git a/libnozzle/tests/api_nozzle_close.c b/libnozzle/tests/api_nozzle_close.c index bb7e1f9c..99b2fc46 100644 --- a/libnozzle/tests/api_nozzle_close.c +++ b/libnozzle/tests/api_nozzle_close.c @@ -1,131 +1,134 @@ /* * Copyright (C) 2018-2025 Red Hat, Inc. All rights reserved. * * Author: Fabio M. Di Nitto * * This software licensed under GPL-2.0+ */ #include "config.h" #include #include #include #include #include #include #include #include +#ifdef KNET_SOLARIS +#include +#else #include - +#endif #ifdef KNET_LINUX #include #include #endif #ifdef KNET_BSD #include #endif #include "test-common.h" char testipv4_1[IPBUFSIZE]; char testipv4_2[IPBUFSIZE]; char testipv6_1[IPBUFSIZE]; char testipv6_2[IPBUFSIZE]; static int test(void) { char device_name[2*IFNAMSIZ]; size_t size = IFNAMSIZ; nozzle_t nozzle; memset(device_name, 0, sizeof(device_name)); /* * this test is duplicated from api_nozzle_open.c */ printf("Testing random nozzle interface:\n"); if (test_iface(device_name, size, NULL) < 0) { printf("Unable to create random interface\n"); return -1; } printf("Testing ERROR conditions\n"); printf("Testing nozzle_close with NULL nozzle\n"); if ((nozzle_close(NULL) >= 0) || (errno != EINVAL)) { printf("Something is wrong in nozzle_close sanity checks\n"); return -1; } printf("Testing nozzle_close with random bytes nozzle pointer\n"); nozzle = (nozzle_t)0x1; if ((nozzle_close(nozzle) >= 0) || (errno != EINVAL)) { printf("Something is wrong in nozzle_close sanity checks\n"); return -1; } return 0; } /* * requires running the test suite with valgrind */ static int check_nozzle_close_leak(void) { char device_name[IFNAMSIZ]; size_t size = IFNAMSIZ; int err=0; nozzle_t nozzle; printf("Testing close leak (needs valgrind)\n"); memset(device_name, 0, size); nozzle = nozzle_open(device_name, size, NULL); if (!nozzle) { printf("Unable to init %s\n", device_name); return -1; } printf("Adding ip: %s/24\n", testipv4_1); err = nozzle_add_ip(nozzle, testipv4_1, "24"); if (err < 0) { printf("Unable to assign IP address\n"); err=-1; goto out_clean; } printf("Adding ip: %s/24\n", testipv4_2); err = nozzle_add_ip(nozzle, testipv4_2, "24"); if (err < 0) { printf("Unable to assign IP address\n"); err=-1; goto out_clean; } out_clean: nozzle_close(nozzle); return err; } int main(void) { need_root(); need_tun(); make_local_ips(testipv4_1, testipv4_2, testipv6_1, testipv6_2); if (test() < 0) return FAIL; if (check_nozzle_close_leak() < 0) return FAIL; return 0; } diff --git a/libnozzle/tests/api_nozzle_del_ip.c b/libnozzle/tests/api_nozzle_del_ip.c index ad14695c..25010a75 100644 --- a/libnozzle/tests/api_nozzle_del_ip.c +++ b/libnozzle/tests/api_nozzle_del_ip.c @@ -1,267 +1,276 @@ /* * Copyright (C) 2010-2025 Red Hat, Inc. All rights reserved. * * Author: Fabio M. Di Nitto * * This software licensed under GPL-2.0+ */ #include "config.h" #include #include #include #include #include #include #include #include #include "test-common.h" char testipv4_1[IPBUFSIZE]; char testipv4_2[IPBUFSIZE]; char testipv6_1[IPBUFSIZE]; char testipv6_2[IPBUFSIZE]; static int test(void) { char device_name[IFNAMSIZ]; size_t size = IFNAMSIZ; char verifycmd[2048]; int err = 0; nozzle_t nozzle; char *error_string = NULL; printf("Testing interface del ip\n"); memset(device_name, 0, size); nozzle = nozzle_open(device_name, size, NULL); if (!nozzle) { printf("Unable to init %s\n", device_name); return -1; } printf("Testing error conditions\n"); printf("Testing invalid nozzle handle\n"); err = nozzle_del_ip(NULL, testipv4_1, "24"); if ((!err) || (errno != EINVAL)) { printf("nozzle_del_ip accepted invalid nozzle handle\n"); err = -1; goto out_clean; } printf("Testing empty ip address\n"); err = nozzle_del_ip(nozzle, NULL, "24"); if ((!err) || (errno != EINVAL)) { printf("nozzle_del_ip accepted invalid ip address\n"); err = -1; goto out_clean; } printf("Testing empty netmask\n"); err = nozzle_del_ip(nozzle, testipv4_1, NULL); if ((!err) || (errno != EINVAL)) { printf("nozzle_del_ip accepted invalid netmask\n"); err = -1; goto out_clean; } printf("Adding ip: %s/24\n", testipv4_1); err = nozzle_add_ip(nozzle, testipv4_1, "24"); if (err < 0) { printf("Unable to assign IP address\n"); err = -1; goto out_clean; } printf("Checking ip: %s/24\n", testipv4_1); memset(verifycmd, 0, sizeof(verifycmd)); snprintf(verifycmd, sizeof(verifycmd)-1, #ifdef KNET_LINUX "ip addr show dev %s | grep -q %s/24", nozzle->name, testipv4_1); #endif -#ifdef KNET_BSD +#if defined(KNET_BSD) || defined(KNET_SOLARIS) "ifconfig %s | grep -q %s", nozzle->name, testipv4_1); #endif err = execute_bin_sh_command(verifycmd, &error_string); if (error_string) { printf("Error string: %s\n", error_string); free(error_string); error_string = NULL; } if (err) { printf("Unable to verify IP address\n"); err = -1; goto out_clean; } printf("Deleting ip: %s/24\n", testipv4_1); err = nozzle_del_ip(nozzle, testipv4_1, "24"); if (err < 0) { printf("Unable to delete IP address\n"); err = -1; goto out_clean; } printf("Checking ip: %s/24\n", testipv4_1); memset(verifycmd, 0, sizeof(verifycmd)); snprintf(verifycmd, sizeof(verifycmd)-1, #ifdef KNET_LINUX "ip addr show dev %s | grep -q %s/24", nozzle->name, testipv4_1); #endif -#ifdef KNET_BSD +#if defined(KNET_BSD) || defined(KNET_SOLARIS) "ifconfig %s | grep -q %s", nozzle->name, testipv4_1); #endif err = execute_bin_sh_command(verifycmd, &error_string); if (error_string) { printf("Error string: %s\n", error_string); free(error_string); error_string = NULL; } if (!err) { printf("Unable to verify IP address\n"); err = -1; goto out_clean; } printf("Deleting ip: %s/24 again\n", testipv4_1); err = nozzle_del_ip(nozzle, testipv4_1, "24"); if (err < 0) { printf("Unable to delete IP address\n"); err = -1; goto out_clean; } printf("Adding ip: %s/64\n", testipv6_1); err = nozzle_add_ip(nozzle, testipv6_1, "64"); if (err < 0) { printf("Unable to assign IP address\n"); err = -1; goto out_clean; } memset(verifycmd, 0, sizeof(verifycmd)); snprintf(verifycmd, sizeof(verifycmd)-1, #ifdef KNET_LINUX "ip addr show dev %s | grep -q %s/64", nozzle->name, testipv6_1); #endif #ifdef KNET_BSD "ifconfig %s | grep -q %s", nozzle->name, testipv6_1); +#endif +#ifdef KNET_SOLARIS + "ifconfig %s:1 inet6 | grep -q %s", nozzle->name, testipv6_1); #endif err = execute_bin_sh_command(verifycmd, &error_string); if (error_string) { printf("Error string: %s\n", error_string); free(error_string); error_string = NULL; } if (err) { printf("Unable to verify IP address\n"); err = -1; goto out_clean; } printf("Deleting ip: %s/64\n", testipv6_1); err = nozzle_del_ip(nozzle, testipv6_1, "64"); if (err) { printf("Unable to delete IP address\n"); err = -1; goto out_clean; } memset(verifycmd, 0, sizeof(verifycmd)); snprintf(verifycmd, sizeof(verifycmd)-1, #ifdef KNET_LINUX "ip addr show dev %s | grep -q %s/64", nozzle->name, testipv6_1); #endif #ifdef KNET_BSD "ifconfig %s | grep -q %s", nozzle->name, testipv6_1); +#endif +#ifdef KNET_SOLARIS + "ifconfig %s inet6 | grep -q %s", nozzle->name, testipv6_1); #endif err = execute_bin_sh_command(verifycmd, &error_string); if (error_string) { printf("Error string: %s\n", error_string); free(error_string); error_string = NULL; } if (!err) { printf("Unable to verify IP address\n"); err = -1; goto out_clean; } printf("Testing deleting an IPv6 address with mtu < 1280 (in db, not on interface)\n"); printf("Lowering interface MTU\n"); err = nozzle_set_mtu(nozzle, 1200); if (err) { printf("Unable to set MTU to 1200\n"); err = -1; goto out_clean; } printf("Adding ip: %s/64\n", testipv6_1); err = nozzle_add_ip(nozzle, testipv6_1, "64"); if (err < 0) { printf("Unable to assign IP address\n"); err = -1; goto out_clean; } memset(verifycmd, 0, sizeof(verifycmd)); snprintf(verifycmd, sizeof(verifycmd)-1, #ifdef KNET_LINUX "ip addr show dev %s | grep -q %s/64", nozzle->name, testipv6_1); #endif #ifdef KNET_BSD "ifconfig %s | grep -q %s", nozzle->name, testipv6_1); +#endif +#ifdef KNET_SOLARIS + "ifconfig %s inet6 | grep -q %s", nozzle->name, testipv6_1); #endif err = execute_bin_sh_command(verifycmd, &error_string); if (error_string) { printf("Error string: %s\n", error_string); free(error_string); error_string = NULL; } if (!err) { printf("Unable to verify IP address\n"); err = -1; goto out_clean; } printf("Deleting ip: %s/64 with low mtu\n", testipv6_1); err = nozzle_del_ip(nozzle, testipv6_1, "64"); if (err) { printf("Unable to delete IP address\n"); err = -1; goto out_clean; } out_clean: nozzle_close(nozzle); return err; } int main(void) { need_root(); need_tun(); make_local_ips(testipv4_1, testipv4_2, testipv6_1, testipv6_2); if (test() < 0) return FAIL; return PASS; } diff --git a/libnozzle/tests/api_nozzle_get_mac.c b/libnozzle/tests/api_nozzle_get_mac.c index f26a00a9..793e97be 100644 --- a/libnozzle/tests/api_nozzle_get_mac.c +++ b/libnozzle/tests/api_nozzle_get_mac.c @@ -1,131 +1,135 @@ /* * Copyright (C) 2018-2025 Red Hat, Inc. All rights reserved. * * Author: Fabio M. Di Nitto * * This software licensed under GPL-2.0+ */ #include "config.h" #include #include #include #include #include #include #include #include +#ifdef KNET_SOLARIS +#include +#else #include +#endif #ifdef KNET_LINUX #include #include #endif #ifdef KNET_BSD #include #endif #include "test-common.h" static int test(void) { char device_name[IFNAMSIZ]; size_t size = IFNAMSIZ; int err=0; nozzle_t nozzle; char *current_mac = NULL, *temp_mac = NULL, *err_mac = NULL; struct ether_addr *cur_mac, *tmp_mac; printf("Testing get MAC\n"); memset(device_name, 0, size); nozzle = nozzle_open(device_name, size, NULL); if (!nozzle) { printf("Unable to init %s\n", device_name); return -1; } printf("Get current MAC\n"); if (nozzle_get_mac(nozzle, ¤t_mac) < 0) { printf("Unable to get current MAC address.\n"); err = -1; goto out_clean; } printf("Current MAC: %s\n", current_mac); printf("Setting MAC: 00:01:01:01:01:01\n"); if (nozzle_set_mac(nozzle, "00:01:01:01:01:01") < 0) { printf("Unable to set current MAC address.\n"); err = -1; goto out_clean; } if (nozzle_get_mac(nozzle, &temp_mac) < 0) { printf("Unable to get current MAC address.\n"); err = -1; goto out_clean; } printf("Current MAC: %s\n", temp_mac); cur_mac = ether_aton(current_mac); tmp_mac = ether_aton(temp_mac); printf("Comparing MAC addresses\n"); if (memcmp(cur_mac, tmp_mac, sizeof(struct ether_addr))) { printf("Mac addresses are not the same?!\n"); err = -1; goto out_clean; } printf("Testing ERROR conditions\n"); printf("Pass NULL to get_mac (pass1)\n"); errno = 0; if ((nozzle_get_mac(NULL, &err_mac) >= 0) || (errno != EINVAL)) { printf("Something is wrong in nozzle_get_mac sanity checks\n"); err = -1; goto out_clean; } printf("Pass NULL to get_mac (pass2)\n"); errno = 0; if ((nozzle_get_mac(nozzle, NULL) >= 0) || (errno != EINVAL)) { printf("Something is wrong in nozzle_get_mac sanity checks\n"); err = -1; goto out_clean; } out_clean: if (err_mac) { printf("Something managed to set err_mac!\n"); err = -1; free(err_mac); } if (current_mac) free(current_mac); if (temp_mac) free(temp_mac); if (nozzle) { nozzle_close(nozzle); } return err; } int main(void) { need_root(); need_tun(); if (test() < 0) return FAIL; return PASS; } diff --git a/libnozzle/tests/api_nozzle_get_mtu.c b/libnozzle/tests/api_nozzle_get_mtu.c index d5cf3301..57a44374 100644 --- a/libnozzle/tests/api_nozzle_get_mtu.c +++ b/libnozzle/tests/api_nozzle_get_mtu.c @@ -1,99 +1,104 @@ /* * Copyright (C) 2018-2025 Red Hat, Inc. All rights reserved. * * Author: Fabio M. Di Nitto * * This software licensed under GPL-2.0+ */ #include "config.h" #include #include #include #include #include #include #include #include "test-common.h" static int test(void) { char device_name[IFNAMSIZ]; size_t size = IFNAMSIZ; int err=0; nozzle_t nozzle; int current_mtu = 0; int expected_mtu = 1500; printf("Testing get MTU\n"); memset(device_name, 0, size); nozzle = nozzle_open(device_name, size, NULL); if (!nozzle) { printf("Unable to init %s\n", device_name); return -1; } printf("Comparing default MTU\n"); current_mtu = nozzle_get_mtu(nozzle); if (current_mtu < 0) { printf("Unable to get MTU\n"); err = -1; goto out_clean; } if (current_mtu != expected_mtu) { printf("current mtu [%d] does not match expected default [%d]\n", current_mtu, expected_mtu); err = -1; goto out_clean; } - printf("Setting MTU to 9000\n"); +#ifdef KNET_SOLARIS + // Solaris doesn't allow MTU > 1500 + expected_mtu = 900; +#else expected_mtu = 9000; +#endif + printf("Setting MTU to %d\n", expected_mtu); if (nozzle_set_mtu(nozzle, expected_mtu) < 0) { printf("Unable to set MTU to %d\n", expected_mtu); err = -1; goto out_clean; } current_mtu = nozzle_get_mtu(nozzle); if (current_mtu < 0) { printf("Unable to get MTU\n"); err = -1; goto out_clean; } if (current_mtu != expected_mtu) { printf("current mtu [%d] does not match expected value [%d]\n", current_mtu, expected_mtu); err = -1; goto out_clean; } printf("Testing ERROR conditions\n"); printf("Passing empty struct to get_mtu\n"); if (nozzle_get_mtu(NULL) > 0) { printf("Something is wrong in nozzle_get_mtu sanity checks\n"); err = -1; goto out_clean; } out_clean: if (nozzle) { nozzle_close(nozzle); } return err; } int main(void) { need_root(); need_tun(); if (test() < 0) return FAIL; return PASS; } diff --git a/libnozzle/tests/api_nozzle_open.c b/libnozzle/tests/api_nozzle_open.c index 120b12f8..52f270fe 100644 --- a/libnozzle/tests/api_nozzle_open.c +++ b/libnozzle/tests/api_nozzle_open.c @@ -1,203 +1,205 @@ /* * Copyright (C) 2018-2025 Red Hat, Inc. All rights reserved. * * Author: Fabio M. Di Nitto * * This software licensed under GPL-2.0+ */ #include "config.h" #include #include #include #include #include "test-common.h" static int test_multi_eth(void) { char device_name1[IFNAMSIZ]; char device_name2[IFNAMSIZ]; size_t size = IFNAMSIZ; int err=0; nozzle_t nozzle1 = NULL; nozzle_t nozzle2 = NULL; printf("Testing multiple nozzle interface instances\n"); memset(device_name1, 0, size); memset(device_name2, 0, size); nozzle1 = nozzle_open(device_name1, size, NULL); if (!nozzle1) { printf("Unable to init %s\n", device_name1); err = -1; goto out_clean; } if (is_if_in_system(device_name1) > 0) { printf("Found interface %s on the system\n", device_name1); } else { printf("Unable to find interface %s on the system\n", device_name1); } nozzle2 = nozzle_open(device_name2, size, NULL); if (!nozzle2) { printf("Unable to init %s\n", device_name2); err = -1; goto out_clean; } if (is_if_in_system(device_name2) > 0) { printf("Found interface %s on the system\n", device_name2); } else { printf("Unable to find interface %s on the system\n", device_name2); } if (nozzle1) { nozzle_close(nozzle1); } if (nozzle2) { nozzle_close(nozzle2); } - +#ifndef KNET_SOLARIS printf("Testing error conditions\n"); printf("Open same device twice\n"); memset(device_name1, 0, size); nozzle1 = nozzle_open(device_name1, size, NULL); if (!nozzle1) { printf("Unable to init %s\n", device_name1); err = -1; goto out_clean; } if (is_if_in_system(device_name1) > 0) { printf("Found interface %s on the system\n", device_name1); } else { printf("Unable to find interface %s on the system\n", device_name1); } nozzle2 = nozzle_open(device_name1, size, NULL); if (nozzle2) { printf("We were able to init 2 interfaces with the same name!\n"); err = -1; goto out_clean; } - +#endif out_clean: if (nozzle1) { nozzle_close(nozzle1); } if (nozzle2) { nozzle_close(nozzle2); } return err; } static int test(void) { char device_name[2*IFNAMSIZ]; char fakepath[PATH_MAX]; size_t size = IFNAMSIZ; +#ifndef KNET_SOLARIS uint8_t randombyte = get_random_byte(); +#endif memset(device_name, 0, sizeof(device_name)); printf("Creating random nozzle interface:\n"); if (test_iface(device_name, size, NULL) < 0) { printf("Unable to create random interface\n"); return -1; } #ifdef KNET_LINUX printf("Creating kronostest%u nozzle interface:\n", randombyte); snprintf(device_name, IFNAMSIZ, "kronostest%u", randombyte); if (test_iface(device_name, size, NULL) < 0) { printf("Unable to create kronostest%u interface\n", randombyte); return -1; } #endif -#ifdef KNET_BSD +#if KNET_BSD printf("Creating tap%u nozzle interface:\n", randombyte); snprintf(device_name, IFNAMSIZ, "tap%u", randombyte); if (test_iface(device_name, size, NULL) < 0) { printf("Unable to create tap%u interface\n", randombyte); return -1; } printf("Creating kronostest%u nozzle interface:\n", randombyte); snprintf(device_name, IFNAMSIZ, "kronostest%u", randombyte); if (test_iface(device_name, size, NULL) == 0) { printf("BSD should not accept kronostest%u interface\n", randombyte); return -1; } #endif printf("Testing ERROR conditions\n"); printf("Testing dev == NULL\n"); errno=0; if ((test_iface(NULL, size, NULL) >= 0) || (errno != EINVAL)) { printf("Something is wrong in nozzle_open sanity checks\n"); return -1; } printf("Testing size < IFNAMSIZ\n"); errno=0; if ((test_iface(device_name, 1, NULL) >= 0) || (errno != EINVAL)) { printf("Something is wrong in nozzle_open sanity checks\n"); return -1; } printf("Testing device_name size > IFNAMSIZ\n"); errno=0; strcpy(device_name, "abcdefghilmnopqrstuvwz"); if ((test_iface(device_name, IFNAMSIZ, NULL) >= 0) || (errno != E2BIG)) { printf("Something is wrong in nozzle_open sanity checks\n"); return -1; } printf("Testing updown path != abs\n"); errno=0; memset(device_name, 0, IFNAMSIZ); if ((test_iface(device_name, IFNAMSIZ, "foo") >= 0) || (errno != EINVAL)) { printf("Something is wrong in nozzle_open sanity checks\n"); return -1; } memset(fakepath, 0, PATH_MAX); memset(fakepath, '/', PATH_MAX - 2); printf("Testing updown path > PATH_MAX\n"); errno=0; memset(device_name, 0, IFNAMSIZ); if ((test_iface(device_name, IFNAMSIZ, fakepath) >= 0) || (errno != E2BIG)) { printf("Something is wrong in nozzle_open sanity checks\n"); return -1; } return 0; } int main(void) { need_root(); need_tun(); if (test() < 0) return FAIL; if (test_multi_eth() < 0) return FAIL; return PASS; } diff --git a/libnozzle/tests/api_nozzle_set_down.c b/libnozzle/tests/api_nozzle_set_down.c index ec8db122..bd3c3ec9 100644 --- a/libnozzle/tests/api_nozzle_set_down.c +++ b/libnozzle/tests/api_nozzle_set_down.c @@ -1,127 +1,127 @@ /* * Copyright (C) 2018-2025 Red Hat, Inc. All rights reserved. * * Author: Fabio M. Di Nitto * * This software licensed under GPL-2.0+ */ #include "config.h" #include #include #include #include #include #include #include #include "test-common.h" static int test(void) { char verifycmd[1024]; char device_name[IFNAMSIZ]; size_t size = IFNAMSIZ; int err=0; nozzle_t nozzle; char *error_string = NULL; printf("Testing interface down\n"); memset(device_name, 0, size); nozzle = nozzle_open(device_name, size, NULL); if (!nozzle) { printf("Unable to init %s\n", device_name); return -1; } printf("Put the interface up\n"); err = nozzle_set_up(nozzle); if (err < 0) { printf("Unable to set interface up\n"); err = -1; goto out_clean; } memset(verifycmd, 0, sizeof(verifycmd)); snprintf(verifycmd, sizeof(verifycmd)-1, #ifdef KNET_LINUX "ip addr show dev %s | grep -q UP", nozzle->name); #endif -#ifdef KNET_BSD +#if defined(KNET_BSD) || defined(KNET_SOLARIS) "ifconfig %s | sed -e 's/LOWER_UP/GROT/' | grep -q UP", nozzle->name); #endif err = execute_bin_sh_command(verifycmd, &error_string); if (error_string) { printf("Error string: %s\n", error_string); free(error_string); error_string = NULL; } if (err < 0) { printf("Unable to verify inteface UP\n"); err = -1; goto out_clean; } printf("Put the interface down\n"); err = nozzle_set_down(nozzle); if (err < 0) { printf("Unable to put the interface down\n"); err = -1; goto out_clean; } memset(verifycmd, 0, sizeof(verifycmd)); snprintf(verifycmd, sizeof(verifycmd)-1, #ifdef KNET_LINUX "ip addr show dev %s | grep -q UP", nozzle->name); #endif -#ifdef KNET_BSD +#if defined(KNET_BSD) || defined(KNET_SOLARIS) "ifconfig %s | sed -e 's/LOWER_UP/GROT/' | grep -q UP", nozzle->name); #endif err = execute_bin_sh_command(verifycmd, &error_string); if (error_string) { printf("Error string: %s\n", error_string); free(error_string); error_string = NULL; } if (!err) { printf("Unable to verify inteface DOWN\n"); err = -1; goto out_clean; } printf("Try to DOWN the same interface twice\n"); if (nozzle_set_down(nozzle) < 0) { printf("Interface was already DOWN, spurious error received from nozzle_set_down\n"); err = -1; goto out_clean; } printf("Pass NULL to nozzle set_down\n"); errno = 0; if ((nozzle_set_down(NULL) >= 0) || (errno != EINVAL)) { printf("Something is wrong in nozzle_set_down sanity checks\n"); err = -1; goto out_clean; } out_clean: nozzle_close(nozzle); return err; } int main(void) { need_root(); need_tun(); if (test() < 0) return FAIL; return PASS; } diff --git a/libnozzle/tests/api_nozzle_set_mac.c b/libnozzle/tests/api_nozzle_set_mac.c index 0f4bdfea..7af046ca 100644 --- a/libnozzle/tests/api_nozzle_set_mac.c +++ b/libnozzle/tests/api_nozzle_set_mac.c @@ -1,159 +1,163 @@ /* * Copyright (C) 2018-2025 Red Hat, Inc. All rights reserved. * * Author: Fabio M. Di Nitto * * This software licensed under GPL-2.0+ */ #include "config.h" #include #include #include #include #include #include #include #include +#ifdef KNET_SOLARIS +#include +#else #include +#endif #ifdef KNET_LINUX #include #include #endif #ifdef KNET_BSD #include #endif #include "test-common.h" static int test(void) { char device_name[IFNAMSIZ]; size_t size = IFNAMSIZ; int err=0; nozzle_t nozzle; char *original_mac = NULL, *current_mac = NULL, *temp_mac = NULL; struct ether_addr *orig_mac, *cur_mac, *tmp_mac; printf("Testing set MAC\n"); memset(device_name, 0, size); nozzle = nozzle_open(device_name, size, NULL); if (!nozzle) { printf("Unable to init %s\n", device_name); return -1; } printf("Get current MAC\n"); if (nozzle_get_mac(nozzle, &original_mac) < 0) { printf("Unable to get current MAC address.\n"); err = -1; goto out_clean; } orig_mac = ether_aton(original_mac); if (nozzle_get_mac(nozzle, ¤t_mac) < 0) { printf("Unable to get current MAC address.\n"); err = -1; goto out_clean; } printf("Current MAC: %s\n", current_mac); printf("Setting MAC: 00:01:01:01:01:01\n"); if (nozzle_set_mac(nozzle, "00:01:01:01:01:01") < 0) { printf("Unable to set current MAC address.\n"); err = -1; goto out_clean; } if (nozzle_get_mac(nozzle, &temp_mac) < 0) { printf("Unable to get current MAC address.\n"); err = -1; goto out_clean; } printf("Current MAC: %s\n", temp_mac); cur_mac = ether_aton(current_mac); tmp_mac = ether_aton(temp_mac); printf("Comparing MAC addresses\n"); if (memcmp(cur_mac, tmp_mac, sizeof(struct ether_addr))) { printf("Mac addresses are not the same?!\n"); err = -1; goto out_clean; } printf("Testing reset_mac\n"); if (nozzle_reset_mac(nozzle) < 0) { printf("Unable to reset mac address\n"); err = -1; goto out_clean; } if (current_mac) { free(current_mac); current_mac = NULL; } if (nozzle_get_mac(nozzle, ¤t_mac) < 0) { printf("Unable to get current MAC address.\n"); err = -1; goto out_clean; } cur_mac = ether_aton(current_mac); if (memcmp(cur_mac, orig_mac, sizeof(struct ether_addr))) { printf("Mac addresses are not the same?!\n"); err = -1; goto out_clean; } printf("Testing ERROR conditions\n"); printf("Pass NULL to set_mac (pass1)\n"); errno = 0; if ((nozzle_set_mac(nozzle, NULL) >= 0) || (errno != EINVAL)) { printf("Something is wrong in nozzle_set_mac sanity checks\n"); err = -1; goto out_clean; } printf("Pass NULL to set_mac (pass2)\n"); errno = 0; if ((nozzle_set_mac(NULL, current_mac) >= 0) || (errno != EINVAL)) { printf("Something is wrong in nozzle_set_mac sanity checks\n"); err = -1; goto out_clean; } out_clean: if (current_mac) free(current_mac); if (temp_mac) free(temp_mac); if (original_mac) free(original_mac); if (nozzle) { nozzle_close(nozzle); } return err; } int main(void) { need_root(); need_tun(); if (test() < 0) return FAIL; return PASS; } diff --git a/libnozzle/tests/api_nozzle_set_mtu.c b/libnozzle/tests/api_nozzle_set_mtu.c index 46942550..c605e951 100644 --- a/libnozzle/tests/api_nozzle_set_mtu.c +++ b/libnozzle/tests/api_nozzle_set_mtu.c @@ -1,298 +1,315 @@ /* * Copyright (C) 2018-2025 Red Hat, Inc. All rights reserved. * * Author: Fabio M. Di Nitto * * This software licensed under GPL-2.0+ */ #include "config.h" #include #include #include #include #include #include #include #include "test-common.h" char testipv4_1[IPBUFSIZE]; char testipv4_2[IPBUFSIZE]; char testipv6_1[IPBUFSIZE]; char testipv6_2[IPBUFSIZE]; static int test(void) { char device_name[IFNAMSIZ]; size_t size = IFNAMSIZ; int err=0; nozzle_t nozzle; int current_mtu = 0; int expected_mtu = 1500; printf("Testing set MTU\n"); memset(device_name, 0, size); nozzle = nozzle_open(device_name, size, NULL); if (!nozzle) { printf("Unable to init %s\n", device_name); return -1; } printf("Comparing default MTU\n"); current_mtu = nozzle_get_mtu(nozzle); if (current_mtu < 0) { printf("Unable to get MTU\n"); err = -1; goto out_clean; } if (current_mtu != expected_mtu) { printf("current mtu [%d] does not match expected default [%d]\n", current_mtu, expected_mtu); err = -1; goto out_clean; } - printf("Setting MTU to 9000\n"); +#ifdef KNET_SOLARIS + // Solaris doesn't allow MTU > 1500 + expected_mtu = 900; +#else expected_mtu = 9000; +#endif + printf("Setting MTU to %d\n", expected_mtu); if (nozzle_set_mtu(nozzle, expected_mtu) < 0) { printf("Unable to set MTU to %d\n", expected_mtu); err = -1; goto out_clean; } current_mtu = nozzle_get_mtu(nozzle); if (current_mtu < 0) { printf("Unable to get MTU\n"); err = -1; goto out_clean; } if (current_mtu != expected_mtu) { printf("current mtu [%d] does not match expected value [%d]\n", current_mtu, expected_mtu); err = -1; goto out_clean; } printf("Restoring MTU to default\n"); expected_mtu = 1500; if (nozzle_reset_mtu(nozzle) < 0) { printf("Unable to reset mtu\n"); err = -1; goto out_clean; } current_mtu = nozzle_get_mtu(nozzle); if (current_mtu < 0) { printf("Unable to get MTU\n"); err = -1; goto out_clean; } if (current_mtu != expected_mtu) { printf("current mtu [%d] does not match expected value [%d]\n", current_mtu, expected_mtu); err = -1; goto out_clean; } printf("Testing ERROR conditions\n"); printf("Passing empty struct to set_mtu\n"); if (nozzle_set_mtu(NULL, 1500) == 0) { printf("Something is wrong in nozzle_set_mtu sanity checks\n"); err = -1; goto out_clean; } printf("Passing 0 mtu to set_mtu\n"); if (nozzle_set_mtu(nozzle, 0) == 0) { printf("Something is wrong in nozzle_set_mtu sanity checks\n"); err = -1; goto out_clean; } out_clean: if (nozzle) { nozzle_close(nozzle); } return err; } static int test_ipv6(void) { char device_name[IFNAMSIZ]; size_t size = IFNAMSIZ; char verifycmd[2048]; int err=0; nozzle_t nozzle; char *error_string = NULL; int current_mtu = 0; printf("Testing get/set MTU with IPv6 address\n"); memset(device_name, 0, size); nozzle = nozzle_open(device_name, size, NULL); if (!nozzle) { printf("Unable to init %s\n", device_name); return -1; } printf("Adding ip: %s/64\n", testipv6_1); err = nozzle_add_ip(nozzle, testipv6_1, "64"); if (err) { printf("Unable to assign IP address\n"); err=-1; goto out_clean; } memset(verifycmd, 0, sizeof(verifycmd)); snprintf(verifycmd, sizeof(verifycmd)-1, #ifdef KNET_LINUX "ip addr show dev %s | grep -q %s/64", nozzle->name, testipv6_1); #endif +#ifdef KNET_SOLARIS + "ifconfig %s:1 inet6| grep -q %s", nozzle->name, testipv6_1); +#endif #ifdef KNET_BSD "ifconfig %s | grep -q %s", nozzle->name, testipv6_1); #endif err = execute_bin_sh_command(verifycmd, &error_string); if (error_string) { printf("Error string: %s\n", error_string); free(error_string); error_string = NULL; } if (err) { printf("Unable to verify IP address\n"); err=-1; goto out_clean; } printf("Setting MTU to 1200\n"); if (nozzle_set_mtu(nozzle, 1200) < 0) { printf("Unable to set MTU to 1200\n"); err = -1; goto out_clean; } err = execute_bin_sh_command(verifycmd, &error_string); if (error_string) { printf("Error string: %s\n", error_string); free(error_string); error_string = NULL; } #ifdef KNET_LINUX if (!err) { #endif -#ifdef KNET_BSD +#if defined(KNET_BSD) || defined(KNET_SOLARIS) if (err) { #endif printf("Unable to verify IP address\n"); err=-1; goto out_clean; } printf("Adding ip: %s/64\n", testipv6_2); err = nozzle_add_ip(nozzle, testipv6_2, "64"); if (err < 0) { printf("Unable to assign IP address\n"); err=-1; goto out_clean; } memset(verifycmd, 0, sizeof(verifycmd)); snprintf(verifycmd, sizeof(verifycmd)-1, #ifdef KNET_LINUX "ip addr show dev %s | grep -q %s/64", nozzle->name, testipv6_2); #endif +#ifdef KNET_SOLARIS + "ifconfig %s:1 inet6| grep -q %s", nozzle->name, testipv6_2); +#endif #ifdef KNET_BSD "ifconfig %s | grep -q %s", nozzle->name, testipv6_2); #endif err = execute_bin_sh_command(verifycmd, &error_string); if (error_string) { printf("Error string: %s\n", error_string); free(error_string); error_string = NULL; } if (!err) { printf("Unable to verify IP address\n"); err=-1; goto out_clean; } printf("Restoring MTU to default\n"); if (nozzle_reset_mtu(nozzle) < 0) { printf("Unable to reset mtu\n"); err = -1; goto out_clean; } current_mtu = nozzle_get_mtu(nozzle); if (current_mtu != 1500) { printf("current mtu [%d] does not match expected value [1500]\n", current_mtu); err = -1; goto out_clean; } memset(verifycmd, 0, sizeof(verifycmd)); snprintf(verifycmd, sizeof(verifycmd)-1, #ifdef KNET_LINUX "ip addr show dev %s | grep -q %s/64", nozzle->name, testipv6_1); #endif +#ifdef KNET_SOLARIS + "ifconfig %s:1 inet6| grep -q %s", nozzle->name, testipv6_1); +#endif #ifdef KNET_BSD "ifconfig %s | grep -q %s", nozzle->name, testipv6_1); #endif err = execute_bin_sh_command(verifycmd, &error_string); if (error_string) { printf("Error string: %s\n", error_string); free(error_string); error_string = NULL; } if (err) { printf("Unable to verify IP address\n"); err=-1; goto out_clean; } memset(verifycmd, 0, sizeof(verifycmd)); snprintf(verifycmd, sizeof(verifycmd)-1, #ifdef KNET_LINUX "ip addr show dev %s | grep -q %s/64", nozzle->name, testipv6_2); #endif +#ifdef KNET_SOLARIS + "ifconfig %s:3 inet6| grep -q %s", nozzle->name, testipv6_2); +#endif #ifdef KNET_BSD "ifconfig %s | grep -q %s", nozzle->name, testipv6_2); #endif err = execute_bin_sh_command(verifycmd, &error_string); if (error_string) { printf("Error string: %s\n", error_string); free(error_string); error_string = NULL; } if (err) { printf("Unable to verify IP address\n"); err=-1; goto out_clean; } out_clean: if (nozzle) { nozzle_close(nozzle); } return err; } int main(void) { need_root(); need_tun(); make_local_ips(testipv4_1, testipv4_2, testipv6_1, testipv6_2); if (test() < 0) return FAIL; if (test_ipv6() < 0) return FAIL; return PASS; } diff --git a/libnozzle/tests/api_nozzle_set_up.c b/libnozzle/tests/api_nozzle_set_up.c index 1d274442..ac0ed8f7 100644 --- a/libnozzle/tests/api_nozzle_set_up.c +++ b/libnozzle/tests/api_nozzle_set_up.c @@ -1,101 +1,101 @@ /* * Copyright (C) 2018-2025 Red Hat, Inc. All rights reserved. * * Author: Fabio M. Di Nitto * * This software licensed under GPL-2.0+ */ #include "config.h" #include #include #include #include #include #include #include #include #include "test-common.h" static int test(void) { char verifycmd[1024]; char device_name[IFNAMSIZ]; size_t size = IFNAMSIZ; int err=0; nozzle_t nozzle; char *error_string = NULL; printf("Testing interface up/down\n"); memset(device_name, 0, size); nozzle = nozzle_open(device_name, size, NULL); if (!nozzle) { printf("Unable to init %s\n", device_name); return -1; } printf("Put the interface up\n"); if (nozzle_set_up(nozzle) < 0) { printf("Unable to set interface up\n"); err = -1; goto out_clean; } memset(verifycmd, 0, sizeof(verifycmd)); snprintf(verifycmd, sizeof(verifycmd)-1, #ifdef KNET_LINUX "ip addr show dev %s | grep -q UP", nozzle->name); #endif -#ifdef KNET_BSD +#if defined(KNET_BSD) || defined(KNET_SOLARIS) "ifconfig %s | grep -q UP", nozzle->name); #endif err = execute_bin_sh_command(verifycmd, &error_string); if (error_string) { printf("Error string: %s\n", error_string); free(error_string); error_string = NULL; } if (err < 0) { printf("Unable to verify inteface UP\n"); err = -1; goto out_clean; } printf("Test ERROR conditions\n"); printf("Try to UP the same interface twice\n"); if (nozzle_set_up(nozzle) < 0) { printf("Interface was already UP, spurious error received from nozzle_set_up\n"); err = -1; goto out_clean; } printf("Pass NULL to nozzle set_up\n"); errno = 0; if ((nozzle_set_up(NULL) >= 0) || (errno != EINVAL)) { printf("Something is wrong in nozzle_set_up sanity checks\n"); err = -1; goto out_clean; } out_clean: nozzle_set_down(nozzle); nozzle_close(nozzle); return err; } int main(void) { need_root(); need_tun(); if (test() < 0) return FAIL; return PASS; } diff --git a/libnozzle/tests/test-common.c b/libnozzle/tests/test-common.c index 00f26c88..0a3233ec 100644 --- a/libnozzle/tests/test-common.c +++ b/libnozzle/tests/test-common.c @@ -1,202 +1,220 @@ /* * Copyright (C) 2018-2025 Red Hat, Inc. All rights reserved. * * Author: Fabio M. Di Nitto * * This software licensed under GPL-2.0+ */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #ifdef KNET_BSD #include #include #endif - +#ifdef KNET_SOLARIS +#include +#endif #include "test-common.h" void need_root(void) { if (geteuid() != 0) { printf("This test requires root privileges\n"); exit(SKIP); } } void need_tun(void) { int fd; #ifdef KNET_LINUX const char *tundev = "/dev/net/tun"; #endif #ifdef KNET_BSD const char *tundev = "/dev/tap"; struct ifreq ifr; +#endif +#ifdef KNET_SOLARIS + const char *tundev = "/dev/tun"; +#endif +#if defined(KNET_BSD) || defined(KNET_SOLARIS) int ioctlfd = socket(AF_LOCAL, SOCK_DGRAM, 0); if (ioctlfd < 0) { printf("Unable to init ioctlfd (errno=%d)\n", errno); exit(FAIL); } #endif fd = open(tundev, O_RDWR); if (fd < 0) { printf("Failed to open %s (errno=%d); this test requires TUN support\n", tundev, errno); -#ifdef KNET_BSD +#if defined(KNET_BSD) || defined(KNET_SOLARIS) close(ioctlfd); #endif exit(SKIP); } #ifdef KNET_BSD memset(&ifr, 0, sizeof(struct ifreq)); ioctl(fd, TAPGIFNAME, &ifr); #endif close(fd); #ifdef KNET_BSD ioctl(ioctlfd, SIOCIFDESTROY, &ifr); ioctl(ioctlfd, SIOCGIFFLAGS, &ifr); close(ioctlfd); #endif } int test_iface(char *name, size_t size, const char *updownpath) { nozzle_t nozzle; nozzle=nozzle_open(name, size, updownpath); if (!nozzle) { printf("Unable to open nozzle (errno=%d).\n", errno); return -1; } printf("Created interface: %s\n", name); if (is_if_in_system(name) > 0) { printf("Found interface %s on the system\n", name); } else { printf("Unable to find interface %s on the system\n", name); } if (!nozzle_get_handle_by_name(name)) { printf("Unable to find interface %s in nozzle db\n", name); } else { printf("Found interface %s in nozzle db\n", name); } nozzle_close(nozzle); if (is_if_in_system(name) == 0) printf("Successfully removed interface %s from the system\n", name); return 0; } int is_if_in_system(char *name) { +#ifdef KNET_SOLARIS + dlpi_handle_t dlpi_handle; + + int err = dlpi_open(name, &dlpi_handle, 0); + if (err != DLPI_SUCCESS) { + return 0; + } + dlpi_close(dlpi_handle); + return 1; +#else struct ifaddrs *ifap = NULL; struct ifaddrs *ifa; int found = 0; if (getifaddrs(&ifap) < 0) { printf("Unable to get interface list.\n"); return -1; } ifa = ifap; while (ifa) { if (!strncmp(name, ifa->ifa_name, IFNAMSIZ)) { found = 1; break; } ifa=ifa->ifa_next; } freeifaddrs(ifap); return found; +#endif } int get_random_byte(void) { pid_t mypid; uint8_t *pid; uint8_t randombyte = 0; uint8_t i; if (sizeof(pid_t) < 4) { printf("pid_t is smaller than 4 bytes?\n"); exit(77); } mypid = getpid(); pid = (uint8_t *)&mypid; for (i = 0; i < sizeof(pid_t); i++) { if (pid[i] == 0) { pid[i] = 128; } } randombyte = pid[1]; return randombyte; } void make_local_ips(char *testipv4_1, char *testipv4_2, char *testipv6_1, char *testipv6_2) { pid_t mypid; uint8_t *pid; uint8_t i; memset(testipv4_1, 0, IPBUFSIZE); memset(testipv4_2, 0, IPBUFSIZE); memset(testipv6_1, 0, IPBUFSIZE); memset(testipv6_2, 0, IPBUFSIZE); mypid = getpid(); pid = (uint8_t *)&mypid; for (i = 0; i < sizeof(pid_t); i++) { if ((pid[i] == 0) || (pid[i] == 255)) { pid[i] = 128; } } snprintf(testipv4_1, IPBUFSIZE - 1, "127.%u.%u.%u", pid[1], pid[2], pid[0]); snprintf(testipv4_2, IPBUFSIZE - 1, "127.%u.%d.%u", pid[1], pid[2]+1, pid[0]); snprintf(testipv6_1, IPBUFSIZE - 1, - "fd%x:%x%x::1", + "fe%02x:%x%x::1", pid[1], pid[2], pid[0]); snprintf(testipv6_2, IPBUFSIZE - 1, - "fd%x:%x%x:1::1", + "fe%02x:%x%x:1::1", pid[1], pid[2], pid[0]); }