diff --git a/.gitignore b/.gitignore index 1bcf390e..35aecf8f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,60 +1,58 @@ *.o *.a *.la *.lo *.pc *.tar.* *.sha256* stamp-h1 Makefile.in Makefile .deps .libs .version .dirstamp # build-aux/release.mk related litter /.tarball-version /tag-* aclocal.m4 autoconf autoheader autom4te.cache automake compile config.* configure* -debian/changelog -debian/kronosnetd.postinst -debian/patches depcomp install-sh libtoolize ltmain.sh m4/libtool.m4 m4/lt~obsolete.m4 m4/ltoptions.m4 m4/ltsugar.m4 m4/ltversion.m4 missing libtool autoscan.log -init/kronosnetd -init/kronosnetd.service -kronosnetd/kronosnetd -kronosnetd/knet-keygen -kronosnetd/kronosnetd.logrotate kronosnet.spec *.swp *_test *_bench test-driver *.trs *.log Doxyfile* doxyfile*.stamp xml-*/ doxyxml *.3 cov* +*target/ +*Cargo.lock +*Cargo.toml +*libknet/bindings/rust/src/sys/libknet.rs +*libnozzle/bindings/rust/src/sys/libnozzle.rs +*build.rs diff --git a/NOTES_TO_PACKAGE_MAINTAINERS b/NOTES_TO_PACKAGE_MAINTAINERS index fd931f4b..b6396260 100644 --- a/NOTES_TO_PACKAGE_MAINTAINERS +++ b/NOTES_TO_PACKAGE_MAINTAINERS @@ -1,29 +1,35 @@ +# Copyright (C) 2016-2021 Red Hat, Inc. All rights reserved. +# +# Author: Fabio M. Di Nitto +# +# This software licensed under GPL-2.0+ + To: distribution package maintainers Those are a few things about this project that you should know. I surely welcome patches to support both in a better way. libnozzle is a simple commodity library currently used by corosync, to manage tun/tap devices. libknet is the core of this project. It is considered stable and supported in the stable* branches and still under heavy development in master branch. Upstream does guarantee onwire and update compatibility between releases in the same major versions (aka 1.x will always be able to talk to 1.x+n). There is NO guarantee of onwire compatibility between major versions of knet (aka: 1.x might not be able to talk to 2.x). libknet has a lot of build dependencies due to its modular implementation. It does not, however, link with all those libraries but uses a dlopen model to save runtime resources and provide flexibility to users to install only the libraries they are planning to use. Make sure that you do build with all feature enabled (compatible with your distribution licencing/patent policy of course) and tune your packaging to Recommend/Suggest the external libraries. Thanks Your upstream maintainers diff --git a/README b/README index f1fc011a..7d2fd9c9 100644 --- a/README +++ b/README @@ -1,46 +1,66 @@ # # Copyright (C) 2010-2021 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://trello.com/kronosnet (TODO list and activities tracking) https://goo.gl/9ZvkLS (google shared drive) https://github.com/kronosnet/knet-ansible-ci https://lists.kronosnet.org/mailman/listinfo/users https://lists.kronosnet.org/mailman/listinfo/devel https://lists.kronosnet.org/mailman/listinfo/commits 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 ----------------------- 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 +Please avoid using 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". + + +Rust Bindings +------------- + +Rust bindings for libknet and libnozzle are part of this +source tree, but are included here mainly to keep all of the +kronosnet APIs in one place and to ensure that everything is kept +up-to-date and properly tested in our CI system. + +The correct place to get the Rust crates for libknet and libnozzle +is still crates.io as it would be for other crates. These will be +updated when we issue a new release of knet. + +https://crates.io/crates/knet-bindings +https://crates.io/crates/nozzle-bindings + +Of course, if you want to try any new features in the APIs that +may have not yet been released then you can try these sources, but +please keep in touch with us via email or IRC if you do so. diff --git a/build-aux/knet_valgrind_helgrind.supp b/build-aux/knet_valgrind_helgrind.supp index 116ee4cf..d3976c29 100644 --- a/build-aux/knet_valgrind_helgrind.supp +++ b/build-aux/knet_valgrind_helgrind.supp @@ -1,43 +1,49 @@ +# Copyright (C) 2016-2021 Red Hat, Inc. All rights reserved. +# +# Author: Fabio M. Di Nitto +# +# This software licensed under GPL-2.0+ + { link enable/disable known race (safe to ignore) Helgrind:Race fun:_link_updown fun:knet_link_set_enable fun:test fun:main } { link enable/disable known race (safe to ignore) Helgrind:Race fun:_handle_heartbt_thread obj:/usr/lib64/valgrind/vgpreload_helgrind-amd64-linux.so fun:start_thread fun:clone } { helgrind glitch in parsing the heartbeat code Helgrind:Race fun:_handle_check_each fun:_handle_heartbt_thread obj:/usr/lib64/valgrind/vgpreload_helgrind-amd64-linux.so fun:start_thread fun:clone } { helgrind glitch in parsing the recv from links code Helgrind:Race fun:_parse_recv_from_links fun:_handle_recv_from_links fun:_handle_recv_from_links_thread obj:/usr/lib64/valgrind/vgpreload_helgrind-amd64-linux.so fun:start_thread fun:clone } { helgrind glitch in parsing the PMTUd code Helgrind:Race fun:_handle_pmtud_link_thread obj:/usr/lib64/valgrind/vgpreload_helgrind-amd64-linux.so fun:start_thread fun:clone } diff --git a/build-aux/knet_valgrind_memcheck.supp b/build-aux/knet_valgrind_memcheck.supp index e5789b99..107d1e43 100644 --- a/build-aux/knet_valgrind_memcheck.supp +++ b/build-aux/knet_valgrind_memcheck.supp @@ -1,723 +1,771 @@ +# Copyright (C) 2016-2021 Red Hat, Inc. All rights reserved. +# +# Author: Fabio M. Di Nitto +# +# This software licensed under GPL-2.0+ + { lzma internals (spotted on Debian 9 and Ubuntu 18.04 LTS x86-64) Memcheck:Cond obj:/lib/x86_64-linux-gnu/liblzma.so.5.2.2 obj:/lib/x86_64-linux-gnu/liblzma.so.5.2.2 obj:/lib/x86_64-linux-gnu/liblzma.so.5.2.2 obj:/lib/x86_64-linux-gnu/liblzma.so.5.2.2 fun:lzma_block_buffer_encode fun:lzma_stream_buffer_encode fun:lzma_easy_buffer_encode fun:lzma_compress fun:compress_lib_test fun:compress_cfg fun:knet_handle_compress fun:test } { lzma internals (spotted on Ubuntu 18.04 LTS i386) Memcheck:Cond obj:/lib/i386-linux-gnu/liblzma.so.5.2.2 obj:/lib/i386-linux-gnu/liblzma.so.5.2.2 obj:/lib/i386-linux-gnu/liblzma.so.5.2.2 obj:/lib/i386-linux-gnu/liblzma.so.5.2.2 obj:/lib/i386-linux-gnu/liblzma.so.5.2.2 obj:/lib/i386-linux-gnu/liblzma.so.5.2.2 fun:lzma_stream_buffer_encode fun:lzma_easy_buffer_encode fun:lzma_compress fun:compress_lib_test fun:compress_cfg fun:knet_handle_compress } { openssl internals (spotted on OpenSUSE 15) Memcheck:Cond fun:__memcmp_sse4_1 obj:/usr/lib64/libcrypto.so.1.1 fun:FIPS_selftest obj:/usr/lib64/libcrypto.so.1.1 fun:FIPS_mode_set obj:/usr/lib64/libcrypto.so.1.1 fun:call_init.part.0 fun:_dl_init fun:dl_open_worker fun:_dl_catch_error fun:_dl_open fun:dlopen_doit } { openssl internals (spotted on OpenSUSE Tumbleweed) Memcheck:Cond obj:/usr/lib64/libcrypto.so.1.1 fun:RAND_DRBG_generate obj:/usr/lib64/libcrypto.so.1.1 fun:RAND_DRBG_instantiate obj:/usr/lib64/libcrypto.so.1.1 fun:RAND_DRBG_get0_public obj:/usr/lib64/libcrypto.so.1.1 fun:encrypt_openssl.isra.0 fun:opensslcrypto_encrypt_and_signv fun:opensslcrypto_encrypt_and_sign fun:_handle_check_each fun:_send_pings fun:_handle_heartbt_thread fun:start_thread } { openssl internals (spotted on Ubuntu Devel x86-64 - 2019-10-30) Memcheck:Cond obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:RAND_DRBG_generate obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:RAND_DRBG_instantiate obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:RAND_DRBG_get0_public obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:encrypt_openssl.isra.0 fun:opensslcrypto_encrypt_and_signv fun:opensslcrypto_encrypt_and_sign fun:_handle_check_each fun:_send_pings fun:_handle_heartbt_thread fun:start_thread } { openssl internals (spotted on OpenSUSE Tumbleweed) Memcheck:Cond obj:/usr/lib64/libcrypto.so.1.1 obj:/usr/lib64/libcrypto.so.1.1 fun:RAND_DRBG_generate obj:/usr/lib64/libcrypto.so.1.1 fun:RAND_DRBG_instantiate obj:/usr/lib64/libcrypto.so.1.1 fun:RAND_DRBG_get0_public obj:/usr/lib64/libcrypto.so.1.1 fun:encrypt_openssl.isra.0 fun:opensslcrypto_encrypt_and_signv fun:opensslcrypto_encrypt_and_sign fun:_handle_check_each fun:_send_pings fun:_handle_heartbt_thread } { openssl internals (spotted on Ubuntu Devel x86-64 - 2019-10-30) Memcheck:Cond obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:RAND_DRBG_generate obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:RAND_DRBG_instantiate obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:RAND_DRBG_get0_public obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:encrypt_openssl.isra.0 fun:opensslcrypto_encrypt_and_signv fun:opensslcrypto_encrypt_and_sign fun:_handle_check_each fun:_send_pings fun:_handle_heartbt_thread } { openssl internals (spotted on OpenSUSE Tumbleweed) Memcheck:Cond obj:/usr/lib64/libcrypto.so.1.1 obj:/usr/lib64/libcrypto.so.1.1 fun:RAND_DRBG_generate fun:RAND_DRBG_bytes fun:encrypt_openssl.isra.0 fun:opensslcrypto_encrypt_and_signv fun:opensslcrypto_encrypt_and_sign fun:_handle_check_each fun:_send_pings fun:_handle_heartbt_thread fun:start_thread fun:clone } { openssl internals (spotted on Ubuntu Devel x86-64 - 2019-10-30) Memcheck:Cond obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:RAND_DRBG_generate fun:RAND_DRBG_bytes fun:encrypt_openssl.isra.0 fun:opensslcrypto_encrypt_and_signv fun:opensslcrypto_encrypt_and_sign fun:_handle_check_each fun:_send_pings fun:_handle_heartbt_thread fun:start_thread fun:clone } { openssl internals (spotted on OpenSUSE Tumbleweed) Memcheck:Cond obj:/usr/lib64/libcrypto.so.1.1 fun:RAND_DRBG_generate fun:RAND_DRBG_bytes fun:encrypt_openssl.isra.0 fun:opensslcrypto_encrypt_and_signv fun:opensslcrypto_encrypt_and_sign fun:_handle_check_each fun:_send_pings fun:_handle_heartbt_thread fun:start_thread fun:clone } { openssl internals (spotted on Ubuntu Devel x86-64 - 2019-10-30) Memcheck:Cond obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:RAND_DRBG_generate fun:RAND_DRBG_bytes fun:encrypt_openssl.isra.0 fun:opensslcrypto_encrypt_and_signv fun:opensslcrypto_encrypt_and_sign fun:_handle_check_each fun:_send_pings fun:_handle_heartbt_thread fun:start_thread fun:clone } { openssl internals (spotted on OpenSUSE 15) Memcheck:Cond obj:/usr/lib64/libcrypto.so.1.1 fun:FIPS_mode_set obj:/usr/lib64/libcrypto.so.1.1 fun:call_init.part.0 fun:_dl_init fun:dl_open_worker fun:_dl_catch_error fun:_dl_open fun:dlopen_doit fun:_dl_catch_error fun:_dlerror_run fun:dlopen@@GLIBC_2.2.5 } { openssl uninitialised byte(s) (spotted on OpenSUSE Tumbleweed and Ubuntu Devel x86-64 - 2019-10-30) Memcheck:Param socketcall.sendto(msg) fun:sendto fun:_handle_check_each fun:_send_pings fun:_handle_heartbt_thread fun:start_thread fun:clone } { openssl uninitialised byte(s) (spotted on OpenSUSE Tumbleweed and Ubuntu Devel x86-64 - 2019-10-30) Memcheck:Param socketcall.sendto(msg) fun:sendto fun:_parse_recv_from_links fun:_handle_recv_from_links fun:_handle_recv_from_links_thread fun:start_thread fun:clone } { openssl uninitialised byte(s) (spotted on OpenSUSE Tumbleweed and Ubuntu Devel x86-64 - 2019-10-30) Memcheck:Param socketcall.sendto(msg) fun:sendto fun:_handle_check_link_pmtud fun:_handle_check_pmtud fun:_handle_pmtud_link_thread fun:start_thread fun:clone } { openssl uninitialised byte(s) (spotted on OpenSUSE Tumbleweed) Memcheck:Param sendmsg(msg.msg_iov[0]) fun:sendmsg fun:_sendmmsg fun:_dispatch_to_links fun:_parse_recv_from_sock fun:_handle_send_to_links fun:_handle_send_to_links_thread fun:start_thread fun:clone } { openssl internals (spotted on Ubuntu Devel x86-64 - 2019-10-30) Memcheck:Param sendmsg(msg.msg_iov[0]) fun:__libc_sendmsg fun:sendmsg fun:_sendmmsg fun:_dispatch_to_links fun:_parse_recv_from_sock fun:_handle_send_to_links fun:_handle_send_to_links_thread fun:start_thread fun:clone } { openssl internals (spotted on Ubuntu Devel x86-64 - 2020-07-10) Memcheck:Cond obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:RAND_DRBG_generate fun:RAND_DRBG_bytes fun:encrypt_openssl fun:opensslcrypto_encrypt_and_signv fun:opensslcrypto_encrypt_and_sign fun:_handle_check_each fun:_send_pings fun:_handle_heartbt_thread fun:start_thread fun:clone } { openssl internals (spotted on Ubuntu Devel x86-64 - 2020-07-10) Memcheck:Cond obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:RAND_DRBG_generate obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:RAND_DRBG_instantiate obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:RAND_DRBG_get0_public obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:encrypt_openssl fun:opensslcrypto_encrypt_and_signv fun:opensslcrypto_encrypt_and_sign fun:_handle_check_each fun:_send_pings fun:_handle_heartbt_thread } { openssl internals (spotted on Ubuntu Devel x86-64 - 2020-07-10) Memcheck:Cond obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:RAND_DRBG_generate obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:RAND_DRBG_instantiate obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:RAND_DRBG_get0_public obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:encrypt_openssl fun:opensslcrypto_encrypt_and_signv fun:opensslcrypto_encrypt_and_sign fun:_handle_check_each fun:_send_pings fun:_handle_heartbt_thread } { openssl internals (spotted on Ubuntu Devel x86-64 - 2020-07-10) Memcheck:Cond obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:RAND_DRBG_generate fun:RAND_DRBG_bytes fun:encrypt_openssl fun:opensslcrypto_encrypt_and_signv fun:opensslcrypto_encrypt_and_sign fun:_handle_check_each fun:_send_pings fun:_handle_heartbt_thread fun:start_thread fun:clone } { nss internal leak (3.41) non recurring (spotted on f29) Memcheck:Leak match-leak-kinds: definite fun:malloc obj:* obj:* obj:* obj:* obj:* obj:* obj:* obj:* obj:* obj:* obj:/usr/lib64/libnss3.so } { nss internal leak (3.41) non recurring Memcheck:Leak match-leak-kinds: definite fun:calloc obj:* obj:* obj:* obj:* obj:* fun:init_nss fun:nsscrypto_init fun:crypto_init fun:knet_handle_crypto_set_config fun:test fun:main } { nss internal leak (3.41) non recurring Memcheck:Leak match-leak-kinds: definite fun:malloc obj:* obj:* obj:* obj:* obj:* fun:init_nss fun:nsscrypto_init fun:crypto_init fun:knet_handle_crypto_set_config fun:test fun:main } { nss internal leak (3.55) non recurring (spotted on f34) Memcheck:Leak match-leak-kinds: definite fun:malloc fun:realpath@@GLIBC_2.3 obj:* obj:* obj:* obj:/usr/lib64/libnss3.so fun:SECMOD_LoadModule fun:SECMOD_LoadModule obj:/usr/lib64/libnss3.so fun:NSS_NoDB_Init fun:init_nss fun:nsscrypto_init fun:crypto_init fun:_knet_handle_crypto_set_config } { openssl uncoditional jump (spotted on Ubuntu devel 10092020) Memcheck:Cond obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:RAND_DRBG_generate obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:RAND_DRBG_instantiate obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:RAND_DRBG_get0_public obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:encrypt_openssl.isra.0 fun:opensslcrypto_encrypt_and_signv fun:opensslcrypto_encrypt_and_sign fun:send_ping fun:_send_pings fun:_handle_heartbt_thread fun:start_thread } { openssl uncoditional jump (spotted on Ubuntu devel 10092020) Memcheck:Cond obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:RAND_DRBG_generate obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:RAND_DRBG_instantiate obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:RAND_DRBG_get0_public obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:encrypt_openssl.isra.0 fun:opensslcrypto_encrypt_and_signv fun:opensslcrypto_encrypt_and_sign fun:send_ping fun:_send_pings fun:_handle_heartbt_thread } { openssl uncoditional jump (spotted on Ubuntu devel 10092020) Memcheck:Cond obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:RAND_DRBG_generate fun:RAND_DRBG_bytes fun:encrypt_openssl.isra.0 fun:opensslcrypto_encrypt_and_signv fun:opensslcrypto_encrypt_and_sign fun:send_ping fun:_send_pings fun:_handle_heartbt_thread fun:start_thread fun:clone } { openssl uncoditional jump (spotted on Ubuntu devel 10092020) Memcheck:Cond obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:RAND_DRBG_generate fun:RAND_DRBG_bytes fun:encrypt_openssl.isra.0 fun:opensslcrypto_encrypt_and_signv fun:opensslcrypto_encrypt_and_sign fun:send_ping fun:_send_pings fun:_handle_heartbt_thread fun:start_thread fun:clone } { ubuntu-devel new toolchain is not stable yet (spotted on Ubuntu devel 10092020) Memcheck:Param socketcall.sendto(msg) fun:sendto fun:send_ping fun:_send_pings fun:_handle_heartbt_thread fun:start_thread fun:clone } { ubuntu-devel new toolchain is not stable yet (spotted on Ubuntu devel 10092020) Memcheck:Param socketcall.sendto(msg) fun:sendto fun:send_pong fun:process_ping fun:_parse_recv_from_links fun:_handle_recv_from_links fun:_handle_recv_from_links_thread fun:start_thread fun:clone } { ubuntu-devel new toolchain is not stable yet (spotted on Ubuntu devel 10092020) Memcheck:Param socketcall.sendto(msg) fun:sendto fun:send_pmtud_reply fun:process_pmtud fun:_parse_recv_from_links fun:_handle_recv_from_links fun:_handle_recv_from_links_thread fun:start_thread fun:clone } { ubuntu-devel new toolchain is not stable yet (spotted on Ubuntu devel 10092020) Memcheck:Param sendmsg(msg.msg_iov[0]) fun:__libc_sendmsg fun:sendmsg fun:_sendmmsg fun:_dispatch_to_links fun:_prep_and_send_msgs fun:_parse_recv_from_sock fun:_handle_send_to_links fun:_handle_send_to_links_thread fun:start_thread fun:clone } { openssl uncoditional jump (clang) (spotted on Ubuntu devel 10092020) Memcheck:Cond obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:RAND_DRBG_generate obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:RAND_DRBG_instantiate obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:RAND_DRBG_get0_public obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:encrypt_openssl fun:opensslcrypto_encrypt_and_signv fun:opensslcrypto_encrypt_and_sign fun:send_ping fun:_send_pings fun:_handle_heartbt_thread fun:start_thread } { openssl uncoditional jump (clang) (spotted on Ubuntu devel 10092020) Memcheck:Cond obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:RAND_DRBG_generate obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:RAND_DRBG_instantiate obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:RAND_DRBG_get0_public obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:encrypt_openssl fun:opensslcrypto_encrypt_and_signv fun:opensslcrypto_encrypt_and_sign fun:send_ping fun:_send_pings fun:_handle_heartbt_thread } { openssl uncoditional jump (clang) (spotted on Ubuntu devel 10092020) Memcheck:Cond obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:RAND_DRBG_generate fun:RAND_DRBG_bytes fun:encrypt_openssl fun:opensslcrypto_encrypt_and_signv fun:opensslcrypto_encrypt_and_sign fun:send_ping fun:_send_pings fun:_handle_heartbt_thread fun:start_thread fun:clone } { openssl uncoditional jump (clang) (spotted on Ubuntu devel 10092020) Memcheck:Cond obj:/usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 fun:RAND_DRBG_generate fun:RAND_DRBG_bytes fun:encrypt_openssl fun:opensslcrypto_encrypt_and_signv fun:opensslcrypto_encrypt_and_sign fun:send_ping fun:_send_pings fun:_handle_heartbt_thread fun:start_thread fun:clone } { nss internal leak (3.59) non recurring (spotted on f34) Memcheck:Leak match-leak-kinds: definite fun:malloc fun:realpath@@GLIBC_2.3 obj:* obj:* obj:* obj:* obj:/usr/lib64/libnss3.so fun:SECMOD_LoadModule fun:SECMOD_LoadModule obj:/usr/lib64/libnss3.so fun:NSS_NoDB_Init fun:init_nss fun:nsscrypto_init } { nss internal leak (3.59) non recurring (spotted on f34) Memcheck:Leak match-leak-kinds: definite fun:malloc fun:realpath@@GLIBC_2.3 obj:* obj:* obj:* obj:* obj:* obj:* obj:* obj:* obj:* fun:init_nss fun:nsscrypto_init } { libgcrypt internal conditinal (debian 10) Memcheck:Cond obj:/usr/lib/x86_64-linux-gnu/libgcrypt.so.20.2.4 obj:/usr/lib/x86_64-linux-gnu/libgcrypt.so.20.2.4 obj:/usr/lib/x86_64-linux-gnu/libgcrypt.so.20.2.4 obj:/usr/lib/x86_64-linux-gnu/libgcrypt.so.20.2.4 obj:/usr/lib/x86_64-linux-gnu/libgcrypt.so.20.2.4 obj:/usr/lib/x86_64-linux-gnu/libgcrypt.so.20.2.4 fun:encrypt_gcrypt.isra.0 fun:gcryptcrypto_encrypt_and_signv fun:gcryptcrypto_encrypt_and_sign fun:send_ping fun:_send_pings fun:_handle_heartbt_thread fun:start_thread fun:clone } { libgcrypt internal conditinal (debian 10 - clang) Memcheck:Cond obj:/usr/lib/x86_64-linux-gnu/libgcrypt.so.20.2.4 obj:/usr/lib/x86_64-linux-gnu/libgcrypt.so.20.2.4 obj:/usr/lib/x86_64-linux-gnu/libgcrypt.so.20.2.4 obj:/usr/lib/x86_64-linux-gnu/libgcrypt.so.20.2.4 obj:/usr/lib/x86_64-linux-gnu/libgcrypt.so.20.2.4 obj:/usr/lib/x86_64-linux-gnu/libgcrypt.so.20.2.4 fun:encrypt_gcrypt fun:gcryptcrypto_encrypt_and_signv fun:gcryptcrypto_encrypt_and_sign fun:send_ping fun:_send_pings fun:_handle_heartbt_thread fun:start_thread fun:clone } { libgcrypt internal conditinal (opensuse 15) Memcheck:Cond obj:/usr/lib64/libgcrypt.so.20.2.2 obj:/usr/lib64/libgcrypt.so.20.2.2 obj:/usr/lib64/libgcrypt.so.20.2.2 obj:/usr/lib64/libgcrypt.so.20.2.2 obj:/usr/lib64/libgcrypt.so.20.2.2 obj:/usr/lib64/libgcrypt.so.20.2.2 fun:encrypt_gcrypt.isra.0 fun:gcryptcrypto_encrypt_and_signv fun:gcryptcrypto_encrypt_and_sign fun:send_ping fun:_send_pings fun:_handle_heartbt_thread fun:start_thread fun:clone } { libgcrypt internal conditinal (opensuse 15 - clang) Memcheck:Cond obj:/usr/lib64/libgcrypt.so.20.2.2 obj:/usr/lib64/libgcrypt.so.20.2.2 obj:/usr/lib64/libgcrypt.so.20.2.2 obj:/usr/lib64/libgcrypt.so.20.2.2 obj:/usr/lib64/libgcrypt.so.20.2.2 obj:/usr/lib64/libgcrypt.so.20.2.2 fun:encrypt_gcrypt fun:gcryptcrypto_encrypt_and_signv fun:gcryptcrypto_encrypt_and_sign fun:send_ping fun:_send_pings fun:_handle_heartbt_thread fun:start_thread fun:clone } { libgcrypt internal leak (fedora and others) Memcheck:Leak match-leak-kinds: definite fun:malloc obj:* obj:* obj:* obj:* obj:* fun:gcryptcrypto_init fun:crypto_init fun:knet_handle_crypto_set_config fun:test fun:main } { libgcrypt internal leak (fedora and others) Memcheck:Leak match-leak-kinds: definite fun:malloc obj:* obj:* obj:* obj:* obj:* fun:encrypt_gcrypt fun:gcryptcrypto_encrypt_and_signv fun:gcryptcrypto_encrypt_and_sign fun:send_ping fun:_send_pings fun:_handle_heartbt_thread fun:start_thread fun:clone } +{ + For knet-test Rust API checker + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + ... + fun:_ZN* +} +{ + For knet-test Rust API checker + Memcheck:Leak + match-leak-kinds: possible + fun:calloc + fun:UnknownInlinedFun + fun:allocate_dtv + fun:_dl_allocate_tls + fun:pthread_create + ... + fun:_ZN* +} +{ + For knet-test Rust API checker + Memcheck:Leak + match-leak-kinds: possible + fun:calloc + fun:UnknownInlinedFun + fun:allocate_dtv + fun:_dl_allocate_tls + fun:pthread_create + ... + fun:_ZN* + fun:main +} +{ + For knet-test Rust API checker + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + ... + fun:_ZN* + fun:main +} diff --git a/build-aux/release.mk b/build-aux/release.mk index 81c7ff8f..245c0431 100644 --- a/build-aux/release.mk +++ b/build-aux/release.mk @@ -1,110 +1,112 @@ # # Copyright (C) 2012-2021 Red Hat, Inc. All rights reserved. # # Author: Fabio M. Di Nitto # # This software licensed under GPL-2.0+ # # to build official release tarballs, handle tagging and publish. # example: # make -f build-aux/release.mk all version=0.9 release=yes publish gpgsignkey = 1F22889A project = kronosnet deliverables = $(project)-$(version).sha256 \ $(project)-$(version).tar.bz2 \ $(project)-$(version).tar.gz \ $(project)-$(version).tar.xz .PHONY: all all: tag tarballs sign # first/last skipped per release/gpgsignkey respectively .PHONY: checks checks: ifeq (,$(version)) @echo ERROR: need to define version= @exit 1 endif @if [ ! -d .git ]; then \ echo This script needs to be executed from top level cluster git tree; \ exit 1; \ fi .PHONY: setup setup: checks ./autogen.sh - ./configure + ./configure --enable-rust-bindings make maintainer-clean .PHONY: tag tag: setup ./tag-$(version) tag-$(version): ifeq (,$(release)) @echo Building test release $(version), no tagging echo '$(version)' > .tarball-version else # following will be captured by git-version-gen automatically git tag -a -m "v$(version) release" v$(version) HEAD @touch $@ endif .PHONY: tarballs tarballs: tag ./autogen.sh - ./configure - #make distcheck (disabled.. needs root) - make dist + ./configure --enable-rust-bindings + make distcheck DISTCHECK_CONFIGURE_FLAGS="--enable-rust-bindings" + make all -j .PHONY: sha256 sha256: $(project)-$(version).sha256 # NOTE: dependency backtrack may fail trying to sign missing tarballs otherwise # (actually, only when signing tarballs directly, but doesn't hurt anyway) $(deliverables): tarballs $(project)-$(version).sha256: # checksum anything from deliverables except for in-prep checksums file sha256sum $(deliverables:$@=) | sort -k2 > $@ .PHONY: sign ifeq (,$(gpgsignkey)) sign: $(deliverables) @echo No GPG signing key defined else sign: $(deliverables:=.asc) endif # NOTE: cannot sign multiple files at once $(project)-$(version).%.asc: $(project)-$(version).% gpg --default-key "$(gpgsignkey)" \ --detach-sign \ --armor \ $< .PHONY: publish publish: ifeq (,$(release)) @echo Building test release $(version), no publishing! else @echo : pushing tags @git push --follow-tags origin @echo : publishing files @scp $(deliverables) $(deliverables:=.asc) www.kronosnet.org:kronosnet/releases/. + $(MAKE) -C libnozzle/bindings/rust crates-publish + $(MAKE) -C libknet/bindings/rust crates-publish endif .PHONY: clean clean: rm -rf $(project)-* tag-* .tarball-version diff --git a/build-aux/rust-regen.sh b/build-aux/rust-regen.sh new file mode 100755 index 00000000..326a6411 --- /dev/null +++ b/build-aux/rust-regen.sh @@ -0,0 +1,29 @@ +#!/bin/sh +# +# Copyright (C) 2021 Red Hat, Inc. All rights reserved. +# +# Authors: Christine Caulfield +# Fabio M. Di Nitto +# +# This software licensed under GPL-2.0+ +# +# +# Regerate the FFI bindings in src/sys from the current headers +# + +srcheader="$1" +dstrs="$2" +filter="$3" + +bindgen \ + --size_t-is-usize \ + --no-recursive-whitelist \ + --no-prepend-enum-name \ + --no-layout-tests \ + --no-doc-comments \ + --generate functions,types,vars \ + --fit-macro-constant-types \ + --whitelist-var=$3.* \ + --whitelist-type=.* \ + --whitelist-function=*. \ + $srcheader -o $dstrs diff --git a/build-aux/rust.mk b/build-aux/rust.mk new file mode 100644 index 00000000..8e3631c5 --- /dev/null +++ b/build-aux/rust.mk @@ -0,0 +1,106 @@ +# +# Copyright (C) 2021 Red Hat, Inc. All rights reserved. +# +# Author: Fabio M. Di Nitto +# +# This software licensed under GPL-2.0+ +# + +RUST_COMMON = \ + build.rs.in + +RUST_SRCS = $(RUST_SHIP_SRCS) $(RUST_BUILT_SRCS) + +%.rlib: $(RUST_SRCS) Cargo.toml build.rs + PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) $(CARGO) build $(RUST_FLAGS) + +%-test: $(RUST_SRCS) Cargo.toml build.rs + PKG_CONFIG_PATH=$(PKG_CONFIG_PATH) $(CARGO) build $(RUST_FLAGS) + +build.rs: build.rs.in + rm -f $@ $@-t + cat $^ | sed \ + -e 's#@ABSTOPLEVELSRC@#$(abs_top_srcdir)#g' \ + -e 's#@ABSTOPLEVELBUILD@#$(abs_top_builddir)#g' \ + > $@-t + chmod a-w $@-t + mv $@-t $@ + rm -f $@-t + +cargo-tree-prep: + if [ "${abs_builddir}" != "${abs_srcdir}" ]; then \ + echo "Generating builddir out-of-tree rust symlinks"; \ + src_realpath=$(shell realpath ${abs_srcdir}); \ + for i in `find "$$src_realpath/" -type d | \ + grep -v "${abs_builddir}" | \ + sed -e 's#^'$$src_realpath'/##g'`; do \ + $(MKDIR_P) ${abs_builddir}/$${i}; \ + done; \ + find "$$src_realpath/" -type f | { while read src; do \ + process=no; \ + copy=no; \ + case $$src in \ + ${abs_builddir}*) \ + ;; \ + *Makefile.*|*.in) \ + ;; \ + *) \ + process=yes; \ + ;; \ + esac ; \ + dst=`echo $$src | sed -e 's#^'$$src_realpath'/##g'`; \ + if [ $${process} == yes ]; then \ + rm -f ${abs_builddir}/$$dst; \ + $(LN_S) $$src ${abs_builddir}/$$dst; \ + fi; \ + if [ $${copy} == yes ]; then \ + rm -f ${abs_builddir}/$$dst; \ + cp $$src ${abs_builddir}/$$dst; \ + chmod u+w ${abs_builddir}/$$dst; \ + fi; \ + done; }; \ + fi + +cargo-clean: + -$(CARGO) clean + rm -rf Cargo.lock $(RUST_BUILT_SRCS) build.rs target/ + if [ "${abs_builddir}" != "${abs_srcdir}" ]; then \ + echo "Cleaning out-of-tree rust symlinks" ; \ + find "${abs_builddir}/" -type l -delete; \ + find "${abs_builddir}/" -type d -empty -delete; \ + fi + +clippy-check: + $(CARGO) clippy --verbose --all-features -- -D warnings + +doc-check: + $(CARGO) doc --verbose --all-features + +publish-check: + if [ -f "${abs_srcdir}/README" ]; then \ + $(CARGO) publish --dry-run; \ + fi + +crates-publish: + if [ -f "${abs_srcdir}/README" ]; then \ + bindingname=`cat Cargo.toml | grep ^name | sed -e 's#.*= ##g' -e 's#"##g'` && \ + cratesver=`cargo search $$bindingname | grep "^$$bindingname " | sed -e 's#.*= ##g' -e 's#"##g' -e 's/\+.*//g'` && \ + testver=`echo $(localver) | sed -e 's/\+.*//g'` && \ + if [ "$$cratesver" != "$$testver" ]; then \ + $(CARGO) publish; \ + fi; \ + fi + +crates-check: + if [ -f "${abs_srcdir}/README" ]; then \ + bindingname=`cat Cargo.toml | grep ^name | sed -e 's#.*= ##g' -e 's#"##g'` && \ + cratesver=`cargo search $$bindingname | grep "^$$bindingname " | sed -e 's#.*= ##g' -e 's#"##g' -e 's/\+.*//g'` && \ + testver=`echo $(localver) | sed -e 's/\+.*//g'` && \ + if [ "$$cratesver" != "$$testver" ]; then \ + echo "!!!!! WARNING !!!!!"; \ + echo "!!!!! WARNING: $$bindingname local version ($$testver) is higher than the current published one on crates.io ($$cratesver)"; \ + echo "!!!!! WARNING !!!!!"; \ + fi; \ + fi + +check-local: clippy-check doc-check crates-check publish-check diff --git a/build-aux/update-copyright.sh b/build-aux/update-copyright.sh index 62c449c8..9bb9ff14 100755 --- a/build-aux/update-copyright.sh +++ b/build-aux/update-copyright.sh @@ -1,29 +1,29 @@ #!/bin/sh # -# Copyright (C) 2017-2019 Red Hat, Inc. All rights reserved. +# Copyright (C) 2017-2021 Red Hat, Inc. All rights reserved. # # Author: Fabio M. Di Nitto # # This software licensed under GPL-2.0+ # # script to update copyright dates across the tree enddate=$(date +%Y) -input=$(grep -ril -e "Copyright.*Red Hat" |grep -v .swp |grep -v update-copyright |grep -v doxyxml.c) +input=$(grep -ril -e "Copyright.*Red Hat" |grep -v .swp |grep -v update-copyright) for i in $input; do startdate=$(git log --follow "$i" | grep ^Date: | tail -n 1 | awk '{print $6}') if [ "$startdate" != "$enddate" ]; then sed -i -e 's#Copyright (C).*Red Hat#Copyright (C) '$startdate'-'$enddate' Red Hat#g' $i else sed -i -e 's#Copyright (C).*Red Hat#Copyright (C) '$startdate' Red Hat#g' $i fi done input=$(find . -type f |grep -v ".git") for i in $input; do if [ -z "$(grep -i "Copyright" $i)" ]; then echo "WARNING: $i appears to be missing Copyright information" fi done diff --git a/configure.ac b/configure.ac index e429dccd..7a048b85 100644 --- a/configure.ac +++ b/configure.ac @@ -1,421 +1,498 @@ # # Copyright (C) 2010-2021 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]) # --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"])]) AX_CHECK_LINK_FLAG([-Wl,--as-needed], [AM_LDFLAGS="$AM_LDFLAGS -Wl,--as-needed"]) AC_SUBST([AM_LDFLAGS]) 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 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([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])]) # use gcry_mac_open to detect if libgcrypt is new enough KNET_OPTION_DEFINES([gcrypt],[crypto],[ PKG_CHECK_MODULES([gcrypt], [libgcrypt >= 1.8.0],, [AC_CHECK_HEADERS([gcrypt.h], [AC_CHECK_LIB([gcrypt], [gcry_mac_open], [AC_SUBST([gcrypt_LIBS], ["-lgcrypt -ldl -lgpg-error"])])], [AC_MSG_ERROR(["missing required gcrypt.h"])])]) ]) 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]) +AC_ARG_ENABLE([rust-bindings], + [AS_HELP_STRING([--enable-rust-bindings],[rust bindings support])],, + [ enable_rust_bindings="no" ]) +AM_CONDITIONAL([BUILD_RUST_BINDINGS], [test x$enable_rust_bindings = xyes]) + # 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 ]]) # OS detection 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]) ;; *) AC_MSG_ERROR([Unsupported OS? hmmmm]) ;; esac # 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"])]) 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]) # required to detect doxygen2man when libqb is installed # in non standard paths 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]) PKG_CONFIG="$saved_PKG_CONFIG" ac_cv_path_PKG_CONFIG="$saved_ac_cv_path_PKG_CONFIG" if test "x$DOXYGEN2MAN" = "xno"; then AC_MSG_ERROR(["doxygen2man command not found"]) fi AC_SUBST([DOXYGEN2MAN]) fi +# check for rust tools to build bindings +if test "x$enable_rust_bindings" = "xyes"; then + AC_PATH_PROG([CARGO], [cargo], [no]) + if test "x$CARGO" = xno; then + AC_MSG_ERROR(["cargo command not found"]) + fi + + AC_PATH_PROG([RUSTC], [rustc], [no]) + if test "x$RUSTC" = xno; then + AC_MSG_ERROR(["rustc command not found"]) + fi + + AC_PATH_PROG([RUSTDOC], [rustdoc], [no]) + if test "x$RUSTDOC" = xno; then + AC_MSG_ERROR(["rustdoc command not found"]) + fi + + AC_PATH_PROG([BINDGEN], [bindgen], [no]) + if test "x$BINDGEN" = xno; then + AC_MSG_ERROR(["bindgen command not found"]) + fi + + AC_PATH_PROG([CLIPPY], [clippy-driver], [no]) + if test "x$CLIPPY" = xno; then + AC_MSG_ERROR(["clippy-driver command not found"]) + fi + + AC_PATH_PROG([RUSTFMT], [rustfmt], [no]) + if test "x$RUSTFMT" = xno; then + AC_MSG_WARN(["rustfmt command not found (optional)"]) + fi +fi + # 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 fi +# https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html +knetcurrent="2" +knetrevision="0" +knetage="0" +# c:r:a +libknetversion="$knetcurrent:$knetrevision:$knetage" +# soname derived from c:r:a +# use $VERSION as build info https://semver.org/. build info are incremental automatically +knetalpha="-alpha1" +libknetrustver="$(($knetcurrent - $knetage)).$knetage.$knetrevision$knetalpha+$VERSION" + +nozzlecurrent="1" +nozzlerevision="0" +nozzleage="0" +libnozzleversion="$nozzlecurrent:$nozzlerevision:$nozzleage" +# nozzle is stable for now +nozzlealpha="" +libnozzlerustver="$(($nozzlecurrent - $nozzleage)).$nozzleage.$nozzlerevision$nozzlealpha+$VERSION" + +AC_SUBST([libknetversion]) +AC_SUBST([libknetrustver]) +AC_SUBST([libnozzleversion]) +AC_SUBST([libnozzlerustver]) + # local options AC_ARG_ENABLE([debug], [AS_HELP_STRING([--enable-debug],[enable debug build])]) AC_ARG_WITH([sanitizers], [AS_HELP_STRING([--with-sanitizers=...,...], [enable SANitizer build, do *NOT* use for production. Only ASAN/UBSAN/TSAN are currently supported])], [ SANITIZERS="$withval" ], [ SANITIZERS="" ]) 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" + RUST_FLAGS="" + RUST_TARGET_DIR="debug" else OPT_CFLAGS="-O3" + RUST_FLAGS="--release" + RUST_TARGET_DIR="release" fi # gdb flags if test "x${GCC}" = xyes; then GDB_FLAGS="-ggdb3" else GDB_FLAGS="-g" fi # --- ASAN/UBSAN/TSAN (see man gcc) --- # when using SANitizers, we need to pass the -fsanitize.. # to both CFLAGS and LDFLAGS. The CFLAGS/LDFLAGS must be # specified as first in the list or there will be runtime # issues (for example user has to LD_PRELOAD asan for it to work # properly). if test -n "${SANITIZERS}"; then SANITIZERS=$(echo $SANITIZERS | sed -e 's/,/ /g') for SANITIZER in $SANITIZERS; do case $SANITIZER in asan|ASAN) SANITIZERS_CFLAGS="$SANITIZERS_CFLAGS -fsanitize=address" SANITIZERS_LDFLAGS="$SANITIZERS_LDFLAGS -fsanitize=address -lasan" AC_CHECK_LIB([asan],[main],,AC_MSG_ERROR([Unable to find libasan])) ;; ubsan|UBSAN) SANITIZERS_CFLAGS="$SANITIZERS_CFLAGS -fsanitize=undefined" SANITIZERS_LDFLAGS="$SANITIZERS_LDFLAGS -fsanitize=undefined -lubsan" AC_CHECK_LIB([ubsan],[main],,AC_MSG_ERROR([Unable to find libubsan])) ;; tsan|TSAN) SANITIZERS_CFLAGS="$SANITIZERS_CFLAGS -fsanitize=thread" SANITIZERS_LDFLAGS="$SANITIZERS_LDFLAGS -fsanitize=thread -ltsan" AC_CHECK_LIB([tsan],[main],,AC_MSG_ERROR([Unable to find libtsan])) ;; esac done fi DEFAULT_CFLAGS="-Werror -Wall -Wextra" # manual overrides # generates too much noise for stub APIs UNWANTED_CFLAGS="-Wno-unused-parameter" AC_SUBST([AM_CFLAGS],["$SANITIZERS_CFLAGS $OPT_CFLAGS $GDB_FLAGS $DEFAULT_CFLAGS $UNWANTED_CFLAGS"]) LDFLAGS="$SANITIZERS_LDFLAGS $LDFLAGS" +AC_SUBST([RUST_FLAGS]) +AC_SUBST([RUST_TARGET_DIR]) 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 + libnozzle/bindings/Makefile + libnozzle/bindings/rust/Makefile + libnozzle/bindings/rust/Cargo.toml + libnozzle/bindings/rust/tests/Makefile + libnozzle/bindings/rust/tests/Cargo.toml libknet/Makefile libknet/libknet.pc libknet/tests/Makefile + libknet/bindings/Makefile + libknet/bindings/rust/Makefile + libknet/bindings/rust/Cargo.toml + libknet/bindings/rust/tests/Makefile + libknet/bindings/rust/tests/Cargo.toml 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 5ed96ae9..6f64d0de 100644 --- a/libknet/Makefile.am +++ b/libknet/Makefile.am @@ -1,177 +1,174 @@ # # Copyright (C) 2010-2021 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 = 2:0:0 +SUBDIRS = . tests bindings # 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 \ onwire_v1.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 \ onwire_v1.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) libknet_la_CFLAGS = $(AM_CFLAGS) $(PTHREAD_CFLAGS) \ -DPLUGINPATH="\"$(pkglibdir)\"" EXTRA_libknet_la_DEPENDENCIES = $(SYMFILE) libknet_la_LDFLAGS = $(AM_LDFLAGS) \ -Wl,--version-script=$(srcdir)/$(SYMFILE) \ - -version-info $(libversion) + -version-info $(libknetversion) libknet_la_LIBADD = $(PTHREAD_LIBS) $(dl_LIBS) $(rt_LIBS) $(m_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 if BUILD_CRYPTO_GCRYPT pkglib_LTLIBRARIES += crypto_gcrypt.la crypto_gcrypt_la_LDFLAGS = $(MODULELDFLAGS) crypto_gcrypt_la_CFLAGS = $(AM_CFLAGS) $(gcrypt_CFLAGS) crypto_gcrypt_la_LIBADD = $(gcrypt_LIBS) endif diff --git a/libknet/bindings/Makefile.am b/libknet/bindings/Makefile.am new file mode 100644 index 00000000..f7ef3bcf --- /dev/null +++ b/libknet/bindings/Makefile.am @@ -0,0 +1,17 @@ +# +# Copyright (C) 2021 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 + +SUBDIRS = . + +if BUILD_RUST_BINDINGS +SUBDIRS += rust +endif diff --git a/libknet/bindings/rust/Cargo.toml.in b/libknet/bindings/rust/Cargo.toml.in new file mode 100644 index 00000000..8b89e5d0 --- /dev/null +++ b/libknet/bindings/rust/Cargo.toml.in @@ -0,0 +1,28 @@ +# Copyright (C) 2021 Red Hat, Inc. All rights reserved. +# +# Author: Christine Caulfield +# +# This software licensed under LGPL-2.1+ + +[package] +name = "knet-bindings" +version = "@libknetrustver@" +authors = ["Christine Caulfield "] +edition = "2018" +readme = "README" +license = "LGPL-2.1+" +repository = "https://github.com/kronosnet/kronosnet" +description = "Rust bindings for Kronosnet libraries" +categories = ["api-bindings"] +keywords = ["cluster", "high-availability"] +exclude = [ + "*.in", + "Makefile*", +] + +[dependencies] +bitflags = "1.2.1" +lazy_static = "1.4.0" +os_socketaddr = "0.2.0" +libc = "0.2.93" + diff --git a/libknet/bindings/rust/Makefile.am b/libknet/bindings/rust/Makefile.am new file mode 100644 index 00000000..65913549 --- /dev/null +++ b/libknet/bindings/rust/Makefile.am @@ -0,0 +1,37 @@ +# +# Copyright (C) 2021 Red Hat, Inc. All rights reserved. +# +# Author: Christine Caulfield +# +# This software licensed under GPL-2.0+ +# + +MAINTAINERCLEANFILES = Makefile.in + +include $(top_srcdir)/build-aux/check.mk +include $(top_srcdir)/build-aux/rust.mk + +# required for make check +localver = $(libknetrustver) + +SUBDIRS = . tests + +EXTRA_DIST = \ + $(RUST_COMMON) \ + $(RUST_SHIP_SRCS) \ + README + +RUST_SHIP_SRCS = \ + src/knet_bindings.rs \ + src/lib.rs \ + src/sys/mod.rs + +RUST_BUILT_SRCS = \ + src/sys/libknet.rs + +src/sys/libknet.rs: ../../libknet.h + $(top_srcdir)/build-aux/rust-regen.sh $^ $@ KNET + +all-local: cargo-tree-prep target/$(RUST_TARGET_DIR)/knet_bindings.rlib + +clean-local: cargo-clean diff --git a/libknet/bindings/rust/README b/libknet/bindings/rust/README new file mode 100644 index 00000000..21388a5e --- /dev/null +++ b/libknet/bindings/rust/README @@ -0,0 +1,13 @@ +# Copyright (C) 2021 Red Hat, Inc. All rights reserved. +# +# Author: Christine Caulfield +# +# This software licensed under GPL-2.0+ + +This crate contains Rust bindings for the kronosnet (knet) +library libknet: https://kronosnet.org/ + +Kronosnet, often referred to as knet, is a network abstraction +layer designed for High Availability use cases, where redundancy, +security, fault tolerance and fast fail-over are the core +requirements of your application. diff --git a/libknet/bindings/rust/build.rs.in b/libknet/bindings/rust/build.rs.in new file mode 100644 index 00000000..712fcf59 --- /dev/null +++ b/libknet/bindings/rust/build.rs.in @@ -0,0 +1,11 @@ +// Copyright (c) 2021 Red Hat, Inc. +// +// All rights reserved. +// +// Author: Christine Caulfield (ccaulfi@redhat.com) +// + +fn main() { + println!("cargo:rustc-link-search=native=../../"); + println!("cargo:rustc-link-lib=knet"); +} diff --git a/libknet/bindings/rust/src/knet_bindings.rs b/libknet/bindings/rust/src/knet_bindings.rs new file mode 100644 index 00000000..0e954bda --- /dev/null +++ b/libknet/bindings/rust/src/knet_bindings.rs @@ -0,0 +1,2485 @@ +// libknet interface for Rust +// Copyright (c) 2021 Red Hat, Inc. +// +// All rights reserved. +// +// Author: Christine Caulfield (ccaulfi@redhat.com) +// +#![allow(clippy::too_many_arguments)] +#![allow(clippy::collapsible_else_if)] + +// For the code generated by bindgen +use crate::sys::libknet as ffi; + +use std::ffi::{CString, CStr}; +use std::sync::mpsc::*; +use std::ptr::{copy_nonoverlapping, null, null_mut}; +use std::sync::Mutex; +use std::collections::HashMap; +use std::io::{Result, Error, ErrorKind}; +use std::os::raw::{c_void, c_char, c_uchar, c_uint}; +use std::mem::size_of; +use std::net::SocketAddr; +use std::fmt; +use std::thread::spawn; +use std::time::{Duration, SystemTime}; +use os_socketaddr::OsSocketAddr; + +#[derive(Copy, Clone, PartialEq)] +/// The ID of a host known to knet. +pub struct HostId { + host_id: u16, +} +impl HostId { + pub fn new(id: u16) -> HostId + { + HostId{host_id: id} + } + pub fn to_u16(self: HostId) -> u16 + { + self.host_id + } +} +impl fmt::Display for HostId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f,"{}", self.host_id)?; + Ok(()) + } +} + +pub enum TxRx { + Tx = 0, + Rx = 1 +} + +impl TxRx { + pub fn new (tx_rx: u8) -> TxRx + { + match tx_rx { + 1 => TxRx::Rx, + _ => TxRx::Tx + } + } +} +impl fmt::Display for TxRx { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + TxRx::Tx => write!(f, "Tx"), + TxRx::Rx => write!(f, "Rx"), + } + } +} + +bitflags! { +/// Flags passed into [handle_new] + pub struct HandleFlags: u64 + { + const PRIVILEGED = 1; + const NONE = 0; + } +} + +bitflags! { +/// Flags passed into [link_set_config] + pub struct LinkFlags: u64 + { + const TRAFFICHIPRIO = 1; + const NONE = 0; + } +} + + +/// for passing to [handle_crypto_set_config] +pub struct CryptoConfig<'a> { + pub crypto_model: String, + pub crypto_cipher_type: String, + pub crypto_hash_type: String, + pub private_key: &'a [u8], +} + +/// for passing to [handle_compress] +pub struct CompressConfig { + pub compress_model: String, + pub compress_threshold: u32, + pub compress_level: i32, +} + +/// Return value from packet filter +pub enum FilterDecision { + Discard, + Unicast, + Multicast +} +impl FilterDecision { + pub fn to_i32(self: &FilterDecision) -> i32 + { + match self { + FilterDecision::Discard => -1, + FilterDecision::Unicast => 0, + FilterDecision::Multicast => 1, + } + } +} +impl fmt::Display for FilterDecision { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + FilterDecision::Discard => write!(f, "Discard"), + FilterDecision::Unicast => write!(f, "Unicast"), + FilterDecision::Multicast => write!(f, "Multicast"), + } + } +} + +// Used to convert a knet_handle_t into one of ours +lazy_static! { + static ref HANDLE_HASH: Mutex> = Mutex::new(HashMap::new()); +} + +fn get_errno() -> i32 +{ + match Error::last_os_error().raw_os_error() { + Some(e) => e, + None => libc::EINVAL, + } +} + + +/// Callback from [handle_enable_sock_notify] +pub type SockNotifyFn = fn(private_data: u64, + datafd: i32, + channel: i8, + txrx: TxRx, + Result<()>); + +/// Callback called when packets arrive/are sent [handle_enable_filter] +pub type FilterFn = fn(private_data: u64, + outdata: &[u8], + txrx: TxRx, + this_host_id: HostId, + src_host_id: HostId, + channel: &mut i8, + dst_host_ids: &mut Vec) -> FilterDecision; + +/// Callback called when PMTU changes, see [handle_enable_pmtud_notify] +pub type PmtudNotifyFn = fn(private_data: u64, + data_mtu: u32); + + +/// Called when the onwire version number for a node changes, see [handle_enable_onwire_ver_notify] +pub type OnwireNotifyFn = fn(private_data: u64, + onwire_min_ver: u8, + onwire_max_ver: u8, + onwire_ver: u8); + +/// Called when a host status changes, see [host_enable_status_change_notify] +pub type HostStatusChangeNotifyFn = fn(private_data: u64, + host_id: HostId, + reachable: bool, + remote: bool, + external: bool); + +/// Called when a link status changes, see [link_enable_status_change_notify] +pub type LinkStatusChangeNotifyFn = fn(private_data: u64, + host_id: HostId, + link_id: u8, + connected: bool, + remote: bool, + external: bool); + + +// Called from knet, we work out where to route it to and convert params +extern "C" fn rust_sock_notify_fn( + private_data: *mut c_void, + datafd: i32, + channel: i8, + tx_rx: u8, + error: i32, + errorno: i32) +{ + if let Some(h) = HANDLE_HASH.lock().unwrap().get(&(private_data as u64)) { + let res = if error == 0 { + Ok(()) + } else { + Err(Error::from_raw_os_error(errorno)) + }; + // Call user fn + if let Some(f) = h.sock_notify_fn { + f(h.sock_notify_private_data, + datafd, + channel, + TxRx::new(tx_rx), + res); + } + } +} + +// Called from knet, we work out where to route it to and convert params +extern "C" fn rust_filter_fn( + private_data: *mut c_void, + outdata: *const c_uchar, + outdata_len: isize, + tx_rx: u8, + this_host_id: u16, + src_host_id: u16, + channel: *mut i8, + dst_host_ids: *mut u16, + dst_host_ids_entries: *mut usize) -> i32 +{ + let mut ret : i32 = -1; + + if let Some(h) = HANDLE_HASH.lock().unwrap().get(&(private_data as u64)) { + // Is there is filter fn? + if let Some(f) = h.filter_fn { + let data : &[u8] = unsafe { + std::slice::from_raw_parts(outdata as *const u8, outdata_len as usize) + }; + let mut r_channel = unsafe {*channel}; + let mut hosts_vec = Vec::::new(); + + // Call Rust callback + ret = f(h.filter_private_data, + data, + TxRx::new(tx_rx), + HostId{host_id: this_host_id}, + HostId{host_id: src_host_id}, + &mut r_channel, + &mut hosts_vec).to_i32(); + + // Pass back mutable params dst_hosts + unsafe { + *channel = r_channel; + *dst_host_ids_entries = hosts_vec.len(); + let mut retvec = dst_host_ids; + for i in &hosts_vec { + *retvec = i.host_id; + retvec = retvec.offset(1); // next entry + } + } + } + } + ret +} + +// Called from knet, we work out where to route it to and convert params +extern "C" fn rust_pmtud_notify_fn( + private_data: *mut c_void, + data_mtu: u32) +{ + if let Some(h) = HANDLE_HASH.lock().unwrap().get(&(private_data as u64)) { + // Call user fn + if let Some(f) = h.pmtud_notify_fn { + f(h.pmtud_notify_private_data, data_mtu); + } + } +} + + +// Called from knet, we work out where to route it to and convert params +extern "C" fn rust_onwire_notify_fn( + private_data: *mut c_void, + onwire_min_ver: u8, + onwire_max_ver: u8, + onwire_ver: u8) +{ + if let Some(h) = HANDLE_HASH.lock().unwrap().get(&(private_data as u64)) { + // Call user fn + if let Some(f) = h.onwire_notify_fn { + f(h.onwire_notify_private_data, + onwire_min_ver, + onwire_max_ver, + onwire_ver); + } + } +} + +// Called from knet, we work out where to route it to and convert params +extern "C" fn rust_host_status_change_notify_fn( + private_data: *mut c_void, + host_id: u16, + reachable: u8, + remote: u8, + external: u8) +{ + if let Some(h) = HANDLE_HASH.lock().unwrap().get(&(private_data as u64)) { + // Call user fn + if let Some(f) = h.host_status_change_notify_fn { + f(h.host_status_change_notify_private_data, + HostId{host_id}, + crate::u8_to_bool(reachable), + crate::u8_to_bool(remote), + crate::u8_to_bool(external)); + } + } +} + +// Called from knet, we work out where to route it to and convert params +extern "C" fn rust_link_status_change_notify_fn( + private_data: *mut c_void, + host_id: u16, + link_id: u8, + connected: u8, + remote: u8, + external: u8) +{ + if let Some(h) = HANDLE_HASH.lock().unwrap().get(&(private_data as u64)) { + // Call user fn + if let Some(f) = h.link_status_change_notify_fn { + f(h.link_status_change_notify_private_data, + HostId{host_id}, + link_id, + crate::u8_to_bool(connected), + crate::u8_to_bool(remote), + crate::u8_to_bool(external)); + } + } +} + +// Logging thread +fn logging_thread(knet_pipe: i32, sender: Sender) +{ + let mut logbuf = ffi::knet_log_msg {msg: [0; 254], + subsystem: 0, + msglevel: 0, + knet_h: 0 as ffi::knet_handle_t}; + // Make it blocking + unsafe { libc::fcntl(knet_pipe, libc::F_SETFL, + libc::fcntl(knet_pipe, libc::F_GETFL, 0) & !libc::O_NONBLOCK)}; + + + loop { + let msglen = unsafe {libc::read(knet_pipe, &mut logbuf as *mut _ as *mut c_void, + size_of::())}; + if msglen < 1 { + unsafe { libc::close(knet_pipe); } + // EOF on pipe, handle is closed. + return; + } + if msglen == size_of::() as isize { + let rmsg = LogMsg { + msg: crate::string_from_bytes_safe(logbuf.msg.as_ptr(), 254), + subsystem: SubSystem::new(logbuf.subsystem), + level: LogLevel::new(logbuf.msglevel), + handle: Handle{knet_handle: logbuf.knet_h as u64}}; + + if let Err(e) = sender.send(rmsg) { + println!("Error sending log message: {}", e); + } + } + } +} + + +#[derive(Copy, Clone, PartialEq)] +#[repr(transparent)] +/// a handle into the knet library, returned from [handle_new] +pub struct Handle { + knet_handle: u64, +} + +// Private version of knet handle, contains all the callback data so +// we only need to access it in the calback functions, making the rest +// a bit quicker & neater +struct PrivHandle { + log_fd: i32, + sock_notify_fn: Option, + sock_notify_private_data: u64, + filter_fn: Option, + filter_private_data: u64, + pmtud_notify_fn: Option, + pmtud_notify_private_data: u64, + onwire_notify_fn: Option, + onwire_notify_private_data: u64, + host_status_change_notify_fn: Option, + host_status_change_notify_private_data: u64, + link_status_change_notify_fn: Option, + link_status_change_notify_private_data: u64, +} + +/// A knet logging message returned down the log_sender channel set in [handle_new] +pub struct LogMsg { + pub msg: String, + pub subsystem: SubSystem, + pub level: LogLevel, + pub handle: Handle, +} + +/// Initialise the knet library, returns a handle for use with the other API calls +pub fn handle_new(host_id: &HostId, + log_sender: Option>, + default_log_level: LogLevel, + flags: HandleFlags) -> Result +{ + // If a log sender was passed, make an FD & thread for knet + let log_fd = match log_sender { + Some(s) => { + let mut pipes = [0i32; 2]; + if unsafe {libc::pipe(pipes.as_mut_ptr())} != 0 { + return Err(Error::last_os_error()); + } + spawn(move || logging_thread(pipes[0], s)); + pipes[1] + }, + None => 0 + }; + + let res = unsafe { + ffi::knet_handle_new(host_id.host_id, + log_fd, + default_log_level.to_u8(), + flags.bits) + }; + + if res.is_null() { + Err(Error::last_os_error()) + } else { + let rhandle = PrivHandle{log_fd, + sock_notify_fn: None, + sock_notify_private_data: 0u64, + filter_fn: None, + filter_private_data: 0u64, + pmtud_notify_fn: None, + pmtud_notify_private_data: 0u64, + onwire_notify_fn: None, + onwire_notify_private_data: 0u64, + host_status_change_notify_fn: None, + host_status_change_notify_private_data: 0u64, + link_status_change_notify_fn: None, + link_status_change_notify_private_data: 0u64, + + }; + HANDLE_HASH.lock().unwrap().insert(res as u64, rhandle); + Ok(Handle{knet_handle: res as u64}) + } +} + +/// Finish with knet, frees the handle returned by [handle_new] +pub fn handle_free(handle: Handle) -> Result<()> +{ + let res = unsafe { + ffi::knet_handle_free(handle.knet_handle as ffi::knet_handle_t) + }; + + if res == 0 { + // Close the log fd as knet doesn't "do ownership" and this will shut down + // our logging thread. + if let Some(h) = HANDLE_HASH.lock().unwrap().get_mut(&(handle.knet_handle)) { + unsafe { + libc::close(h.log_fd); + }; + } + + HANDLE_HASH.lock().unwrap().remove(&handle.knet_handle); + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// Enable notifications of socket state changes, set callback to 'None' to disable +pub fn handle_enable_sock_notify(handle: Handle, + private_data: u64, + sock_notify_fn: Option) -> Result<()> +{ + let res = match HANDLE_HASH.lock().unwrap().get_mut(&(handle.knet_handle)) { + Some(h) => { + h.sock_notify_private_data = private_data; + h.sock_notify_fn = sock_notify_fn; + match sock_notify_fn { + Some(_f) => + unsafe { + ffi::knet_handle_enable_sock_notify(handle.knet_handle as ffi::knet_handle_t, + handle.knet_handle as *mut c_void, + Some(rust_sock_notify_fn)) + }, + None => + unsafe { + ffi::knet_handle_enable_sock_notify(handle.knet_handle as ffi::knet_handle_t, + handle.knet_handle as *mut c_void, + None) + }, + } + }, + None => return Err(Error::new(ErrorKind::Other, "Rust handle not found")), + }; + + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// Add a data FD to knet. if datafd is 0 then knet will allocate one for you. +pub fn handle_add_datafd(handle: Handle, datafd: i32, channel: i8) -> Result<(i32, i8)> +{ + let mut c_datafd = datafd; + let mut c_channel = channel; + let res = unsafe { + ffi::knet_handle_add_datafd(handle.knet_handle as ffi::knet_handle_t, + &mut c_datafd, + &mut c_channel) + }; + if res == 0 { + Ok((c_datafd, c_channel)) + } else { + Err(Error::last_os_error()) + } +} + +/// Remove a datafd from knet +pub fn handle_remove_datafd(handle: Handle, datafd: i32) -> Result<()> +{ + let res = unsafe { + ffi::knet_handle_remove_datafd(handle.knet_handle as ffi::knet_handle_t, + datafd) + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// Returns the channel associated with data fd +pub fn handle_get_channel(handle: Handle, datafd: i32) -> Result +{ + let mut c_channel = 0i8; + let res = unsafe { + ffi::knet_handle_get_channel(handle.knet_handle as ffi::knet_handle_t, + datafd, &mut c_channel) + }; + if res == 0 { + Ok(c_channel) + } else { + Err(Error::last_os_error()) + } +} + +/// Returns the data FD associated with a channel +pub fn handle_get_datafd(handle: Handle, channel: i8) -> Result +{ + let mut c_datafd = 0i32; + let res = unsafe { + ffi::knet_handle_get_datafd(handle.knet_handle as ffi::knet_handle_t, + channel, &mut c_datafd) + }; + if res == 0 { + Ok(c_datafd) + } else { + Err(Error::last_os_error()) + } +} + +/// Receive messages from knet +pub fn recv(handle: Handle, buf: &[u8], channel: i8) -> Result +{ + let res = unsafe { + ffi::knet_recv(handle.knet_handle as ffi::knet_handle_t, + buf.as_ptr() as *mut c_char, + buf.len(), + channel) + }; + if res >= 0 { + Ok(res) + } else { + if get_errno() == libc::EAGAIN { + Err(Error::new(ErrorKind::WouldBlock, "Try again")) + } else { + Err(Error::last_os_error()) + } + } +} + +/// Send messages knet +pub fn send(handle: Handle, buf: &[u8], channel: i8) -> Result +{ + let res = unsafe { + ffi::knet_send(handle.knet_handle as ffi::knet_handle_t, + buf.as_ptr() as *const c_char, + buf.len(), + channel) + }; + if res >= 0 { + Ok(res) + } else { + if get_errno() == libc::EAGAIN { + Err(Error::new(ErrorKind::WouldBlock, "Try again")) + } else { + Err(Error::last_os_error()) + } + } +} + +/// Send messages to knet and wait till they have gone +pub fn send_sync(handle: Handle, buf: &[u8], channel: i8) -> Result<()> +{ + let res = unsafe { + ffi::knet_send_sync(handle.knet_handle as ffi::knet_handle_t, + buf.as_ptr() as *const c_char, + buf.len(), + channel) + }; + if res == 0 { + Ok(()) + } else { + if get_errno() == libc::EAGAIN { + Err(Error::new(ErrorKind::WouldBlock, "Try again")) + } else { + Err(Error::last_os_error()) + } + } +} + +/// Enable the packet filter. pass 'None' as the callback to disable. +pub fn handle_enable_filter(handle: Handle, + private_data: u64, + filter_fn: Option) -> Result<()> +{ + let res = match HANDLE_HASH.lock().unwrap().get_mut(&(handle.knet_handle)) { + Some(h) => { + h.filter_private_data = private_data; + h.filter_fn = filter_fn; + match filter_fn { + Some(_f) => + unsafe { + ffi::knet_handle_enable_filter(handle.knet_handle as ffi::knet_handle_t, + handle.knet_handle as *mut c_void, + Some(rust_filter_fn)) + }, + None => + unsafe { + ffi::knet_handle_enable_filter(handle.knet_handle as ffi::knet_handle_t, + handle.knet_handle as *mut c_void, + None) + }, + } + }, + + None => return Err(Error::new(ErrorKind::Other, "Rust handle not found")), + }; + + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// Set timer resolution +pub fn handle_set_threads_timer_res(handle: Handle, timeres: u32) -> Result<()> +{ + let res = unsafe { + ffi::knet_handle_set_threads_timer_res(handle.knet_handle as ffi::knet_handle_t, timeres) + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// Get timer resolution +pub fn handle_get_threads_timer_res(handle: Handle) -> Result +{ + let mut c_timeres: u32 = 0; + let res = unsafe { + ffi::knet_handle_get_threads_timer_res(handle.knet_handle as ffi::knet_handle_t, &mut c_timeres) + }; + if res == 0 { + Ok(c_timeres) + } else { + Err(Error::last_os_error()) + } +} + + + +/// Starts traffic moving. You must call this before knet will do anything. +pub fn handle_setfwd(handle: Handle, enabled: bool) -> Result<()> +{ + let res = unsafe { + ffi::knet_handle_setfwd(handle.knet_handle as ffi::knet_handle_t, + enabled as c_uint) + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// Enable access control lists +pub fn handle_enable_access_lists(handle: Handle, enabled: bool) -> Result<()> +{ + let res = unsafe { + ffi::knet_handle_enable_access_lists(handle.knet_handle as ffi::knet_handle_t, + enabled as c_uint) + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// Set frequency that PMTUd will check for MTU changes. value in milliseconds +pub fn handle_pmtud_setfreq(handle: Handle, interval: u32) -> Result<()> +{ + let res = unsafe { + ffi::knet_handle_pmtud_setfreq(handle.knet_handle as ffi::knet_handle_t, + interval) + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// Get frequency that PMTUd will check for MTU changes. value in milliseconds +pub fn handle_pmtud_getfreq(handle: Handle) -> Result +{ + let mut c_interval = 0u32; + let res = unsafe { + ffi::knet_handle_pmtud_getfreq(handle.knet_handle as ffi::knet_handle_t, + &mut c_interval) + }; + if res == 0 { + Ok(c_interval) + } else { + Err(Error::last_os_error()) + } +} + +/// Get the current MTU +pub fn handle_pmtud_get(handle: Handle) -> Result +{ + let mut c_mtu = 0u32; + let res = unsafe { + ffi::knet_handle_pmtud_get(handle.knet_handle as ffi::knet_handle_t, + &mut c_mtu) + }; + if res == 0 { + Ok(c_mtu) + } else { + Err(Error::last_os_error()) + } +} + +/// Set the interface MTU (this should not be necessary) +pub fn handle_pmtud_set(handle: Handle, iface_mtu: u32) -> Result<()> +{ + let res = unsafe { + ffi::knet_handle_pmtud_set(handle.knet_handle as ffi::knet_handle_t, + iface_mtu) + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// Enable notification of MTU changes +pub fn handle_enable_pmtud_notify(handle: Handle, + private_data: u64, + pmtud_notify_fn: Option) -> Result<()> +{ + let res = match HANDLE_HASH.lock().unwrap().get_mut(&(handle.knet_handle)) { + Some(h) => { + h.pmtud_notify_private_data = private_data; + h.pmtud_notify_fn = pmtud_notify_fn; + match pmtud_notify_fn { + Some(_f) => + unsafe { + ffi::knet_handle_enable_pmtud_notify(handle.knet_handle as ffi::knet_handle_t, + handle.knet_handle as *mut c_void, + Some(rust_pmtud_notify_fn)) + }, + None => + unsafe { + ffi::knet_handle_enable_pmtud_notify(handle.knet_handle as ffi::knet_handle_t, + handle.knet_handle as *mut c_void, + None) + }, + } + }, + None => return Err(Error::new(ErrorKind::Other, "Rust handle not found")), + }; + + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// Configure cryptographic seetings for packets being transmitted +pub fn handle_crypto_set_config(handle: Handle, config: &CryptoConfig, config_num: u8) -> Result<()> +{ + let mut crypto_cfg = ffi::knet_handle_crypto_cfg { + crypto_model: [0; 16], + crypto_cipher_type: [0; 16], + crypto_hash_type: [0; 16], + private_key: [0; 4096], + private_key_len: 0, + }; + + if config.private_key.len() > 4096 { + return Err(Error::new(ErrorKind::Other, "key too long")); + } + + crate::string_to_bytes(&config.crypto_model, &mut crypto_cfg.crypto_model)?; + crate::string_to_bytes(&config.crypto_cipher_type, &mut crypto_cfg.crypto_cipher_type)?; + crate::string_to_bytes(&config.crypto_hash_type, &mut crypto_cfg.crypto_hash_type)?; + unsafe { + // NOTE param order is 'wrong-way round' from C + copy_nonoverlapping(config.private_key.as_ptr(), crypto_cfg.private_key.as_mut_ptr(), config.private_key.len()); + } + crypto_cfg.private_key_len = config.private_key.len() as u32; + + let res = unsafe { + ffi::knet_handle_crypto_set_config(handle.knet_handle as ffi::knet_handle_t, + &mut crypto_cfg, + config_num) + }; + if res == 0 { + Ok(()) + } else { + if res == -2 { + Err(Error::new(ErrorKind::Other, "Other cryto error")) + } else { + Err(Error::last_os_error()) + } + } +} + +/// Whether to allow or disallow clear-text traffic when crypto is enabled with [handle_crypto_rx_clear_traffic] +pub enum RxClearTraffic { + Allow = 0, + Disallow = 1, +} + +/// Enable or disable clear-text traffic when crypto is enabled +pub fn handle_crypto_rx_clear_traffic(handle: Handle, value: RxClearTraffic) -> Result<()> +{ + let c_value : u8 = + match value { + RxClearTraffic::Allow => 0, + RxClearTraffic::Disallow => 1 + }; + + let res = unsafe { + ffi::knet_handle_crypto_rx_clear_traffic(handle.knet_handle as ffi::knet_handle_t, + c_value) + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// Tell knet which crypto settings to use +pub fn handle_crypto_use_config(handle: Handle, config_num: u8) -> Result<()> +{ + let res = unsafe { + ffi::knet_handle_crypto_use_config(handle.knet_handle as ffi::knet_handle_t, + config_num) + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + + +/// Set up packet compression +pub fn handle_compress(handle: Handle, config: &CompressConfig) -> Result<()> +{ + let mut compress_cfg = ffi::knet_handle_compress_cfg { + compress_model: [0; 16], + compress_threshold : config.compress_threshold, + compress_level : config.compress_level + }; + + if config.compress_model.len() > 16 { + return Err(Error::new(ErrorKind::Other, "key too long")); + } + + crate::string_to_bytes(&config.compress_model, &mut compress_cfg.compress_model)?; + + let res = unsafe { + ffi::knet_handle_compress(handle.knet_handle as ffi::knet_handle_t, + &mut compress_cfg) + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// Stats for the knet handle +pub type HandleStats = ffi::knet_handle_stats; + +impl HandleStats { + pub fn new() -> HandleStats + { + HandleStats { + size: 0, + tx_uncompressed_packets: 0, + tx_compressed_packets: 0, + tx_compressed_original_bytes: 0, + tx_compressed_size_bytes: 0, + tx_compress_time_ave: 0, + tx_compress_time_min: 0, + tx_compress_time_max: 0, + tx_failed_to_compress: 0, + tx_unable_to_compress: 0, + rx_compressed_packets: 0, + rx_compressed_original_bytes: 0, + rx_compressed_size_bytes: 0, + rx_compress_time_ave: 0, + rx_compress_time_min: 0, + rx_compress_time_max: 0, + rx_failed_to_decompress: 0, + tx_crypt_packets: 0, + tx_crypt_byte_overhead: 0, + tx_crypt_time_ave: 0, + tx_crypt_time_min: 0, + tx_crypt_time_max: 0, + rx_crypt_packets: 0, + rx_crypt_time_ave: 0, + rx_crypt_time_min: 0, + rx_crypt_time_max: 0, + } + } +} +impl Default for ffi::knet_handle_stats { + fn default() -> Self { + ffi::knet_handle_stats::new() + } +} + +impl fmt::Display for HandleStats { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}, ", self.tx_uncompressed_packets)?; + write!(f, "{}, ", self.tx_compressed_packets)?; + write!(f, "{}, ", self.tx_compressed_original_bytes)?; + write!(f, "{}, ", self.tx_compressed_size_bytes)?; + write!(f, "{}, ", self.tx_compress_time_ave)?; + write!(f, "{}, ", self.tx_compress_time_min)?; + write!(f, "{}, ", self.tx_compress_time_max)?; + write!(f, "{}, ", self.tx_failed_to_compress)?; + write!(f, "{}, ", self.tx_unable_to_compress)?; + write!(f, "{}, ", self.rx_compressed_packets)?; + write!(f, "{}, ", self.rx_compressed_original_bytes)?; + write!(f, "{}, ", self.rx_compressed_size_bytes)?; + write!(f, "{}, ", self.rx_compress_time_ave)?; + write!(f, "{}, ", self.rx_compress_time_min)?; + write!(f, "{}, ", self.rx_compress_time_max)?; + write!(f, "{}, ", self.rx_failed_to_decompress)?; + write!(f, "{}, ", self.tx_crypt_packets)?; + write!(f, "{}, ", self.tx_crypt_byte_overhead)?; + write!(f, "{}, ", self.tx_crypt_time_ave)?; + write!(f, "{}, ", self.tx_crypt_time_min)?; + write!(f, "{}, ", self.tx_crypt_time_max)?; + write!(f, "{}, ", self.rx_crypt_packets)?; + write!(f, "{}, ", self.rx_crypt_time_ave)?; + write!(f, "{}, ", self.rx_crypt_time_min)?; + write!(f, "{}, ", self.rx_crypt_time_max)?; + Ok(()) + } +} + +/// Return statistics for this knet handle +pub fn handle_get_stats(handle: Handle) -> Result +{ + let (res, stats) = unsafe { + let mut c_stats = HandleStats::new(); + let res = ffi::knet_handle_get_stats(handle.knet_handle as ffi::knet_handle_t, + &mut c_stats, size_of::()); + (res, c_stats) + }; + if res == 0 { + Ok(stats) + } else { + Err(Error::last_os_error()) + } +} + +/// Tell [handle_clear_stats] whether to cleat all stats or just handle stats +pub enum ClearStats { + Handle = 1, + HandleAndLink = 2, +} + +/// Clear statistics +pub fn handle_clear_stats(handle: Handle, clear_options: ClearStats) -> Result<()> +{ + let c_value : i32 = + match clear_options { + ClearStats::Handle => 1, + ClearStats::HandleAndLink => 2 + }; + + let res = unsafe { + ffi::knet_handle_clear_stats(handle.knet_handle as ffi::knet_handle_t, + c_value) + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// Crypto info returned from [get_crypto_list] +pub struct CryptoInfo +{ + pub name: String, + pub properties: u8, // Unused +} +impl CryptoInfo +{ + pub fn new(c_info: ffi::knet_crypto_info) -> CryptoInfo + { + let cstr = unsafe {CStr::from_ptr(c_info.name) }; + let name = match cstr.to_str() { + Ok(s) => s.to_string(), + Err(e) => e.to_string(), + }; + CryptoInfo {properties: 0, + name} + } +} + +/// Get a list of valid crypto options +pub fn get_crypto_list() -> Result> +{ + let mut list_entries: usize = 256; + let mut c_list : [ffi::knet_crypto_info; 256] = + [ ffi::knet_crypto_info{name: null(), properties: 0u8, pad:[0; 256]}; 256]; + + let res = unsafe { + ffi::knet_get_crypto_list(&mut c_list[0], + &mut list_entries) + }; + if res == 0 { + let mut retvec = Vec::::new(); + for i in c_list.iter().take(list_entries) { + retvec.push(CryptoInfo::new(*i)); + } + Ok(retvec) + } else { + Err(Error::last_os_error()) + } +} + + +/// Compressions types returned from [get_compress_list] +pub struct CompressInfo +{ + pub name: String, + pub properties: u8, // Unused +} +impl CompressInfo +{ + pub fn new(c_info: ffi::knet_compress_info) -> CompressInfo + { + let cstr = unsafe {CStr::from_ptr(c_info.name) }; + let name = match cstr.to_str() { + Ok(s) => s.to_string(), + Err(e) => e.to_string(), + }; + CompressInfo {properties: 0, + name} + } +} + +/// Return a list of compression options +pub fn get_compress_list() -> Result> +{ + let mut list_entries: usize = 256; + let mut c_list : [ffi::knet_compress_info; 256] = + [ ffi::knet_compress_info{name: null(), properties: 0u8, pad:[0; 256]}; 256]; + + let res = unsafe { + ffi::knet_get_compress_list(&mut c_list[0], + &mut list_entries) + }; + if res == 0 { + let mut retvec = Vec::::new(); + for i in c_list.iter().take(list_entries) { + retvec.push(CompressInfo::new(*i)); + } + Ok(retvec) + } else { + Err(Error::last_os_error()) + } +} + +/// Enable callback when the onwire version for a node changes +pub fn handle_enable_onwire_ver_notify(handle: Handle, + private_data: u64, + onwire_notify_fn: Option) -> Result<()> +{ + // This looks a bit different to the other _enable*_notify calls because knet + // calls the calback function in the API. Which results in a deadlock with our + // own mutex + match HANDLE_HASH.lock().unwrap().get_mut(&(handle.knet_handle)) { + Some(h) => { + h.onwire_notify_private_data = private_data; + h.onwire_notify_fn = onwire_notify_fn; + }, + None => return Err(Error::new(ErrorKind::Other, "Rust handle not found")), + }; + + let res = match onwire_notify_fn { + Some(_f) => + unsafe { + ffi::knet_handle_enable_onwire_ver_notify(handle.knet_handle as ffi::knet_handle_t, + handle.knet_handle as *mut c_void, + Some(rust_onwire_notify_fn)) + }, + None => + unsafe { + ffi::knet_handle_enable_onwire_ver_notify(handle.knet_handle as ffi::knet_handle_t, + handle.knet_handle as *mut c_void, + None) + }, + }; + + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + + +/// Get the onsure version for a node +pub fn handle_get_onwire_ver(handle: Handle, host_id: &HostId) -> Result<(u8,u8,u8)> +{ + let mut onwire_min_ver = 0u8; + let mut onwire_max_ver = 0u8; + let mut onwire_ver = 0u8; + let res = unsafe { + ffi::knet_handle_get_onwire_ver(handle.knet_handle as ffi::knet_handle_t, + host_id.host_id, + &mut onwire_min_ver, + &mut onwire_max_ver, + &mut onwire_ver) + }; + if res == 0 { + Ok((onwire_min_ver, onwire_max_ver, onwire_ver)) + } else { + Err(Error::last_os_error()) + } +} + +/// Set the onsire version for this node +pub fn handle_set_onwire_ver(handle: Handle, onwire_ver: u8) -> Result<()> +{ + let res = unsafe { + ffi::knet_handle_set_onwire_ver(handle.knet_handle as ffi::knet_handle_t, + onwire_ver) + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// Set the reconnect interval. +pub fn handle_set_transport_reconnect_interval(handle: Handle, msecs: u32) -> Result<()> +{ + let res = unsafe { + ffi::knet_handle_set_transport_reconnect_interval(handle.knet_handle as ffi::knet_handle_t, + msecs) + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// Get the reconnect interval. +pub fn handle_get_transport_reconnect_interval(handle: Handle) -> Result +{ + let mut msecs = 0u32; + let res = unsafe { + ffi::knet_handle_get_transport_reconnect_interval(handle.knet_handle as ffi::knet_handle_t, + &mut msecs) + }; + if res == 0 { + Ok(msecs) + } else { + Err(Error::last_os_error()) + } +} + +/// Add a new host ID +pub fn host_add(handle: Handle, host_id: &HostId) -> Result<()> +{ + let res = unsafe { + ffi::knet_host_add(handle.knet_handle as ffi::knet_handle_t, + host_id.host_id) + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// Remove a Host ID +pub fn host_remove(handle: Handle, host_id: &HostId) -> Result<()> +{ + let res = unsafe { + ffi::knet_host_remove(handle.knet_handle as ffi::knet_handle_t, + host_id.host_id) + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// Set the name of a host +pub fn host_set_name(handle: Handle, host_id: &HostId, name: &str) -> Result<()> +{ + + let c_name = CString::new(name)?; + let res = unsafe { + ffi::knet_host_set_name(handle.knet_handle as ffi::knet_handle_t, + host_id.host_id, c_name.as_ptr()) + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +const KNET_MAX_HOST_LEN:usize = 256; +const KNET_MAX_PORT_LEN:usize = 6; +/// Retrieve the name of a host given its ID +pub fn host_get_name_by_host_id(handle: Handle, host_id: &HostId) -> Result +{ + let mut c_name: [c_char; KNET_MAX_HOST_LEN] = [0; KNET_MAX_HOST_LEN]; + let res = unsafe { + ffi::knet_host_get_name_by_host_id(handle.knet_handle as ffi::knet_handle_t, + host_id.host_id, c_name.as_mut_ptr()) + }; + if res == 0 { + crate::string_from_bytes(c_name.as_ptr(), KNET_MAX_HOST_LEN) + } else { + Err(Error::last_os_error()) + } +} + + +/// Return the ID of a host given its name +pub fn host_get_id_by_host_name(handle: Handle, name: &str) -> Result +{ + + let c_name = CString::new(name)?; + let mut c_host_id = 0u16; + let res = unsafe { + ffi::knet_host_get_id_by_host_name(handle.knet_handle as ffi::knet_handle_t, + c_name.as_ptr(), &mut c_host_id) + }; + if res == 0 { + Ok(HostId{host_id: c_host_id}) + } else { + Err(Error::last_os_error()) + } +} + +const KNET_MAX_HOST: usize = 65536; +/// Return a list of host IDs known to this handle +pub fn host_get_host_list(handle: Handle) -> Result> +{ + let mut c_host_ids: [u16; KNET_MAX_HOST] = [0; KNET_MAX_HOST]; + let mut c_host_ids_entries: usize = 0; + let res = unsafe { + ffi::knet_host_get_host_list(handle.knet_handle as ffi::knet_handle_t, + &mut c_host_ids[0], &mut c_host_ids_entries) + }; + if res == 0 { + let mut host_vec = Vec::::new(); + for i in c_host_ids.iter().take(c_host_ids_entries) { + host_vec.push(HostId {host_id: *i}); + } + Ok(host_vec) + } else { + Err(Error::last_os_error()) + } +} + +/// Link Policies for [host_set_policy] +#[derive(Copy, Clone, PartialEq)] +pub enum LinkPolicy { + Passive, + Active, + Rr, +} + +impl LinkPolicy{ + pub fn new(value: u8) -> LinkPolicy + { + match value { + 2 => LinkPolicy::Rr, + 1 => LinkPolicy::Active, + _ => LinkPolicy::Passive, + } + } + pub fn to_u8(self: LinkPolicy) -> u8 + { + match self { + LinkPolicy::Passive => 0, + LinkPolicy::Active => 1, + LinkPolicy::Rr => 2, + } + } +} +impl fmt::Display for LinkPolicy { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + LinkPolicy::Passive => write!(f, "Passive"), + LinkPolicy::Active => write!(f, "Active"), + LinkPolicy::Rr => write!(f, "RR"), + } + } + +} + +/// Set the policy for this host, this only makes sense if multiple links between hosts are configured +pub fn host_set_policy(handle: Handle, host_id: &HostId, policy: LinkPolicy) -> Result<()> +{ + let c_value: u8 = policy.to_u8(); + + let res = unsafe { + ffi::knet_host_set_policy(handle.knet_handle as ffi::knet_handle_t, + host_id.host_id, c_value) + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + + +/// Return the current link policy for a node +pub fn host_get_policy(handle: Handle, host_id: &HostId) -> Result +{ + let mut c_value: u8 = 0; + let res = unsafe { + ffi::knet_host_get_policy(handle.knet_handle as ffi::knet_handle_t, + host_id.host_id, &mut c_value) + }; + if res == 0 { + Ok(LinkPolicy::new(c_value)) + } else { + Err(Error::last_os_error()) + } +} + +/// Current status of a host. remote & reachable are current not used +pub struct HostStatus +{ + pub reachable: bool, + pub remote: bool, + pub external: bool, +} +impl fmt::Display for HostStatus { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "reachable: {}, ", self.reachable)?; + write!(f, "remote: {}, ", self.remote)?; + write!(f, "external: {}", self.external)?; + Ok(()) + } +} + + +/// Return the current status of a host +pub fn host_get_status(handle: Handle, host_id: &HostId) -> Result +{ + let mut c_value = ffi::knet_host_status { reachable:0, remote:0, external:0}; + let res = unsafe { + ffi::knet_host_get_status(handle.knet_handle as ffi::knet_handle_t, + host_id.host_id, &mut c_value) + }; + if res == 0 { + Ok(HostStatus { + reachable: crate::u8_to_bool(c_value.reachable), + remote: crate::u8_to_bool(c_value.remote), + external: crate::u8_to_bool(c_value.external) + }) + } else { + Err(Error::last_os_error()) + } +} + +/// Enable callbacks when the status of a host changes +pub fn host_enable_status_change_notify(handle: Handle, + private_data: u64, + host_status_change_notify_fn: Option) -> Result<()> +{ + let res = match HANDLE_HASH.lock().unwrap().get_mut(&(handle.knet_handle)) { + Some(h) => { + h.host_status_change_notify_private_data = private_data; + h.host_status_change_notify_fn = host_status_change_notify_fn; + match host_status_change_notify_fn { + Some(_f) => + unsafe { + ffi::knet_host_enable_status_change_notify(handle.knet_handle as ffi::knet_handle_t, + handle.knet_handle as *mut c_void, + Some(rust_host_status_change_notify_fn)) + }, + None => + unsafe { + ffi::knet_host_enable_status_change_notify(handle.knet_handle as ffi::knet_handle_t, + handle.knet_handle as *mut c_void, + None) + }, + } + }, + None => return Err(Error::new(ErrorKind::Other, "Rust handle not found")), + }; + + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// Transport types supported in knet +pub enum TransportId { + Loopback, + Udp, + Sctp, +} +impl TransportId { + pub fn new(id: u8) -> TransportId + { + match id { + 2 => TransportId::Sctp, + 1 => TransportId::Udp, + _ => TransportId::Loopback, + } + } + pub fn to_u8(self: &TransportId) -> u8 + { + match self { + TransportId::Loopback => 0, + TransportId::Udp => 1, + TransportId::Sctp => 2, + } + } + + pub fn to_string(self: &TransportId) -> String + { + match self { + TransportId::Udp => "UDP".to_string(), + TransportId::Sctp => "SCTP".to_string(), + TransportId::Loopback => "Loopback".to_string() + } + } + pub fn from_string(name: String) -> TransportId + { + match name.as_str() { + "UDP" => TransportId::Udp, + "SCTP" => TransportId::Sctp, + "Loopback" => TransportId::Loopback, + _ => TransportId::Loopback, + } + } +} + +/// Transport info returned from [get_transport_list] +pub struct TransportInfo +{ + pub name: String, + pub id: TransportId, + pub properties: u8, // currently unused +} + +// Controversially implementing name_by_id and id_by_name here +impl TransportInfo +{ + pub fn new(c_info: ffi::knet_transport_info) -> TransportInfo + { + let cstr = unsafe {CStr::from_ptr(c_info.name) }; + let name = match cstr.to_str() { + Ok(s) => s.to_string(), + Err(e) => e.to_string(), + }; + TransportInfo {properties: 0, + id: TransportId::new(c_info.id), + name} + } +} + +pub fn get_transport_list() -> Result> +{ + let mut list_entries: usize = 256; + let mut c_list : [ffi::knet_transport_info; 256] = + [ ffi::knet_transport_info{name: null(), id: 0u8, properties: 0u8, pad:[0; 256]}; 256]; + + let res = unsafe { + ffi::knet_get_transport_list(&mut c_list[0], + &mut list_entries) + }; + if res == 0 { + let mut retvec = Vec::::new(); + for i in c_list.iter().take(list_entries) { + retvec.push(TransportInfo::new(*i)); + } + Ok(retvec) + } else { + Err(Error::last_os_error()) + } +} + +/// Configure a link to a host ID. dst_addr may be None for a dynamic link. +pub fn link_set_config(handle: Handle, host_id: &HostId, link_id: u8, + transport: TransportId, + src_addr: &SocketAddr, dst_addr: Option<&SocketAddr>, flags: LinkFlags) -> Result<()> +{ + // Not really mut, but C is dumb + let mut c_srcaddr = make_new_sockaddr_storage(src_addr); + + // dst_addr can be NULL/None if this is a dynamic link + let res = if let Some(dst) = dst_addr { + let mut c_dstaddr = make_new_sockaddr_storage(dst); + + unsafe { + ffi::knet_link_set_config(handle.knet_handle as ffi::knet_handle_t, + host_id.host_id, link_id, + transport.to_u8(), + &mut c_srcaddr, + &mut c_dstaddr, + flags.bits) + } + } else { + unsafe { + ffi::knet_link_set_config(handle.knet_handle as ffi::knet_handle_t, + host_id.host_id, link_id, + transport.to_u8(), + &mut c_srcaddr, + null_mut(), + flags.bits) + } + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + + +/// Return a link's configuration +pub fn link_get_config(handle: Handle, host_id: &HostId, link_id: u8) -> + Result<(TransportId, Option, Option, LinkFlags)> +{ + let mut c_srcaddr = OsSocketAddr::new(); + let mut c_dstaddr = OsSocketAddr::new(); + let mut c_transport = 0u8; + let mut c_flags = 0u64; + let mut c_dynamic = 0u8; + let res = unsafe { + ffi::knet_link_get_config(handle.knet_handle as ffi::knet_handle_t, + host_id.host_id, link_id, + &mut c_transport, + c_srcaddr.as_mut_ptr() as *mut ffi::sockaddr_storage, + c_dstaddr.as_mut_ptr() as *mut ffi::sockaddr_storage, + &mut c_dynamic, + &mut c_flags) + }; + if res == 0 { + let r_transport = TransportId::new(c_transport); + Ok((r_transport, c_srcaddr.into(), c_dstaddr.into(), LinkFlags{bits:c_flags})) + } else { + Err(Error::last_os_error()) + } +} + +/// Clear a link configuration. +pub fn link_clear_config(handle: Handle, host_id: &HostId, link_id: u8) -> Result<()> +{ + let res = unsafe { + ffi::knet_link_clear_config(handle.knet_handle as ffi::knet_handle_t, + host_id.host_id, link_id) + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// Type of ACL +pub enum AclAcceptReject +{ + Accept, + Reject, +} +impl AclAcceptReject { + pub fn new(ar: u32) -> AclAcceptReject + { + match ar { + ffi::CHECK_ACCEPT => AclAcceptReject::Accept, + ffi::CHECK_REJECT => AclAcceptReject::Reject, + _ => AclAcceptReject::Reject, + } + } + pub fn to_u32(self: &AclAcceptReject) -> u32 + { + match self { + AclAcceptReject::Accept => ffi::CHECK_ACCEPT, + AclAcceptReject::Reject => ffi::CHECK_REJECT, + } + } +} + +/// What the ACL should check +pub enum AclCheckType +{ + Address, + Mask, + Range, +} +impl AclCheckType { + pub fn new(ct: u32) -> AclCheckType + { + match ct { + ffi::CHECK_TYPE_ADDRESS => AclCheckType::Address, + ffi::CHECK_TYPE_MASK => AclCheckType::Mask, + ffi::CHECK_TYPE_RANGE => AclCheckType::Range, + _ => AclCheckType::Address, + } + } + pub fn to_u32(self: &AclCheckType) -> u32 + { + match self { + AclCheckType::Address => ffi::CHECK_TYPE_ADDRESS, + AclCheckType::Mask => ffi::CHECK_TYPE_MASK, + AclCheckType::Range => ffi::CHECK_TYPE_RANGE, + } + } +} + +// We need to have a zeroed-out stackaddr storage to pass to the ACL APIs +// as knet compares the whole sockaddr_storage when using knet_rm_acl() +fn make_new_sockaddr_storage(ss: &SocketAddr) -> ffi::sockaddr_storage +{ + // A blank one + let mut new_ss = ffi::sockaddr_storage { + ss_family: 0, + __ss_padding: [0; 118], + __ss_align: 0, + }; + let p_new_ss : *mut ffi::sockaddr_storage = &mut new_ss; + + // Rust only fills in what it thinks is necessary + let c_ss : OsSocketAddr = (*ss).into(); + + // Copy it + unsafe { + // Only copy as much as is in the OsSocketAddr + copy_nonoverlapping(c_ss.as_ptr(), + p_new_ss as *mut libc::sockaddr, + 1); + } + + new_ss +} + + +/// Add an ACL to a link, adds the ACL to the end of the list. +pub fn link_add_acl(handle: Handle, host_id: &HostId, link_id: u8, + ss1: &SocketAddr, ss2: &SocketAddr, + check_type: AclCheckType, acceptreject: AclAcceptReject) -> Result<()> +{ + // Not really mut, but C is dumb + let mut c_ss1 = make_new_sockaddr_storage(ss1); + let mut c_ss2 = make_new_sockaddr_storage(ss2); + let res = unsafe { + ffi::knet_link_add_acl(handle.knet_handle as ffi::knet_handle_t, + host_id.host_id, link_id, + &mut c_ss1, + &mut c_ss2, + check_type.to_u32(), acceptreject.to_u32()) + + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// Insert an ACL anywhere in the ACL list for this host/link +pub fn link_insert_acl(handle: Handle, host_id: &HostId, link_id: u8, + index: i32, + ss1: &SocketAddr, ss2: &SocketAddr, + check_type: AclCheckType, acceptreject: AclAcceptReject) -> Result<()> +{ + // Not really mut, but C is dumb + let mut c_ss1 = make_new_sockaddr_storage(ss1); + let mut c_ss2 = make_new_sockaddr_storage(ss2); + let res = unsafe { + ffi::knet_link_insert_acl(handle.knet_handle as ffi::knet_handle_t, + host_id.host_id, link_id, + index, + &mut c_ss1, + &mut c_ss2, + check_type.to_u32(), acceptreject.to_u32()) + + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// Remove an ACL for this host/link +pub fn link_rm_acl(handle: Handle, host_id: &HostId, link_id: u8, + ss1: &SocketAddr, ss2: &SocketAddr, + check_type: AclCheckType, acceptreject: AclAcceptReject) -> Result<()> +{ + // Not really mut, but C is dumb + let mut c_ss1 = make_new_sockaddr_storage(ss1); + let mut c_ss2 = make_new_sockaddr_storage(ss2); + let res = unsafe { + ffi::knet_link_rm_acl(handle.knet_handle as ffi::knet_handle_t, + host_id.host_id, link_id, + &mut c_ss1, + &mut c_ss2, + check_type.to_u32(), acceptreject.to_u32()) + + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// Clear out all ACLs from this host/link +pub fn link_clear_acl(handle: Handle, host_id: &HostId, link_id: u8) -> Result<()> +{ + let res = unsafe { + ffi::knet_link_clear_acl(handle.knet_handle as ffi::knet_handle_t, + host_id.host_id, link_id) + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + + +/// Enable/disable a link (you still need to call [handle_setfwd] for traffic to flow +pub fn link_set_enable(handle: Handle, host_id: &HostId, link_id: u8, enable: bool) -> Result<()> +{ + let res = unsafe { + ffi::knet_link_set_enable(handle.knet_handle as ffi::knet_handle_t, + host_id.host_id, link_id, enable as u32) + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// Get the 'enabled' status for a link +pub fn link_get_enable(handle: Handle, host_id: &HostId, link_id: u8) -> Result +{ + let mut c_enable = 0u32; + let res = unsafe { + ffi::knet_link_get_enable(handle.knet_handle as ffi::knet_handle_t, + host_id.host_id, link_id, &mut c_enable) + }; + if res == 0 { + Ok(crate::u32_to_bool(c_enable)) + } else { + Err(Error::last_os_error()) + } +} + +/// Set the ping timers for a link +pub fn link_set_ping_timers(handle: Handle, host_id: &HostId, link_id: u8, + interval: i64, timeout: i64, precision: u32) -> Result<()> +{ + let res = unsafe { + ffi::knet_link_set_ping_timers(handle.knet_handle as ffi::knet_handle_t, + host_id.host_id, link_id, + interval, timeout, precision) + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// Get the ping timers for a link +pub fn link_get_ping_timers(handle: Handle, host_id: &HostId, link_id: u8) -> Result<(i64, i64, u32)> +{ + let mut c_interval : ffi::time_t = 0; + let mut c_timeout : ffi::time_t = 0; + let mut c_precision = 0u32; + let res = unsafe { + ffi::knet_link_get_ping_timers(handle.knet_handle as ffi::knet_handle_t, + host_id.host_id, link_id, + &mut c_interval, &mut c_timeout, &mut c_precision) + }; + if res == 0 { + Ok((c_interval as i64, c_timeout as i64, c_precision)) + } else { + Err(Error::last_os_error()) + } +} + +/// Set the pong count for a link +pub fn link_set_pong_count(handle: Handle, host_id: &HostId, link_id: u8, + pong_count: u8) -> Result<()> +{ + let res = unsafe { + ffi::knet_link_set_pong_count(handle.knet_handle as ffi::knet_handle_t, + host_id.host_id, link_id, + pong_count) + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// Get the pong count for a link +pub fn link_get_pong_count(handle: Handle, host_id: &HostId, link_id: u8) -> Result +{ + let mut c_pong_count = 0u8; + let res = unsafe { + ffi::knet_link_get_pong_count(handle.knet_handle as ffi::knet_handle_t, + host_id.host_id, link_id, + &mut c_pong_count) + }; + if res == 0 { + Ok(c_pong_count) + } else { + Err(Error::last_os_error()) + } +} + + +/// Set the link priority (only useful with multiple links to a node) +pub fn link_set_priority(handle: Handle, host_id: &HostId, link_id: u8, + priority: u8) -> Result<()> +{ + let res = unsafe { + ffi::knet_link_set_priority(handle.knet_handle as ffi::knet_handle_t, + host_id.host_id, link_id, + priority) + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// Get the link priority +pub fn link_get_priority(handle: Handle, host_id: &HostId, link_id: u8) -> Result +{ + let mut c_priority = 0u8; + let res = unsafe { + ffi::knet_link_get_priority(handle.knet_handle as ffi::knet_handle_t, + host_id.host_id, link_id, + &mut c_priority) + }; + if res == 0 { + Ok(c_priority) + } else { + Err(Error::last_os_error()) + } +} + +const KNET_MAX_LINK: usize = 8; +/// Get a list of links for this host +pub fn link_get_link_list(handle: Handle, host_id: &HostId) -> Result> +{ + let mut c_link_ids: [u8; KNET_MAX_LINK] = [0; KNET_MAX_LINK]; + let mut c_link_ids_entries: usize = 0; + let res = unsafe { + ffi::knet_link_get_link_list(handle.knet_handle as ffi::knet_handle_t, host_id.host_id, + &mut c_link_ids[0], &mut c_link_ids_entries) + }; + if res == 0 { + let mut link_vec = Vec::::new(); + for i in c_link_ids.iter().take(c_link_ids_entries) { + link_vec.push(*i); + } + Ok(link_vec) + } else { + Err(Error::last_os_error()) + } +} + +/// Enable callbacks when a link status changes +pub fn link_enable_status_change_notify(handle: Handle, + private_data: u64, + link_status_change_notify_fn: Option) -> Result<()> +{ + let res = match HANDLE_HASH.lock().unwrap().get_mut(&(handle.knet_handle)) { + Some(h) => { + h.link_status_change_notify_private_data = private_data; + h.link_status_change_notify_fn = link_status_change_notify_fn; + match link_status_change_notify_fn { + Some(_f) => + unsafe { + ffi::knet_link_enable_status_change_notify(handle.knet_handle as ffi::knet_handle_t, + handle.knet_handle as *mut c_void, + Some(rust_link_status_change_notify_fn)) + }, + None => + unsafe { + ffi::knet_link_enable_status_change_notify(handle.knet_handle as ffi::knet_handle_t, + handle.knet_handle as *mut c_void, + None) + }, + } + }, + None => return Err(Error::new(ErrorKind::Other, "Rust handle not found")), + }; + + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// Link stats +pub struct LinkStats { + pub tx_data_packets: u64, + pub rx_data_packets: u64, + pub tx_data_bytes: u64, + pub rx_data_bytes: u64, + pub rx_ping_packets: u64, + pub tx_ping_packets: u64, + pub rx_ping_bytes: u64, + pub tx_ping_bytes: u64, + pub rx_pong_packets: u64, + pub tx_pong_packets: u64, + pub rx_pong_bytes: u64, + pub tx_pong_bytes: u64, + pub rx_pmtu_packets: u64, + pub tx_pmtu_packets: u64, + pub rx_pmtu_bytes: u64, + pub tx_pmtu_bytes: u64, + pub tx_total_packets: u64, + pub rx_total_packets: u64, + pub tx_total_bytes: u64, + pub rx_total_bytes: u64, + pub tx_total_errors: u64, + pub tx_total_retries: u64, + pub tx_pmtu_errors: u32, + pub tx_pmtu_retries: u32, + pub tx_ping_errors: u32, + pub tx_ping_retries: u32, + pub tx_pong_errors: u32, + pub tx_pong_retries: u32, + pub tx_data_errors: u32, + pub tx_data_retries: u32, + pub latency_min: u32, + pub latency_max: u32, + pub latency_ave: u32, + pub latency_samples: u32, + pub down_count: u32, + pub up_count: u32, + pub last_up_times: Vec, + pub last_down_times: Vec, +} +// Quick & Dirty printing +impl fmt::Display for LinkStats { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}, ", self.tx_data_packets)?; + write!(f, "{}, ", self.rx_data_packets)?; + write!(f, "{}, ", self.tx_data_bytes)?; + write!(f, "{}, ", self.rx_data_bytes)?; + write!(f, "{}, ", self.rx_ping_packets)?; + write!(f, "{}, ", self.tx_ping_packets)?; + write!(f, "{}, ", self.rx_ping_bytes)?; + write!(f, "{}, ", self.tx_ping_bytes)?; + write!(f, "{}, ", self.rx_pong_packets)?; + write!(f, "{}, ", self.tx_pong_packets)?; + write!(f, "{}, ", self.rx_pong_bytes)?; + write!(f, "{}, ", self.tx_pong_bytes)?; + write!(f, "{}, ", self.rx_pmtu_packets)?; + write!(f, "{}, ", self.tx_pmtu_packets)?; + write!(f, "{}, ", self.rx_pmtu_bytes)?; + write!(f, "{}, ", self.tx_pmtu_bytes)?; + write!(f, "{}, ", self.tx_total_packets)?; + write!(f, "{}, ", self.rx_total_packets)?; + write!(f, "{}, ", self.tx_total_bytes)?; + write!(f, "{}, ", self.rx_total_bytes)?; + write!(f, "{}, ", self.tx_total_errors)?; + write!(f, "{}, ", self.tx_total_retries)?; + write!(f, "{}, ", self.tx_pmtu_errors)?; + write!(f, "{}, ", self.tx_pmtu_retries)?; + write!(f, "{}, ", self.tx_ping_errors)?; + write!(f, "{}, ", self.tx_ping_retries)?; + write!(f, "{}, ", self.tx_pong_errors)?; + write!(f, "{}, ", self.tx_pong_retries)?; + write!(f, "{}, ", self.tx_data_errors)?; + write!(f, "{}, ", self.tx_data_retries)?; + write!(f, "{}, ", self.latency_min)?; + write!(f, "{}, ", self.latency_max)?; + write!(f, "{}, ", self.latency_ave)?; + write!(f, "{}, ", self.latency_samples)?; + write!(f, "{}, ", self.down_count)?; + write!(f, "{}, ", self.up_count)?; + write!(f, "Last up times: ")?; + // There's no sensible print for SystemTime in the std library + // and I don't want to add dependancies here for printing as it + // mostly going to be the client's responsibility, so use the Debug option + for i in &self.last_up_times { + write!(f, "{:?}", i)?; + } + write!(f, " Last down times: ")?; + for i in &self.last_down_times { + write!(f, "{:?}", i)?; + } + Ok(()) + } +} + +// I wish this all wasn't necessary! +impl ffi::knet_link_stats { + pub fn new() -> ffi::knet_link_stats { + ffi::knet_link_stats { + tx_data_packets: 0, + rx_data_packets: 0, + tx_data_bytes: 0, + rx_data_bytes: 0, + rx_ping_packets: 0, + tx_ping_packets: 0, + rx_ping_bytes: 0, + tx_ping_bytes: 0, + rx_pong_packets: 0, + tx_pong_packets: 0, + rx_pong_bytes: 0, + tx_pong_bytes: 0, + rx_pmtu_packets: 0, + tx_pmtu_packets: 0, + rx_pmtu_bytes: 0, + tx_pmtu_bytes: 0, + tx_total_packets: 0, + rx_total_packets: 0, + tx_total_bytes: 0, + rx_total_bytes: 0, + tx_total_errors: 0, + tx_total_retries: 0, + tx_pmtu_errors: 0, + tx_pmtu_retries: 0, + tx_ping_errors: 0, + tx_ping_retries: 0, + tx_pong_errors: 0, + tx_pong_retries: 0, + tx_data_errors: 0, + tx_data_retries: 0, + latency_min: 0, + latency_max: 0, + latency_ave: 0, + latency_samples: 0, + down_count: 0, + up_count: 0, + last_up_times: [0; 16], + last_down_times: [0; 16], + last_up_time_index: 0, + last_down_time_index: 0, + } + } +} +impl Default for ffi::knet_link_stats { + fn default() -> Self { + ffi::knet_link_stats::new() + } +} +impl ffi::knet_link_status { + pub fn new()-> ffi::knet_link_status + { + ffi::knet_link_status { + size: 0, + src_ipaddr : [0; KNET_MAX_HOST_LEN], + dst_ipaddr : [0; KNET_MAX_HOST_LEN], + src_port : [0; KNET_MAX_PORT_LEN], + dst_port : [0; KNET_MAX_PORT_LEN], + enabled: 0, + connected: 0, + dynconnected: 0, + pong_last: ffi::timespec{ tv_sec: 0, tv_nsec: 0}, + mtu: 0, + proto_overhead: 0, + stats: ffi::knet_link_stats::new(), + } + } +} +impl Default for ffi::knet_link_status { + fn default() -> Self { + ffi::knet_link_status::new() + } +} + +/// Link status (includes a [LinkStats]) +pub struct LinkStatus +{ + pub src_ipaddr: String, + pub dst_ipaddr: String, + pub src_port: String, + pub dst_port: String, + pub enabled: bool, + pub connected: bool, + pub dynconnected: bool, + pub pong_last: SystemTime, + pub mtu: u32, + pub proto_overhead: u32, + pub stats: LinkStats, +} +impl LinkStatus { + pub fn new(c_stats: ffi::knet_link_status) -> LinkStatus + { + LinkStatus { + src_ipaddr : crate::string_from_bytes_safe(c_stats.src_ipaddr.as_ptr(), KNET_MAX_HOST_LEN), + src_port : crate::string_from_bytes_safe(c_stats.src_port.as_ptr(), KNET_MAX_HOST_LEN), + dst_ipaddr : crate::string_from_bytes_safe(c_stats.dst_ipaddr.as_ptr(), KNET_MAX_HOST_LEN), + dst_port : crate::string_from_bytes_safe(c_stats.dst_port.as_ptr(), KNET_MAX_HOST_LEN), + enabled : crate::u8_to_bool(c_stats.enabled), + connected : crate::u8_to_bool(c_stats.connected), + dynconnected : crate::u8_to_bool(c_stats.dynconnected), + pong_last : systemtime_from_timespec(c_stats.pong_last), + mtu : c_stats.mtu, + proto_overhead : c_stats.proto_overhead, + stats : LinkStats::new(c_stats.stats), + } + } +} +impl fmt::Display for LinkStatus { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "src_ip_addr: {}:{}, ", self.src_ipaddr, self.src_port)?; + write!(f, "dst_ip_addr: {}:{}, ", self.dst_ipaddr, self.src_port)?; + write!(f, "enabled: {}, connected: {}, mtu: {}, overhead: {}, ", + self.enabled, self.connected, self.mtu, self.proto_overhead)?; + write!(f, "stats: {}", self.stats)?; + Ok(()) + } +} + +fn systemtime_from_time_t(t: u64) -> SystemTime +{ + SystemTime::UNIX_EPOCH+Duration::from_secs(t) +} + +fn systemtime_from_timespec(t: ffi::timespec) -> SystemTime +{ + SystemTime::UNIX_EPOCH+Duration::from_secs(t.tv_sec as u64) + +Duration::from_nanos(t.tv_nsec as u64) // TODO may panic?? + +} + +fn copy_circular_buffer_of_link_events(num: usize, times: &[ffi::time_t]) -> Vec +{ + let mut times_vec = Vec::::new(); + + for index in (0 .. num).rev() { + if times[index] == 0 { + break + } + times_vec.push(systemtime_from_time_t(times[index] as u64)); // TODO may panic?? + } + for index in (num+1 .. MAX_LINK_EVENTS).rev() { + if times[index] == 0 { + break; + } + times_vec.push(systemtime_from_time_t(times[index] as u64)); // TODO may panic?? + } + times_vec +} + +const MAX_LINK_EVENTS: usize = 16; +impl LinkStats { + pub fn new(cstats: ffi::knet_link_stats) -> LinkStats + { + let up_times = copy_circular_buffer_of_link_events(cstats.last_up_time_index as usize, + &cstats.last_up_times); + + let down_times = copy_circular_buffer_of_link_events(cstats.last_down_time_index as usize, + &cstats.last_down_times); + + LinkStats { + tx_data_packets: cstats.tx_data_packets, + rx_data_packets: cstats.rx_data_packets, + tx_data_bytes: cstats.tx_data_bytes, + rx_data_bytes: cstats.rx_data_bytes, + rx_ping_packets: cstats.rx_ping_packets, + tx_ping_packets: cstats.tx_ping_packets, + rx_ping_bytes: cstats.rx_ping_bytes, + tx_ping_bytes: cstats.tx_ping_bytes, + rx_pong_packets: cstats.rx_pong_packets, + tx_pong_packets: cstats.tx_pong_packets, + rx_pong_bytes: cstats.rx_pong_bytes, + tx_pong_bytes: cstats.tx_pong_bytes, + rx_pmtu_packets: cstats.rx_pmtu_packets, + tx_pmtu_packets: cstats.tx_pmtu_packets, + rx_pmtu_bytes: cstats.rx_pmtu_bytes, + tx_pmtu_bytes: cstats.tx_pmtu_bytes, + tx_total_packets: cstats.tx_total_packets, + rx_total_packets: cstats.rx_total_packets, + tx_total_bytes: cstats.tx_total_bytes, + rx_total_bytes: cstats.rx_total_bytes, + tx_total_errors: cstats.tx_total_errors, + tx_total_retries: cstats.tx_total_retries, + tx_pmtu_errors: cstats.tx_pmtu_errors, + tx_pmtu_retries: cstats.tx_pmtu_retries, + tx_ping_errors: cstats.tx_ping_errors, + tx_ping_retries: cstats.tx_ping_retries, + tx_pong_errors: cstats.tx_pong_errors, + tx_pong_retries: cstats.tx_pong_retries, + tx_data_errors: cstats.tx_data_errors, + tx_data_retries: cstats.tx_data_retries, + latency_min: cstats.latency_min, + latency_max: cstats.latency_max, + latency_ave: cstats.latency_ave, + latency_samples: cstats.latency_samples, + down_count: cstats.down_count, + up_count: cstats.up_count, + last_up_times: up_times, + last_down_times: down_times, + } + } +} + +/// Get the status (and stats) of a link +pub fn link_get_status(handle: Handle, host_id: &HostId, link_id: u8) -> Result +{ + let (res, stats) = unsafe { + let mut c_stats : ffi::knet_link_status = ffi::knet_link_status::new(); + + let res = ffi::knet_link_get_status(handle.knet_handle as ffi::knet_handle_t, + host_id.host_id, link_id, + &mut c_stats, size_of::()); + (res, c_stats) + }; + if res == 0 { + let r_status = LinkStatus::new(stats); + Ok(r_status) + } else { + Err(Error::last_os_error()) + } +} + +/// Get the logging subsystem ID given its name +pub fn log_get_subsystem_id(name: &str) -> Result +{ + let c_name = CString::new(name)?; + let res = unsafe { + ffi::knet_log_get_subsystem_id(c_name.as_ptr()) + }; + Ok(res) +} + +/// Get the logging subsystem name given its ID +pub fn log_get_subsystem_name(id: u8) -> Result +{ + let res = unsafe { + ffi::knet_log_get_subsystem_name(id) + }; + crate::string_from_bytes(res, 256) +} + +/// Get the name of a logging level +pub fn log_get_loglevel_id(name: &str) -> Result +{ + let c_name = CString::new(name)?; + let res = unsafe { + ffi::knet_log_get_loglevel_id(c_name.as_ptr()) + }; + Ok(res) +} + +/// Get the ID of a logging level, given its name +pub fn log_get_loglevel_name(id: u8) -> Result +{ + let res = unsafe { + ffi::knet_log_get_loglevel_name(id) + }; + crate::string_from_bytes(res, 256) +} + +/// Logging levels +pub enum LogLevel { + Err, + Warn, + Info, + Debug, +} +impl LogLevel { + pub fn new(level: u8) -> LogLevel { + match level { + 0 => LogLevel::Err, + 1 => LogLevel::Warn, + 2 => LogLevel::Info, + _ => LogLevel::Debug, // 3=Debug, but default anything to it too + } + } + pub fn to_u8(self: &LogLevel) -> u8 + { + match self { + LogLevel::Err => 0, + LogLevel::Warn => 1, + LogLevel::Info => 2, + LogLevel::Debug => 3, + } + } +} +impl fmt::Display for LogLevel { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + LogLevel::Err => write!(f, "Err"), + LogLevel::Warn => write!(f, "Warn"), + LogLevel::Info => write!(f, "Info"), + LogLevel::Debug => write!(f, "Debug"), + } + } +} + +/// Subsystems known to the knet logger +pub enum SubSystem +{ + Common, + Handle, + Host, + Listener, + Link, + Transport, + Crypto, + Compress, + + Filter, + + Dstcache, + Heartbeat, + Pmtud, + Tx, + Rx, + + TranspBase, + TranspLoopback, + TranspUdp, + TranspSctp, + + NssCrypto, + OpensslCrypto, + + Zlibcomp, + Lz4comp, + Lz4hccomp, + Lzo2comp, + Lzmacomp, + Bzip2comp, + Zstdcomp, + + Unknown, +} +impl SubSystem { + pub fn to_u8(self: &SubSystem) -> u8 + { + match self { + SubSystem::Common => ffi::KNET_SUB_COMMON, + SubSystem::Handle => ffi::KNET_SUB_HANDLE, + SubSystem::Host => ffi::KNET_SUB_HOST, + SubSystem::Listener => ffi::KNET_SUB_LISTENER, + SubSystem::Link => ffi::KNET_SUB_LINK, + SubSystem::Transport => ffi::KNET_SUB_TRANSPORT, + SubSystem::Crypto => ffi::KNET_SUB_CRYPTO, + SubSystem::Compress => ffi::KNET_SUB_COMPRESS, + SubSystem::Filter => ffi::KNET_SUB_FILTER, + SubSystem::Dstcache => ffi::KNET_SUB_DSTCACHE, + SubSystem::Heartbeat => ffi::KNET_SUB_HEARTBEAT, + SubSystem::Pmtud => ffi::KNET_SUB_PMTUD, + SubSystem::Tx => ffi::KNET_SUB_TX, + SubSystem::Rx => ffi::KNET_SUB_RX, + SubSystem::TranspBase => ffi::KNET_SUB_TRANSP_BASE, + SubSystem::TranspLoopback => ffi::KNET_SUB_TRANSP_LOOPBACK, + SubSystem::TranspUdp => ffi::KNET_SUB_TRANSP_UDP, + SubSystem::TranspSctp => ffi::KNET_SUB_TRANSP_SCTP, + SubSystem::NssCrypto => ffi::KNET_SUB_NSSCRYPTO, + SubSystem::OpensslCrypto => ffi::KNET_SUB_OPENSSLCRYPTO, + SubSystem::Zlibcomp => ffi::KNET_SUB_ZLIBCOMP, + SubSystem::Lz4comp => ffi::KNET_SUB_LZ4COMP, + SubSystem::Lz4hccomp => ffi::KNET_SUB_LZ4HCCOMP, + SubSystem::Lzo2comp => ffi::KNET_SUB_LZO2COMP, + SubSystem::Lzmacomp => ffi::KNET_SUB_LZMACOMP, + SubSystem::Bzip2comp => ffi::KNET_SUB_BZIP2COMP, + SubSystem::Zstdcomp => ffi::KNET_SUB_ZSTDCOMP, + SubSystem::Unknown => ffi::KNET_SUB_UNKNOWN, + } + } + pub fn new(subsys: u8) -> SubSystem + { + match subsys { + 1 => SubSystem::Unknown, + 2 => SubSystem::Unknown, + _ => SubSystem::Unknown, + } + } + +} + +/// Set the current logging level +pub fn log_set_loglevel(handle: Handle, subsystem: SubSystem, level: LogLevel) -> Result<()> +{ + let c_level = level.to_u8(); + let c_subsys = subsystem.to_u8(); + let res = unsafe { + ffi::knet_log_set_loglevel(handle.knet_handle as ffi::knet_handle_t, + c_subsys, c_level) + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// Get the current logging level +pub fn log_get_loglevel(handle: Handle, subsystem: SubSystem) -> Result +{ + let mut c_level:u8 = 0; + let c_subsys = subsystem.to_u8(); + let res = unsafe { + ffi::knet_log_get_loglevel(handle.knet_handle as ffi::knet_handle_t, + c_subsys, &mut c_level) + }; + if res == 0 { + Ok(LogLevel::new(c_level)) + } else { + Err(Error::last_os_error()) + } +} diff --git a/libknet/bindings/rust/src/lib.rs b/libknet/bindings/rust/src/lib.rs new file mode 100644 index 00000000..ada9dee4 --- /dev/null +++ b/libknet/bindings/rust/src/lib.rs @@ -0,0 +1,165 @@ +// Copyright (C) 2021 Red Hat, Inc. All rights reserved. +// +// Authors: Christine Caulfield +// +// This software licensed under LGPL-2.0+ +// + +//! This crate provides access to the kronosnet library 'libknet' +//! from Rust. They are a fairly thin layer around the actual API calls but with Rust data types +//! and iterators. +//! +//! No more information about knet itself will be provided here, it is expected that if +//! you feel you need access to the knet API calls, you know what they do :) +//! +//! # Example +//! ``` +//! use knet_bindings::knet_bindings as knet; +//! use std::net::{SocketAddr, IpAddr, Ipv4Addr}; +//! use std::thread::spawn; +//! use std::sync::mpsc::Receiver; +//! use std::sync::mpsc::channel; +//! use std::io::{Result, ErrorKind, Error}; +//! use std::{thread, time}; +//! +//! const CHANNEL: i8 = 1; +//! +//! pub fn main() -> Result<()> +//! { +//! let host_id = knet::HostId::new(1); +//! let other_host_id = knet::HostId::new(2); +//! +//! let (log_sender, log_receiver) = channel::(); +//! spawn(move || logging_thread(log_receiver)); +//! +//! let knet_handle = match knet::handle_new(&our_hostid, Some(log_sender), +//! knet::LogLevel::Debug, knet::HandleFlags::NONE) { +//! Ok(h) => h, +//! Err(e) => { +//! return Err(e); +//! } +//! }; +//! +//! if let Err(e) = knet::host_add(knet_handle, &other_hostid) { +//! return Err(e); +//! } +//! if let Err(e) = knet::link_set_config(knet_handle, &other_hostid, 0, +//! knet::TransportId::Udp, +//! &SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8000+(our_hostid.to_u16())), +//! &SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8000+(other_hostid.to_u16())), +//! knet::LinkFlags::NONE) { +//! return Err(e); +//! } +//! if let Err(e) = knet::handle_add_datafd(knet_handle, 0, CHANNEL) { +//! return Err(e); +//! } +//! +//! if let Err(e) = knet::handle_crypto_rx_clear_traffic(knet_handle, knet::RxClearTraffic::Allow) { +//! return Err(e); +//! } +//! +//! if let Err(e) = knet::link_set_enable(knet_handle, &other_hostid, 0, true) { +//! return Err(e); +//! } +//! +//! if let Err(e) = knet::handle_setfwd(knet_handle, true) { +//! return Err(e); +//! } +//! +//! Ok() +//! } +//! + + +mod sys; +pub mod knet_bindings; + +#[macro_use] +extern crate lazy_static; +#[macro_use] +extern crate bitflags; + +use std::os::raw::c_char; +use std::ptr::copy_nonoverlapping; +use std::ffi::CString; +use std::io::{Error, Result, ErrorKind}; + + +// Quick & dirty u8 to boolean +fn u8_to_bool(val: u8) -> bool +{ + val != 0 +} + +fn u32_to_bool(val: u32) -> bool +{ + val != 0 +} + +// General internal routine to copy bytes from a C array into a Rust String +fn string_from_bytes(bytes: *const ::std::os::raw::c_char, max_length: usize) -> Result +{ + let mut newbytes = Vec::::new(); + newbytes.resize(max_length, 0u8); + + // Get length of the string in old-fashioned style + let mut length: usize = 0; + let mut count = 0; + let mut tmpbytes = bytes; + while count < max_length || length == 0 { + if unsafe {*tmpbytes} == 0 && length == 0 { + length = count; + break; + } + count += 1; + tmpbytes = unsafe { tmpbytes.offset(1) } + } + + // Cope with an empty string + if length == 0 { + return Ok(String::new()); + } + + unsafe { + // We need to fully copy it, not shallow copy it. + // Messy casting on both parts of the copy here to get it to work on both signed + // and unsigned char machines + copy_nonoverlapping(bytes as *mut i8, newbytes.as_mut_ptr() as *mut i8, length); + } + + + let cs = CString::new(&newbytes[0..length as usize])?; + + // This is just to convert the error type + match cs.into_string() { + Ok(s) => Ok(s), + Err(_) => Err(Error::new(ErrorKind::Other, "Cannot convert to String")), + } +} + +// As below but always returns a string even if there was an error doing the conversion +fn string_from_bytes_safe(bytes: *const ::std::os::raw::c_char, max_length: usize) -> String +{ + match string_from_bytes(bytes, max_length) { + Ok(s) => s, + Err(_)=> "".to_string() + } +} + +fn string_to_bytes(s: &str, bytes: &mut [c_char]) -> Result<()> +{ + let c_name = match CString::new(s) { + Ok(n) => n, + Err(_) => return Err(Error::new(ErrorKind::Other, "Rust conversion error")), + }; + + if c_name.as_bytes().len() > bytes.len() { + return Err(Error::new(ErrorKind::Other, "String too long")); + } + + unsafe { + // NOTE param order is 'wrong-way round' from C + copy_nonoverlapping(c_name.as_ptr(), bytes.as_mut_ptr(), c_name.as_bytes().len()); + } + Ok(()) +} diff --git a/libknet/bindings/rust/src/sys/mod.rs b/libknet/bindings/rust/src/sys/mod.rs new file mode 100644 index 00000000..7fa0cbeb --- /dev/null +++ b/libknet/bindings/rust/src/sys/mod.rs @@ -0,0 +1,10 @@ +// Copyright (c) 2021 Red Hat, Inc. +// +// All rights reserved. +// +// Author: Christine Caulfield (ccaulfi@redhat.com) +// + +#![allow(non_camel_case_types, non_snake_case, dead_code, improper_ctypes)] + +pub mod libknet; diff --git a/libknet/bindings/rust/tests/Cargo.toml.in b/libknet/bindings/rust/tests/Cargo.toml.in new file mode 100644 index 00000000..8d87851b --- /dev/null +++ b/libknet/bindings/rust/tests/Cargo.toml.in @@ -0,0 +1,26 @@ +# Copyright (C) 2021 Red Hat, Inc. All rights reserved. +# +# Author: Christine Caulfield +# +# This software licensed under GPL-2.0+ + +[package] +name = "knet-bindings-tests" +version = "@libknetrustver@" +authors = ["Christine Caulfield "] +edition = "2018" + +[build-dependencies] +cc = "1.0" +pkg-config = "0.3.19" + +[dependencies] +knet-bindings = { path = ".." } +libc = "0.2.97" + +[[bin]] +name = "knet-test" +test = true +bench = false + + diff --git a/libknet/bindings/rust/tests/Makefile.am b/libknet/bindings/rust/tests/Makefile.am new file mode 100644 index 00000000..b5c31e86 --- /dev/null +++ b/libknet/bindings/rust/tests/Makefile.am @@ -0,0 +1,34 @@ +# +# Copyright (C) 2021 Red Hat, Inc. All rights reserved. +# +# Author: Christine Caulfield +# +# This software licensed under GPL-2.0+ +# + +MAINTAINERCLEANFILES = Makefile.in + +include $(top_srcdir)/build-aux/check.mk +include $(top_srcdir)/build-aux/rust.mk + +EXTRA_DIST = \ + $(RUST_COMMON) \ + $(RUST_SHIP_SRCS) + +RUST_SHIP_SRCS = src/bin/set_plugin_path.c \ + src/bin/knet-test.rs + +check_SCRIPTS = target/$(RUST_TARGET_DIR)/knet-test + +noinst_SCRIPTS = $(check_SCRIPTS) + +if INSTALL_TESTS +testsuitedir = $(TESTDIR) +testsuite_SCRIPTS = $(check_SCRIPTS) +endif + +AM_TESTS_ENVIRONMENT=LD_LIBRARY_PATH="$(abs_top_builddir)/libknet/.libs" + +TESTS = $(check_SCRIPTS) + +clean-local: cargo-clean diff --git a/libknet/bindings/rust/tests/build.rs.in b/libknet/bindings/rust/tests/build.rs.in new file mode 100644 index 00000000..544a3c28 --- /dev/null +++ b/libknet/bindings/rust/tests/build.rs.in @@ -0,0 +1,30 @@ +// Copyright (c) 2021 Red Hat, Inc. +// +// All rights reserved. +// +// Author: Christine Caulfield (ccaulfi@redhat.com) +// + +extern crate pkg_config; + +fn main() { + + // Tell the compiler to use the build-tree libs & headers for compiling + println!("cargo:rustc-link-search=native=../../../.libs/"); + println!("cargo:rustc-link-lib=knet"); + + let lib = pkg_config::probe_library("libqb").unwrap(); + + cc::Build::new() + .file("src/bin/set_plugin_path.c") + .file("@ABSTOPLEVELSRC@/libknet/tests/test-common.c") // for find_plugins_path() + .flag("-Wno-unused-parameter") // Needed for test-common.c to compile cleanly + .include("@ABSTOPLEVELSRC@") // for config.h + .include("@ABSTOPLEVELSRC@/libknet") // for internals.h + .include("@ABSTOPLEVELSRC@/libknet/tests") // for test-common.h + .include("@ABSTOPLEVELBUILD@") // for config.h + .include("@ABSTOPLEVELBUILD@/libknet") // for internals.h + .include("@ABSTOPLEVELBUILD@/libknet/tests") // for test-common.h + .includes(lib.include_paths) + .compile("set_plugin_path"); +} diff --git a/libknet/bindings/rust/tests/src/bin/knet-test.rs b/libknet/bindings/rust/tests/src/bin/knet-test.rs new file mode 100644 index 00000000..24f15e6a --- /dev/null +++ b/libknet/bindings/rust/tests/src/bin/knet-test.rs @@ -0,0 +1,921 @@ +// Testing the Knet Rust APIs +// +// Copyright (c) 2021 Red Hat, Inc. +// +// All rights reserved. +// +// Author: Christine Caulfield (ccaulfi@redhat.com) +// + +use knet_bindings::knet_bindings as knet; +use std::net::{SocketAddr, IpAddr, Ipv4Addr}; +use std::thread::spawn; +use std::sync::mpsc::Receiver; +use std::sync::mpsc::channel; +use std::io::{Result, ErrorKind, Error}; +use std::{thread, time}; + +const CHANNEL: i8 = 1; + +// Dirty C function to set the plugin path for testing (only) +extern { + fn set_plugin_path(knet_h: knet::Handle); +} + +// Callbacks +fn sock_notify_fn(private_data: u64, + datafd: i32, + channel: i8, + txrx: knet::TxRx, + _res: Result<()>) +{ + println!("sock notify called for host {}, datafd: {}, channel: {}, {}", + private_data, datafd, channel, txrx); +} + + +fn link_notify_fn(private_data: u64, + host_id: knet::HostId, + link_id: u8, + connected: bool, + _remote: bool, + _external: bool) +{ + println!("link status notify called ({}) for host {}, linkid: {}, connected: {}", + private_data, host_id.to_u16(), link_id, connected); +} + +fn host_notify_fn(private_data: u64, + host_id: knet::HostId, + connected: bool, + _remote: bool, + _external: bool) +{ + println!("host status notify called ({}) for host {}, connected: {}", + private_data, host_id.to_u16(), connected); +} + +fn pmtud_fn(private_data: u64, data_mtu: u32) { + println!("PMTUD notify: host {}, MTU:{} ", private_data, data_mtu); +} + +fn onwire_fn(private_data: u64, + onwire_min_ver: u8, + onwire_max_ver: u8, + onwire_ver: u8) { + println!("Onwire ver notify for {} : {}/{}/{}", private_data, onwire_min_ver, onwire_max_ver, onwire_ver); +} + +fn filter_fn(private_data: u64, + _outdata: &[u8], + txrx: knet::TxRx, + this_host_id: knet::HostId, + src_host_id: knet::HostId, + channel: &mut i8, + dst_host_ids: &mut Vec) -> knet::FilterDecision +{ + println!("Filter ({}) called {} to {} from {}, channel: {}", + private_data, txrx, this_host_id, src_host_id, channel); + + match txrx { + knet::TxRx::Tx => { + knet::FilterDecision::Multicast + } + knet::TxRx::Rx => { + dst_host_ids.push(this_host_id); + knet::FilterDecision::Unicast + } + } +} + +fn logging_thread(recvr: Receiver) +{ + for i in &recvr { + eprintln!("KNET: {}", i.msg); + } + eprintln!("Logging thread finished"); +} + +fn setup_node(our_hostid: &knet::HostId, other_hostid: &knet::HostId, name: &str) -> Result +{ + let (log_sender, log_receiver) = channel::(); + spawn(move || logging_thread(log_receiver)); + + let knet_handle = match knet::handle_new(&our_hostid, Some(log_sender), + knet::LogLevel::Debug, knet::HandleFlags::NONE) { + Ok(h) => h, + Err(e) => { + println!("Error from handle_new: {}", e); + return Err(e); + } + }; + + // Make sure we use the build-tree plugins if LD_LIBRRAY_PATH points to them + unsafe { + set_plugin_path(knet_handle); + } + + if let Err(e) = knet::host_add(knet_handle, &other_hostid) { + println!("Error from host_add: {}", e); + return Err(e); + } + if let Err(e) = knet::host_set_name(knet_handle, &other_hostid, name) { + println!("Error from host_set_name: {}", e); + return Err(e); + } + + Ok(knet_handle) +} + +// Called by the ACL tests to get a free port for a dynamic link +fn setup_dynamic_link(handle: knet::Handle, hostid: &knet::HostId, link: u8, + lowest_port: u16) -> Result<()> +{ + let mut src_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0); + for p in lowest_port..=65535 { + src_addr.set_port(p); + + if let Err(e) = knet::link_set_config(handle, hostid, link, + knet::TransportId::Udp, + &src_addr, + None, + knet::LinkFlags::NONE) { + if let Some(os_err) = e.raw_os_error() { + if os_err != libc::EADDRINUSE { + println!("Error from link_set_config(dyn): {}", e); + return Err(e); + } + // In use - try the next port number + } + } else { + println!("Dynamic link - Using port {}", p); + return Ok(()) + } + } + Err(Error::new(ErrorKind::Other, "No ports available")) +} + +// This is the bit that configures two links on two handles that talk to each other +// while also making sure they don't clash with anything else on the system +fn setup_links(handle1: knet::Handle, hostid1: &knet::HostId, + handle2: knet::Handle, hostid2: &knet::HostId) -> Result +{ + let mut src_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0); + let mut dst_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0); + for p in 1025..=65534 { + src_addr.set_port(p); + dst_addr.set_port(p+1); + + if let Err(e) = knet::link_set_config(handle1, hostid2, 0, + knet::TransportId::Udp, + &src_addr, + Some(&dst_addr), + knet::LinkFlags::NONE) { + if let Some(os_err) = e.raw_os_error() { + if os_err != libc::EADDRINUSE { + println!("Error from link_set_config(1): {}", e); + return Err(e); + } + // In use - try the next port number + } else { + return Err(Error::new(ErrorKind::Other, "Error returned from link_set_config(1) was not an os_error")); + } + } else { + // Now try the other handle + if let Err(e) = knet::link_set_config(handle2, hostid1, 0, + knet::TransportId::Udp, + &dst_addr, + Some(&src_addr), + knet::LinkFlags::NONE) { + if let Some(os_err) = e.raw_os_error() { + if os_err != libc::EADDRINUSE { + println!("Error from link_set_config(2): {}", e); + return Err(e); + } else { + // In use - clear handle1 and try next pair of ports + knet::link_clear_config(handle1, hostid2, 0)?; + } + } else { + return Err(Error::new(ErrorKind::Other, "Error returned from link_set_config(1) was not an os_error")); + } + } + println!("Bound to ports {} & {}",p, p+1); + return Ok(p+2) + } + } + Err(Error::new(ErrorKind::Other, "No ports available")) +} + +// Finish configuring links +fn configure_link(knet_handle: knet::Handle, our_hostid: &knet::HostId, other_hostid: &knet::HostId) -> Result<()> +{ + if let Err(e) = knet::handle_enable_sock_notify(knet_handle, our_hostid.to_u16() as u64, Some(sock_notify_fn)) { + println!("Error from handle_enable_sock_notify: {}", e); + return Err(e); + } + + if let Err(e) = knet::link_enable_status_change_notify(knet_handle, our_hostid.to_u16() as u64, Some(link_notify_fn)) { + println!("Error from handle_enable_link_notify: {}", e); + return Err(e); + } + if let Err(e) = knet::host_enable_status_change_notify(knet_handle, our_hostid.to_u16() as u64, Some(host_notify_fn)) { + println!("Error from handle_enable_host_notify: {}", e); + return Err(e); + } + if let Err(e) = knet::handle_enable_filter(knet_handle, our_hostid.to_u16() as u64, Some(filter_fn)) { + println!("Error from handle_enable_filter: {}", e); + return Err(e); + } + + if let Err(e) = knet::handle_enable_pmtud_notify(knet_handle, our_hostid.to_u16() as u64, Some(pmtud_fn)) { + println!("Error from handle_enable_pmtud_notify: {}", e); + return Err(e); + } + if let Err(e) = knet::handle_enable_onwire_ver_notify(knet_handle, our_hostid.to_u16() as u64, Some(onwire_fn)) { + println!("Error from handle_enable_onwire_ver_notify: {}", e); + return Err(e); + } + match knet::handle_add_datafd(knet_handle, 0, CHANNEL) { + Ok((fd,chan)) => { + println!("Added datafd, fd={}, channel={}", fd, chan); + }, + Err(e) => { + println!("Error from add_datafd: {}", e); + return Err(e); + } + } + + if let Err(e) = knet::handle_crypto_rx_clear_traffic(knet_handle, knet::RxClearTraffic::Allow) { + println!("Error from handle_crypto_rx_clear_traffic: {}", e); + return Err(e); + } + + if let Err(e) = knet::link_set_enable(knet_handle, &other_hostid, 0, true) { + println!("Error from set_link_enable(true): {}", e); + return Err(e); + } + + if let Err(e) = knet::link_set_ping_timers(knet_handle, &other_hostid, 0, + 500, 1000, 1024) { + println!("Error from set_link_ping_timers: {}", e); + return Err(e); + } + + match knet::link_get_ping_timers(knet_handle, &other_hostid, 0) { + Ok((a,b,c)) => { + if a != 500 || b != 1000 || c != 1024 { + println!("get_link_ping_timers returned wrong values {}, {},{} (s/b 500,1000,1024)", + a,b,c); + return Err(Error::new(ErrorKind::Other, "Error in ping timers")); + } + }, + Err(e) => { + println!("Error from set_link_ping_timers: {}", e); + return Err(e); + } + } + + if let Err(e) = knet::handle_setfwd(knet_handle, true) { + println!("Error from setfwd(true): {}", e); + return Err(e); + } + + // Check status + let data_fd = + match knet::handle_get_datafd(knet_handle, CHANNEL) { + Ok(f) => { + println!("got datafd {} for channel", f); + f + } + Err(e) => { + println!("Error from handle_get_datafd: {}", e); + return Err(e); + } + }; + match knet::handle_get_channel(knet_handle, data_fd) { + Ok(c) => + if c != CHANNEL { + println!("handle_get_channel returned wrong channel ID: {}, {}",c, CHANNEL); + return Err(Error::new(ErrorKind::Other, "Error in handle_get_channel")); + } + Err(e) => { + println!("Error from handle_get_channel: {}", e); + return Err(e); + } + } + + match knet::link_get_enable(knet_handle, other_hostid, 0) { + Ok(b) => if !b { + println!("link not enabled (according to link_get_enable()"); + }, + Err(e) => { + println!("Error from link_get_enable: {}", e); + return Err(e); + } + } + + Ok(()) +} + +fn recv_stuff(handle: knet::Handle, host: knet::HostId) -> Result<()> +{ + let buf = [0u8; 1024]; + + loop { + match knet::recv(handle, &buf, CHANNEL) { + Ok(len) => { + let recv_len = len as usize; + if recv_len == 0 { + break; // EOF?? + } else { + let s = String::from_utf8(buf[0..recv_len].to_vec()).unwrap(); + println!("recvd on {}: {} {:?} {} ", host, recv_len, &buf[0..recv_len], s); + if s == *"QUIT" { + println!("got QUIT on {}, exitting", host); + break; + } + } + } + Err(e) => { + if e.kind() == ErrorKind::WouldBlock { + thread::sleep(time::Duration::from_millis(100)); + } else { + println!("recv failed: {}", e); + return Err(e); + } + } + } + } + Ok(()) +} + + +fn close_handle(handle: knet::Handle, remnode: u16) -> Result<()> +{ + let other_hostid = knet::HostId::new(remnode); + + if let Err(e) = knet::handle_setfwd(handle, false) { + println!("Error from setfwd 1 (false): {}", e); + return Err(e); + } + + let data_fd = + match knet::handle_get_datafd(handle, CHANNEL) { + Ok(f) => { + println!("got datafd {} for channel", f); + f + } + Err(e) => { + println!("Error from handle_get_datafd: {}", e); + return Err(e); + } + }; + if let Err(e) = knet::handle_remove_datafd(handle, data_fd) { + println!("Error from handle_remove_datafd: {}", e); + return Err(e); + } + + if let Err(e) = knet::link_set_enable(handle, &other_hostid, 0, false) { + println!("Error from set_link_enable(false): {}", e); + return Err(e); + } + + if let Err(e) = knet::link_clear_config(handle, &other_hostid, 0) { + println!("clear config failed: {}", e); + return Err(e); + } + + if let Err(e) = knet::host_remove(handle, &other_hostid) { + println!("host remove failed: {}", e); + return Err(e); + } + + if let Err(e) = knet::handle_free(handle) { + println!("handle_free failed: {}", e); + return Err(e); + } + Ok(()) +} + + +fn set_compress(handle: knet::Handle) -> Result<()> +{ + let compress_config = knet::CompressConfig { + compress_model: "zlib".to_string(), + compress_threshold : 10, + compress_level: 1, + }; + if let Err(e) = knet::handle_compress(handle, &compress_config) { + println!("Error from handle_compress: {}", e); + Err(e) + } else { + Ok(()) + } +} + +fn set_crypto(handle: knet::Handle) -> Result<()> +{ + let private_key = [55u8; 2048]; + + // Add some crypto + let crypto_config = knet::CryptoConfig { + crypto_model: "openssl".to_string(), + crypto_cipher_type: "aes256".to_string(), + crypto_hash_type: "sha256".to_string(), + private_key: &private_key, + }; + + if let Err(e) = knet::handle_crypto_set_config(handle, &crypto_config, 1) { + println!("Error from handle_crypto_set_config: {}", e); + return Err(e); + } + + if let Err(e) = knet::handle_crypto_use_config(handle, 1) { + println!("Error from handle_crypto_use_config: {}", e); + return Err(e); + } + + if let Err(e) = knet::handle_crypto_rx_clear_traffic(handle, knet::RxClearTraffic::Disallow) { + println!("Error from handle_crypto_rx_clear_traffic: {}", e); + return Err(e); + } + Ok(()) +} + + +fn send_messages(handle: knet::Handle, send_quit: bool) -> Result<()> +{ + let mut buf : [u8; 20] = [b'0'; 20]; + for i in 0..10 { + buf[i as usize + 1] = i + b'0'; + match knet::send(handle, &buf, CHANNEL) { + Ok(len) => { + if len as usize != buf.len() { + println!("sent {} bytes instead of {}", len, buf.len()); + } + }, + Err(e) => { + println!("send failed: {}", e); + return Err(e); + } + } + } + + // Sleep to allow messages to calm down before we remove the filter + thread::sleep(time::Duration::from_millis(3000)); + if let Err(e) = knet::handle_enable_filter(handle, 0, None) { + println!("Error from handle_enable_filter (disable): {}", e); + return Err(e); + } + + let s = String::from("SYNC TEST").into_bytes(); + if let Err(e) = knet::send_sync(handle, &s, CHANNEL) { + println!("send_sync failed: {}", e); + return Err(e); + } + + if send_quit { + let b = String::from("QUIT").into_bytes(); + match knet::send(handle, &b, CHANNEL) { + Ok(len) => { + if len as usize != b.len() { + println!("sent {} bytes instead of {}", len, b.len()); + } + }, + Err(e) => { + println!("send failed: {}", e); + return Err(e); + } + } + } + Ok(()) +} + +fn test_link_host_list(handle: knet::Handle) -> Result<()> +{ + match knet::host_get_host_list(handle) { + Ok(hosts) => { + for i in &hosts { + print!("host {}: links: ", i); + match knet::link_get_link_list(handle, i) { + Ok(ll) => { + for j in ll { + print!(" {}",j); + } + }, + Err(e) => { + println!("link_get_link_list failed: {}", e); + return Err(e); + } + } + println!(); + } + } + Err(e) => { + println!("link_get_host_list failed: {}", e); + return Err(e); + } + } + Ok(()) +} + +// Try some metadata calls +fn test_metadata_calls(handle: knet::Handle, host: &knet::HostId) -> Result<()> +{ + if let Err(e) = knet::handle_set_threads_timer_res(handle, 190000) { + println!("knet_handle_set_threads_timer_res failed: {:?}", e); + return Err(e); + } + match knet::handle_get_threads_timer_res(handle) { + Ok(v) => { + if v != 190000 { + println!("knet_handle_get_threads_timer_res returned wrong value {}", v); + } + }, + Err(e) => { + println!("knet_handle_set_threads_timer_res failed: {:?}", e); + return Err(e); + } + } + + if let Err(e) = knet::handle_pmtud_set(handle, 1000) { + println!("knet_handle_pmtud_set failed: {:?}", e); + return Err(e); + } + match knet::handle_pmtud_get(handle) { + Ok(v) => { + if v != 1000 { + println!("knet_handle_pmtud_get returned wrong value {} (ALLOWED)", v); + // Don't fail on this, it might not have been set yet + } + }, + Err(e) => { + println!("knet_handle_pmtud_get failed: {:?}", e); + return Err(e); + } + } + + if let Err(e) = knet::handle_pmtud_setfreq(handle, 1000) { + println!("knet_handle_pmtud_setfreq failed: {:?}", e); + return Err(e); + } + match knet::handle_pmtud_getfreq(handle) { + Ok(v) => { + if v != 1000 { + println!("knet_handle_pmtud_getfreq returned wrong value {}", v); + } + }, + Err(e) => { + println!("knet_handle_pmtud_getfreq failed: {:?}", e); + return Err(e); + } + } + + if let Err(e) = knet::handle_set_transport_reconnect_interval(handle, 100) { + println!("knet_handle_set_transport_reconnect_interval failed: {:?}", e); + return Err(e); + } + match knet::handle_get_transport_reconnect_interval(handle) { + Ok(v) => { + if v != 100 { + println!("knet_handle_get_transport_reconnect_interval {}", v); + } + }, + Err(e) => { + println!("knet_handle_get_transport_reconnect_interval failed: {:?}", e); + return Err(e); + } + } + + + if let Err(e) = knet::link_set_pong_count(handle, host, 0, 4) { + println!("knet_link_set_pong_count failed: {:?}", e); + return Err(e); + } + match knet::link_get_pong_count(handle, host, 0) { + Ok(v) => { + if v != 4 { + println!("knet_link_get_pong_count returned wrong value {}", v); + } + }, + Err(e) => { + println!("knet_link_get_pong_count failed: {:?}", e); + return Err(e); + } + } + + if let Err(e) = knet::host_set_policy(handle, host, knet::LinkPolicy::Active) { + println!("knet_host_set_policy failed: {:?}", e); + return Err(e); + } + match knet::host_get_policy(handle, host) { + Ok(v) => { + if v != knet::LinkPolicy::Active { + println!("knet_host_get_policy returned wrong value {}", v); + } + }, + Err(e) => { + println!("knet_host_get_policy failed: {:?}", e); + return Err(e); + } + } + + + if let Err(e) = knet::link_set_priority(handle, host, 0, 5) { + println!("knet_link_set_priority failed: {:?}", e); + return Err(e); + } + match knet::link_get_priority(handle, host, 0) { + Ok(v) => { + if v != 5 { + println!("knet_link_get_priority returned wrong value {}", v); + } + }, + Err(e) => { + println!("knet_link_get_priority failed: {:?}", e); + return Err(e); + } + } + + let name = match knet::host_get_name_by_host_id(handle, host) { + Ok(n) => { + println!("Returned host name is {}", n); + n + }, + Err(e) => { + println!("knet_host_get_name_by_host_id failed: {:?}", e); + return Err(e); + } + }; + match knet::host_get_id_by_host_name(handle, &name) { + Ok(n) => { + println!("Returned host id is {}", n); + if n != *host { + println!("Returned host id is not 2"); + return Err(Error::new(ErrorKind::Other, "Error in get_id_by_host_name")); + } + }, + Err(e) => { + println!("knet_host_get_id_by_host_name failed: {:?}", e); + return Err(e); + } + } + + match knet::link_get_config(handle, host, 0) { + Ok((t, s, d, _f)) => { + println!("Got link config: {}, {:?}, {:?}", t.to_string(),s,d); + }, + Err(e) => { + println!("knet_link_get_config failed: {:?}", e); + return Err(e); + } + } + + // Can't set this to anything different + if let Err(e) = knet::handle_set_onwire_ver(handle, 1) { + println!("knet_link_set_onwire_ver failed: {:?}", e); + return Err(e); + } + + match knet::handle_get_onwire_ver(handle, &host) { + Ok((min, max, ver)) => { + println!("get_onwire_ver: Got onwire ver: {}/{}/{}", min, max, ver); + }, + Err(e) => { + println!("knet_link_get_onwire_ver failed: {:?}", e); + return Err(e); + } + } + + // Logging + match knet::log_get_subsystem_name(3) { + Ok(n) => println!("subsystem name for 3 is {}", n), + Err(e) => { + println!("knet_log_get_subsystem_name failed: {:?}", e); + return Err(e); + } + } + match knet::log_get_subsystem_id("TX") { + Ok(n) => println!("subsystem ID for TX is {}", n), + Err(e) => { + println!("knet_log_get_subsystem_id failed: {:?}", e); + return Err(e); + } + } + match knet::log_get_loglevel_id("DEBUG") { + Ok(n) => println!("loglevel ID for DEBUG is {}", n), + Err(e) => { + println!("knet_log_get_loglevel_id failed: {:?}", e); + return Err(e); + } + } + + match knet::log_get_loglevel_name(1) { + Ok(n) => println!("loglevel name for 1 is {}", n), + Err(e) => { + println!("knet_log_get_loglevel_name failed: {:?}", e); + return Err(e); + } + } + + if let Err(e) = knet::log_set_loglevel(handle, knet::SubSystem::Handle , knet::LogLevel::Debug) { + println!("knet_log_set_loglevel failed: {:?}", e); + return Err(e); + } + match knet::log_get_loglevel(handle, knet::SubSystem::Handle) { + Ok(n) => println!("loglevel for Handle is {}", n), + Err(e) => { + println!("knet_log_get_loglevel failed: {:?}", e); + return Err(e); + } + } + + Ok(()) +} + + +fn test_acl(handle: knet::Handle, host: &knet::HostId, low_port: u16) -> Result<()> +{ + if let Err(e) = knet::handle_enable_access_lists(handle, true) { + println!("Error from handle_enable_access_lists: {:?}", e); + return Err(e); + } + + // Dynamic link for testing ACL APIs (it never gets used) + if let Err(e) = setup_dynamic_link(handle, host, 1, low_port) { + println!("Error from link_set_config (dynamic): {}", e); + return Err(e); + } + + // These ACLs are nonsense on stilts + if let Err(e) = knet::link_add_acl(handle, host, 1, + &SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8003_u16), + &SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8003_u16), + knet::AclCheckType::Address, knet::AclAcceptReject::Reject) { + println!("Error from link_add_acl: {:?}", e); + return Err(e); + } + if let Err(e) = knet::link_insert_acl(handle, host, 1, + 0, + &SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 8004_u16), + &SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)), 8004_u16), + knet::AclCheckType::Address, knet::AclAcceptReject::Reject) { + println!("Error from link_add_acl: {:?}", e); + return Err(e); + } + if let Err(e) = knet::link_rm_acl(handle, host, 1, + &SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8003_u16), + &SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8003_u16), + knet::AclCheckType::Address, knet::AclAcceptReject::Reject) { + println!("Error from link_rm_acl: {:?}", e); + return Err(e); + } + if let Err(e) = knet::link_clear_acl(handle, host, 1) { + println!("Error from link_clear_acl: {:?}", e); + return Err(e); + } + + // Get rid of this link before it messes things up + if let Err(e) = knet::link_clear_config(handle, host, 1) { + println!("clear config (dynamic) failed: {}", e); + return Err(e); + } + + if let Err(e) = knet::handle_enable_access_lists(handle, false) { + println!("Error from handle_enable_access_lists: {:?}", e); + return Err(e); + } + Ok(()) +} + +fn main() -> Result<()> +{ + // Start with some non-handle information + match knet::get_crypto_list() { + Ok(l) => { + print!("Crypto models:"); + for i in &l { + print!(" {}", i.name); + } + println!(); + } + Err(e) => { + println!("link_get_crypto_list failed: {:?}", e); + return Err(e); + } + } + + match knet::get_compress_list() { + Ok(l) => { + print!("Compress models:"); + for i in &l { + print!(" {}", i.name); + } + println!(); + } + Err(e) => { + println!("link_get_compress_list failed: {:?}", e); + return Err(e); + } + } + + match knet::get_transport_list() { + Ok(l) => { + print!("Transports:"); + for i in &l { + print!(" {}", i.name); + } + println!(); + } + Err(e) => { + println!("link_get_transport_list failed: {:?}", e); + return Err(e); + } + } + let host1 = knet::HostId::new(1); + let host2 = knet::HostId::new(2); + + // Now test traffic + let handle1 = setup_node(&host1, &host2, "host2")?; + let handle2 = setup_node(&host2, &host1, "host1")?; + let low_port = setup_links(handle1, &host1, handle2, &host2)?; + configure_link(handle1, &host1, &host2)?; + configure_link(handle2, &host2, &host1)?; + + // Copy stuff for the threads + let handle1_clone = handle1; + let handle2_clone = handle2; + let host1_clone = host1; + let host2_clone = host2; + + // Wait for links to start + thread::sleep(time::Duration::from_millis(10000)); + test_link_host_list(handle1)?; + test_link_host_list(handle2)?; + + // Start recv threads for each handle + let thread_handles = vec![ + spawn(move || recv_stuff(handle1_clone, host1_clone)), + spawn(move || recv_stuff(handle2_clone, host2_clone)) + ]; + + send_messages(handle1, false)?; + send_messages(handle2, false)?; + thread::sleep(time::Duration::from_millis(3000)); + + set_crypto(handle1)?; + set_crypto(handle2)?; + + set_compress(handle1)?; + set_compress(handle2)?; + + thread::sleep(time::Duration::from_millis(3000)); + + send_messages(handle1, true)?; + send_messages(handle2, true)?; + + test_acl(handle1, &host2, low_port)?; + + // Wait for recv threads to finish + for handle in thread_handles { + if let Err(error) = handle.join() { + println!("thread join error: {:?}", error); + } + } + + // Try some statses + match knet::handle_get_stats(handle1) { + Ok(s) => println!("handle stats: {}", s), + Err(e) => { + println!("handle_get_stats failed: {:?}", e); + return Err(e); + } + } + match knet::host_get_status(handle1, &host2) { + Ok(s) => println!("host status: {}", s), + Err(e) => { + println!("host_get_status failed: {:?}", e); + return Err(e); + } + } + match knet::link_get_status(handle1, &host2, 0) { + Ok(s) => println!("link status: {}", s), + Err(e) => { + println!("link_get_status failed: {:?}", e); + return Err(e); + } + } + if let Err(e) = knet::handle_clear_stats(handle1, knet::ClearStats::Handle) { + println!("handle_clear_stats failed: {:?}", e); + return Err(e); + } + + test_metadata_calls(handle1, &knet::HostId::new(2))?; + + close_handle(handle1, 2)?; + close_handle(handle2, 1)?; + + // Sleep to see if log thread dies + thread::sleep(time::Duration::from_millis(3000)); + Ok(()) +} diff --git a/libknet/bindings/rust/tests/src/bin/set_plugin_path.c b/libknet/bindings/rust/tests/src/bin/set_plugin_path.c new file mode 100644 index 00000000..14dda67a --- /dev/null +++ b/libknet/bindings/rust/tests/src/bin/set_plugin_path.c @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2021 Red Hat, Inc. All rights reserved. + * + * Author: Christine Caulfield + * + * This software licensed under GPL-2.0+ + */ + +#include + +#include "test-common.h" +#include "internals.h" +#include "libknet.h" + +// Set the path for compress/crypto plugins when running the test program +void set_plugin_path(knet_handle_t knet_h) +{ + struct knet_handle *handle = (struct knet_handle *)knet_h; + char *plugins_path = find_plugins_path(); + if (plugins_path) { + handle->plugin_path = plugins_path; + } +} diff --git a/libknet/links.c b/libknet/links.c index 91facbe3..d9c932f3 100644 --- a/libknet/links.c +++ b/libknet/links.c @@ -1,1595 +1,1596 @@ /* * Copyright (C) 2012-2021 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 "netutils.h" #include "internals.h" #include "logging.h" #include "links.h" #include "transports.h" #include "host.h" #include "threads_common.h" #include "links_acl.h" int _link_updown(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id, unsigned int enabled, unsigned int connected, unsigned int lock_stats) { struct knet_host *host = knet_h->host_index[host_id]; struct knet_link *link = &host->link[link_id]; int notify_status = link->status.connected; int savederrno = 0; if ((link->status.enabled == enabled) && (link->status.connected == connected)) return 0; if ((link->status.enabled) && (knet_h->link_status_change_notify_fn)) { if (link->status.connected != connected) { notify_status = connected; /* connection state */ } if (!enabled) { notify_status = 0; /* disable == disconnected */ } knet_h->link_status_change_notify_fn( knet_h->link_status_change_notify_fn_private_data, host_id, link_id, notify_status, host->status.remote, host->status.external); } link->status.enabled = enabled; link->status.connected = connected; _host_dstcache_update_async(knet_h, host); if ((link->status.dynconnected) && (!link->status.connected)) { link->status.dynconnected = 0; } if (!connected) { transport_link_is_down(knet_h, link); } if (lock_stats) { savederrno = pthread_mutex_lock(&link->link_stats_mutex); if (savederrno) { log_err(knet_h, KNET_SUB_LINK, "Unable to get stats mutex lock for host %u link %u: %s", host_id, link_id, strerror(savederrno)); errno = savederrno; return -1; } } if (connected) { time(&link->status.stats.last_up_times[link->status.stats.last_up_time_index]); link->status.stats.up_count++; if (++link->status.stats.last_up_time_index >= MAX_LINK_EVENTS) { link->status.stats.last_up_time_index = 0; } } else { time(&link->status.stats.last_down_times[link->status.stats.last_down_time_index]); link->status.stats.down_count++; if (++link->status.stats.last_down_time_index >= MAX_LINK_EVENTS) { link->status.stats.last_down_time_index = 0; } } if (lock_stats) { pthread_mutex_unlock(&link->link_stats_mutex); } return 0; } void _link_clear_stats(knet_handle_t knet_h) { struct knet_host *host; struct knet_link *link; uint32_t host_id; uint8_t link_id; for (host_id = 0; host_id < KNET_MAX_HOST; host_id++) { host = knet_h->host_index[host_id]; if (!host) { continue; } for (link_id = 0; link_id < KNET_MAX_LINK; link_id++) { link = &host->link[link_id]; memset(&link->status.stats, 0, sizeof(struct knet_link_stats)); } } } 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) { int savederrno = 0, err = 0, i; struct knet_host *host; struct knet_link *link; if (!_is_valid_handle(knet_h)) { return -1; } if (link_id >= KNET_MAX_LINK) { errno = EINVAL; return -1; } if (!src_addr) { errno = EINVAL; return -1; } if (dst_addr && (src_addr->ss_family != dst_addr->ss_family)) { log_err(knet_h, KNET_SUB_LINK, "Source address family does not match destination address family"); errno = EINVAL; return -1; } if (transport >= KNET_MAX_TRANSPORTS) { errno = EINVAL; return -1; } savederrno = get_global_wrlock(knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_LINK, "Unable to get write lock: %s", strerror(savederrno)); errno = savederrno; return -1; } if (transport == KNET_TRANSPORT_LOOPBACK && knet_h->host_id != host_id) { log_err(knet_h, KNET_SUB_LINK, "Cannot create loopback link to remote node"); err = -1; savederrno = EINVAL; goto exit_unlock; } if (knet_h->host_id == host_id && knet_h->has_loop_link) { log_err(knet_h, KNET_SUB_LINK, "Cannot create more than 1 link when loopback is active"); err = -1; savederrno = EINVAL; goto exit_unlock; } host = knet_h->host_index[host_id]; if (!host) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "Unable to find host %u: %s", host_id, strerror(savederrno)); goto exit_unlock; } if (transport == KNET_TRANSPORT_LOOPBACK && knet_h->host_id == host_id) { for (i=0; ilink[i].configured) { log_err(knet_h, KNET_SUB_LINK, "Cannot add loopback link when other links are already configured."); err = -1; savederrno = EINVAL; goto exit_unlock; } } } link = &host->link[link_id]; if (link->configured != 0) { err =-1; savederrno = EBUSY; log_err(knet_h, KNET_SUB_LINK, "Host %u link %u is currently configured: %s", host_id, link_id, strerror(savederrno)); goto exit_unlock; } if (link->status.enabled != 0) { err =-1; savederrno = EBUSY; log_err(knet_h, KNET_SUB_LINK, "Host %u link %u is currently in use: %s", host_id, link_id, strerror(savederrno)); goto exit_unlock; } memmove(&link->src_addr, src_addr, sizeof(struct sockaddr_storage)); err = knet_addrtostr(src_addr, sizeof(struct sockaddr_storage), link->status.src_ipaddr, KNET_MAX_HOST_LEN, link->status.src_port, KNET_MAX_PORT_LEN); if (err) { if (err == EAI_SYSTEM) { savederrno = errno; log_warn(knet_h, KNET_SUB_LINK, "Unable to resolve host: %u link: %u source addr/port: %s", host_id, link_id, strerror(savederrno)); } else { savederrno = EINVAL; log_warn(knet_h, KNET_SUB_LINK, "Unable to resolve host: %u link: %u source addr/port: %s", host_id, link_id, gai_strerror(err)); } err = -1; goto exit_unlock; } if (!dst_addr) { link->dynamic = KNET_LINK_DYNIP; } else { link->dynamic = KNET_LINK_STATIC; memmove(&link->dst_addr, dst_addr, sizeof(struct sockaddr_storage)); err = knet_addrtostr(dst_addr, sizeof(struct sockaddr_storage), link->status.dst_ipaddr, KNET_MAX_HOST_LEN, link->status.dst_port, KNET_MAX_PORT_LEN); if (err) { if (err == EAI_SYSTEM) { savederrno = errno; log_warn(knet_h, KNET_SUB_LINK, "Unable to resolve host: %u link: %u destination addr/port: %s", host_id, link_id, strerror(savederrno)); } else { savederrno = EINVAL; log_warn(knet_h, KNET_SUB_LINK, "Unable to resolve host: %u link: %u destination addr/port: %s", host_id, link_id, gai_strerror(err)); } err = -1; goto exit_unlock; } } link->pmtud_crypto_timeout_multiplier = KNET_LINK_PMTUD_CRYPTO_TIMEOUT_MULTIPLIER_MIN; link->pong_count = KNET_LINK_DEFAULT_PONG_COUNT; link->has_valid_mtu = 0; link->ping_interval = KNET_LINK_DEFAULT_PING_INTERVAL * 1000; /* microseconds */ link->pong_timeout = KNET_LINK_DEFAULT_PING_TIMEOUT * 1000; /* microseconds */ link->pong_timeout_backoff = KNET_LINK_PONG_TIMEOUT_BACKOFF; link->pong_timeout_adj = link->pong_timeout * link->pong_timeout_backoff; /* microseconds */ link->latency_max_samples = KNET_LINK_DEFAULT_PING_PRECISION; link->status.stats.latency_samples = 0; link->flags = flags; savederrno = pthread_mutex_init(&link->link_stats_mutex, NULL); if (savederrno) { log_err(knet_h, KNET_SUB_LINK, "Unable to initialize link stats mutex: %s", strerror(savederrno)); err = -1; goto exit_unlock; } if (transport_link_set_config(knet_h, link, transport) < 0) { savederrno = errno; err = -1; goto exit_unlock; } /* * we can only configure default access lists if we know both endpoints * and the protocol uses GENERIC_ACL, otherwise the protocol has * to setup their own access lists above in transport_link_set_config. */ if ((transport_get_acl_type(knet_h, transport) == USE_GENERIC_ACL) && (link->dynamic == KNET_LINK_STATIC)) { log_debug(knet_h, KNET_SUB_LINK, "Configuring default access lists for host: %u link: %u socket: %d", host_id, link_id, link->outsock); if ((check_add(knet_h, link->outsock, transport, -1, &link->dst_addr, &link->dst_addr, CHECK_TYPE_ADDRESS, CHECK_ACCEPT) < 0) && (errno != EEXIST)) { log_warn(knet_h, KNET_SUB_LINK, "Failed to configure default access lists for host: %u link: %u", host_id, link_id); savederrno = errno; err = -1; goto exit_unlock; } } link->configured = 1; log_debug(knet_h, KNET_SUB_LINK, "host: %u link: %u is configured", host_id, link_id); if (transport == KNET_TRANSPORT_LOOPBACK) { knet_h->has_loop_link = 1; knet_h->loop_link = link_id; host->status.reachable = 1; link->status.mtu = KNET_PMTUD_SIZE_V6; } else { /* * calculate the minimum MTU that is safe to use, * based on RFCs and that each network device should * be able to support without any troubles */ if (link->dynamic == KNET_LINK_STATIC) { /* * with static link we can be more precise than using * the generic calc_min_mtu() */ switch (link->dst_addr.ss_family) { case AF_INET6: link->status.mtu = calc_max_data_outlen(knet_h, KNET_PMTUD_MIN_MTU_V6 - (KNET_PMTUD_OVERHEAD_V6 + link->proto_overhead)); break; case AF_INET: link->status.mtu = calc_max_data_outlen(knet_h, KNET_PMTUD_MIN_MTU_V4 - (KNET_PMTUD_OVERHEAD_V4 + link->proto_overhead)); break; } } else { /* * for dynamic links we start with the minimum MTU * possible and PMTUd will kick in immediately * after connection status is 1 */ link->status.mtu = calc_min_mtu(knet_h); } link->has_valid_mtu = 1; } exit_unlock: pthread_rwlock_unlock(&knet_h->global_rwlock); errno = err ? savederrno : 0; return err; } 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) { int savederrno = 0, err = 0; struct knet_host *host; struct knet_link *link; if (!_is_valid_handle(knet_h)) { return -1; } if (link_id >= KNET_MAX_LINK) { errno = EINVAL; return -1; } if (!src_addr) { errno = EINVAL; return -1; } if (!dynamic) { errno = EINVAL; return -1; } if (!transport) { errno = EINVAL; return -1; } if (!flags) { errno = EINVAL; return -1; } savederrno = pthread_rwlock_rdlock(&knet_h->global_rwlock); if (savederrno) { log_err(knet_h, KNET_SUB_LINK, "Unable to get read lock: %s", strerror(savederrno)); errno = savederrno; return -1; } host = knet_h->host_index[host_id]; if (!host) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "Unable to find host %u: %s", host_id, strerror(savederrno)); goto exit_unlock; } link = &host->link[link_id]; if (!link->configured) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "host %u link %u is not configured: %s", host_id, link_id, strerror(savederrno)); goto exit_unlock; } if ((link->dynamic == KNET_LINK_STATIC) && (!dst_addr)) { savederrno = EINVAL; err = -1; goto exit_unlock; } - memmove(src_addr, &link->src_addr, sizeof(struct sockaddr_storage)); + memmove(src_addr, &link->src_addr, sockaddr_len(&link->src_addr)); *transport = link->transport; *flags = link->flags; if (link->dynamic == KNET_LINK_STATIC) { *dynamic = 0; - memmove(dst_addr, &link->dst_addr, sizeof(struct sockaddr_storage)); + memmove(dst_addr, &link->dst_addr, sockaddr_len(&link->dst_addr)); } else { *dynamic = 1; } exit_unlock: pthread_rwlock_unlock(&knet_h->global_rwlock); errno = err ? savederrno : 0; return err; } int knet_link_clear_config(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id) { int savederrno = 0, err = 0; struct knet_host *host; struct knet_link *link; int sock; uint8_t transport; if (!_is_valid_handle(knet_h)) { return -1; } if (link_id >= KNET_MAX_LINK) { errno = EINVAL; return -1; } savederrno = get_global_wrlock(knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_LINK, "Unable to get write lock: %s", strerror(savederrno)); errno = savederrno; return -1; } host = knet_h->host_index[host_id]; if (!host) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "Unable to find host %u: %s", host_id, strerror(savederrno)); goto exit_unlock; } link = &host->link[link_id]; if (link->configured != 1) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "Host %u link %u is not configured: %s", host_id, link_id, strerror(savederrno)); goto exit_unlock; } if (link->status.enabled != 0) { err = -1; savederrno = EBUSY; log_err(knet_h, KNET_SUB_LINK, "Host %u link %u is currently in use: %s", host_id, link_id, strerror(savederrno)); goto exit_unlock; } /* * remove well known access lists here. * After the transport has done clearing the config, * then we can remove any leftover access lists if the link * is no longer in use. */ if ((transport_get_acl_type(knet_h, link->transport) == USE_GENERIC_ACL) && (link->dynamic == KNET_LINK_STATIC)) { if ((check_rm(knet_h, link->outsock, link->transport, &link->dst_addr, &link->dst_addr, CHECK_TYPE_ADDRESS, CHECK_ACCEPT) < 0) && (errno != ENOENT)) { err = -1; savederrno = errno; log_err(knet_h, KNET_SUB_LINK, "Host %u link %u: unable to remove default access list", host_id, link_id); goto exit_unlock; } } /* * cache it for later as we don't know if the transport * will clear link info during clear_config. */ sock = link->outsock; transport = link->transport; if ((transport_link_clear_config(knet_h, link) < 0) && (errno != EBUSY)) { savederrno = errno; err = -1; goto exit_unlock; } /* * remove any other access lists when the socket is no * longer in use by the transport. */ if ((transport_get_acl_type(knet_h, link->transport) == USE_GENERIC_ACL) && (knet_h->knet_transport_fd_tracker[sock].transport == KNET_MAX_TRANSPORTS)) { check_rmall(knet_h, sock, transport); } pthread_mutex_destroy(&link->link_stats_mutex); memset(link, 0, sizeof(struct knet_link)); link->link_id = link_id; if (knet_h->has_loop_link && host_id == knet_h->host_id && link_id == knet_h->loop_link) { knet_h->has_loop_link = 0; if (host->active_link_entries == 0) { host->status.reachable = 0; } } log_debug(knet_h, KNET_SUB_LINK, "host: %u link: %u config has been wiped", host_id, link_id); exit_unlock: pthread_rwlock_unlock(&knet_h->global_rwlock); errno = err ? savederrno : 0; return err; } int knet_link_set_enable(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id, unsigned int enabled) { int savederrno = 0, err = 0; struct knet_host *host; struct knet_link *link; if (!_is_valid_handle(knet_h)) { return -1; } if (link_id >= KNET_MAX_LINK) { errno = EINVAL; return -1; } if (enabled > 1) { errno = EINVAL; return -1; } savederrno = get_global_wrlock(knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_LINK, "Unable to get read lock: %s", strerror(savederrno)); errno = savederrno; return -1; } host = knet_h->host_index[host_id]; if (!host) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "Unable to find host %u: %s", host_id, strerror(savederrno)); goto exit_unlock; } link = &host->link[link_id]; if (!link->configured) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "host %u link %u is not configured: %s", host_id, link_id, strerror(savederrno)); goto exit_unlock; } if (link->status.enabled == enabled) { err = 0; goto exit_unlock; } err = _link_updown(knet_h, host_id, link_id, enabled, link->status.connected, 0); savederrno = errno; if (enabled) { goto exit_unlock; } log_debug(knet_h, KNET_SUB_LINK, "host: %u link: %u is disabled", host_id, link_id); exit_unlock: pthread_rwlock_unlock(&knet_h->global_rwlock); errno = err ? savederrno : 0; return err; } int knet_link_get_enable(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id, unsigned int *enabled) { int savederrno = 0, err = 0; struct knet_host *host; struct knet_link *link; if (!_is_valid_handle(knet_h)) { return -1; } if (link_id >= KNET_MAX_LINK) { errno = EINVAL; return -1; } if (!enabled) { errno = EINVAL; return -1; } savederrno = pthread_rwlock_rdlock(&knet_h->global_rwlock); if (savederrno) { log_err(knet_h, KNET_SUB_LINK, "Unable to get read lock: %s", strerror(savederrno)); errno = savederrno; return -1; } host = knet_h->host_index[host_id]; if (!host) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "Unable to find host %u: %s", host_id, strerror(savederrno)); goto exit_unlock; } link = &host->link[link_id]; if (!link->configured) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "host %u link %u is not configured: %s", host_id, link_id, strerror(savederrno)); goto exit_unlock; } *enabled = link->status.enabled; exit_unlock: pthread_rwlock_unlock(&knet_h->global_rwlock); errno = err ? savederrno : 0; return err; } 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) { int savederrno = 0, err = 0; struct knet_host *host; struct knet_link *link; if (!_is_valid_handle(knet_h)) { return -1; } if (link_id >= KNET_MAX_LINK) { errno = EINVAL; return -1; } if (pong_count < 1) { errno = EINVAL; return -1; } savederrno = get_global_wrlock(knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_LINK, "Unable to get write lock: %s", strerror(savederrno)); errno = savederrno; return -1; } host = knet_h->host_index[host_id]; if (!host) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "Unable to find host %u: %s", host_id, strerror(savederrno)); goto exit_unlock; } link = &host->link[link_id]; if (!link->configured) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "host %u link %u is not configured: %s", host_id, link_id, strerror(savederrno)); goto exit_unlock; } link->pong_count = pong_count; log_debug(knet_h, KNET_SUB_LINK, "host: %u link: %u pong count update: %u", host_id, link_id, link->pong_count); exit_unlock: pthread_rwlock_unlock(&knet_h->global_rwlock); errno = err ? savederrno : 0; return err; } 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) { int savederrno = 0, err = 0; struct knet_host *host; struct knet_link *link; if (!_is_valid_handle(knet_h)) { return -1; } if (link_id >= KNET_MAX_LINK) { errno = EINVAL; return -1; } if (!pong_count) { errno = EINVAL; return -1; } savederrno = pthread_rwlock_rdlock(&knet_h->global_rwlock); if (savederrno) { log_err(knet_h, KNET_SUB_LINK, "Unable to get read lock: %s", strerror(savederrno)); errno = savederrno; return -1; } host = knet_h->host_index[host_id]; if (!host) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "Unable to find host %u: %s", host_id, strerror(savederrno)); goto exit_unlock; } link = &host->link[link_id]; if (!link->configured) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "host %u link %u is not configured: %s", host_id, link_id, strerror(savederrno)); goto exit_unlock; } *pong_count = link->pong_count; exit_unlock: pthread_rwlock_unlock(&knet_h->global_rwlock); errno = err ? savederrno : 0; return err; } 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) { int savederrno = 0, err = 0; struct knet_host *host; struct knet_link *link; if (!_is_valid_handle(knet_h)) { return -1; } if (link_id >= KNET_MAX_LINK) { errno = EINVAL; return -1; } if (!interval) { errno = EINVAL; return -1; } if (!timeout) { errno = ENOSYS; return -1; } if (!precision) { errno = EINVAL; return -1; } savederrno = get_global_wrlock(knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_LINK, "Unable to get write lock: %s", strerror(savederrno)); errno = savederrno; return -1; } host = knet_h->host_index[host_id]; if (!host) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "Unable to find host %u: %s", host_id, strerror(savederrno)); goto exit_unlock; } link = &host->link[link_id]; if (!link->configured) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "host %u link %u is not configured: %s", host_id, link_id, strerror(savederrno)); goto exit_unlock; } link->ping_interval = interval * 1000; /* microseconds */ link->pong_timeout = timeout * 1000; /* microseconds */ link->latency_max_samples = precision; log_debug(knet_h, KNET_SUB_LINK, "host: %u link: %u timeout update - interval: %llu timeout: %llu precision: %u", host_id, link_id, link->ping_interval, link->pong_timeout, precision); exit_unlock: pthread_rwlock_unlock(&knet_h->global_rwlock); errno = err ? savederrno : 0; return err; } 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) { int savederrno = 0, err = 0; struct knet_host *host; struct knet_link *link; if (!_is_valid_handle(knet_h)) { return -1; } if (link_id >= KNET_MAX_LINK) { errno = EINVAL; return -1; } if (!interval) { errno = EINVAL; return -1; } if (!timeout) { errno = EINVAL; return -1; } if (!precision) { errno = EINVAL; return -1; } savederrno = pthread_rwlock_rdlock(&knet_h->global_rwlock); if (savederrno) { log_err(knet_h, KNET_SUB_LINK, "Unable to get read lock: %s", strerror(savederrno)); errno = savederrno; return -1; } host = knet_h->host_index[host_id]; if (!host) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "Unable to find host %u: %s", host_id, strerror(savederrno)); goto exit_unlock; } link = &host->link[link_id]; if (!link->configured) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "host %u link %u is not configured: %s", host_id, link_id, strerror(savederrno)); goto exit_unlock; } *interval = link->ping_interval / 1000; /* microseconds */ *timeout = link->pong_timeout / 1000; *precision = link->latency_max_samples; exit_unlock: pthread_rwlock_unlock(&knet_h->global_rwlock); errno = err ? savederrno : 0; return err; } int knet_link_set_priority(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id, uint8_t priority) { int savederrno = 0, err = 0; struct knet_host *host; struct knet_link *link; uint8_t old_priority; if (!_is_valid_handle(knet_h)) { return -1; } if (link_id >= KNET_MAX_LINK) { errno = EINVAL; return -1; } savederrno = get_global_wrlock(knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_LINK, "Unable to get write lock: %s", strerror(savederrno)); errno = savederrno; return -1; } host = knet_h->host_index[host_id]; if (!host) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "Unable to find host %u: %s", host_id, strerror(savederrno)); goto exit_unlock; } link = &host->link[link_id]; if (!link->configured) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "host %u link %u is not configured: %s", host_id, link_id, strerror(savederrno)); goto exit_unlock; } old_priority = link->priority; if (link->priority == priority) { err = 0; goto exit_unlock; } link->priority = priority; if (_host_dstcache_update_sync(knet_h, host)) { savederrno = errno; log_debug(knet_h, KNET_SUB_LINK, "Unable to update link priority (host: %u link: %u priority: %u): %s", host_id, link_id, link->priority, strerror(savederrno)); link->priority = old_priority; err = -1; goto exit_unlock; } log_debug(knet_h, KNET_SUB_LINK, "host: %u link: %u priority set to: %u", host_id, link_id, link->priority); exit_unlock: pthread_rwlock_unlock(&knet_h->global_rwlock); errno = err ? savederrno : 0; return err; } int knet_link_get_priority(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id, uint8_t *priority) { int savederrno = 0, err = 0; struct knet_host *host; struct knet_link *link; if (!_is_valid_handle(knet_h)) { return -1; } if (link_id >= KNET_MAX_LINK) { errno = EINVAL; return -1; } if (!priority) { errno = EINVAL; return -1; } savederrno = pthread_rwlock_rdlock(&knet_h->global_rwlock); if (savederrno) { log_err(knet_h, KNET_SUB_LINK, "Unable to get read lock: %s", strerror(savederrno)); errno = savederrno; return -1; } host = knet_h->host_index[host_id]; if (!host) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "Unable to find host %u: %s", host_id, strerror(savederrno)); goto exit_unlock; } link = &host->link[link_id]; if (!link->configured) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "host %u link %u is not configured: %s", host_id, link_id, strerror(savederrno)); goto exit_unlock; } *priority = link->priority; exit_unlock: pthread_rwlock_unlock(&knet_h->global_rwlock); errno = err ? savederrno : 0; return err; } 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) { int savederrno = 0, err = 0, i, count = 0; struct knet_host *host; struct knet_link *link; if (!_is_valid_handle(knet_h)) { return -1; } if (!link_ids) { errno = EINVAL; return -1; } if (!link_ids_entries) { errno = EINVAL; return -1; } savederrno = pthread_rwlock_rdlock(&knet_h->global_rwlock); if (savederrno) { log_err(knet_h, KNET_SUB_LINK, "Unable to get read lock: %s", strerror(savederrno)); errno = savederrno; return -1; } host = knet_h->host_index[host_id]; if (!host) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "Unable to find host %u: %s", host_id, strerror(savederrno)); goto exit_unlock; } for (i = 0; i < KNET_MAX_LINK; i++) { link = &host->link[i]; if (!link->configured) { continue; } link_ids[count] = i; count++; } *link_ids_entries = count; exit_unlock: pthread_rwlock_unlock(&knet_h->global_rwlock); errno = err ? savederrno : 0; return err; } 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) { int savederrno = 0, err = 0; struct knet_host *host; struct knet_link *link; if (!_is_valid_handle(knet_h)) { return -1; } if (link_id >= KNET_MAX_LINK) { errno = EINVAL; return -1; } if (!status) { errno = EINVAL; return -1; } savederrno = pthread_rwlock_rdlock(&knet_h->global_rwlock); if (savederrno) { log_err(knet_h, KNET_SUB_LINK, "Unable to get read lock: %s", strerror(savederrno)); errno = savederrno; return -1; } host = knet_h->host_index[host_id]; if (!host) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "Unable to find host %u: %s", host_id, strerror(savederrno)); goto exit_unlock; } link = &host->link[link_id]; if (!link->configured) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "host %u link %u is not configured: %s", host_id, link_id, strerror(savederrno)); goto exit_unlock; } savederrno = pthread_mutex_lock(&link->link_stats_mutex); if (savederrno) { log_err(knet_h, KNET_SUB_LINK, "Unable to get stats mutex lock for host %u link %u: %s", host_id, link_id, strerror(savederrno)); err = -1; goto exit_unlock; } memmove(status, &link->status, struct_size); pthread_mutex_unlock(&link->link_stats_mutex); /* Calculate totals - no point in doing this on-the-fly */ status->stats.rx_total_packets = status->stats.rx_data_packets + status->stats.rx_ping_packets + status->stats.rx_pong_packets + status->stats.rx_pmtu_packets; status->stats.tx_total_packets = status->stats.tx_data_packets + status->stats.tx_ping_packets + status->stats.tx_pong_packets + status->stats.tx_pmtu_packets; status->stats.rx_total_bytes = status->stats.rx_data_bytes + status->stats.rx_ping_bytes + status->stats.rx_pong_bytes + status->stats.rx_pmtu_bytes; status->stats.tx_total_bytes = status->stats.tx_data_bytes + status->stats.tx_ping_bytes + status->stats.tx_pong_bytes + status->stats.tx_pmtu_bytes; status->stats.tx_total_errors = status->stats.tx_data_errors + status->stats.tx_ping_errors + status->stats.tx_pong_errors + status->stats.tx_pmtu_errors; status->stats.tx_total_retries = status->stats.tx_data_retries + status->stats.tx_ping_retries + status->stats.tx_pong_retries + status->stats.tx_pmtu_retries; /* Tell the caller our full size in case they have an old version */ status->size = sizeof(struct knet_link_status); exit_unlock: pthread_rwlock_unlock(&knet_h->global_rwlock); errno = err ? savederrno : 0; return err; } int knet_link_enable_status_change_notify(knet_handle_t knet_h, void *link_status_change_notify_fn_private_data, void (*link_status_change_notify_fn) ( void *private_data, knet_node_id_t host_id, uint8_t link_id, uint8_t connected, uint8_t remote, uint8_t external)) { 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_LINK, "Unable to get write lock: %s", strerror(savederrno)); errno = savederrno; return -1; } knet_h->link_status_change_notify_fn_private_data = link_status_change_notify_fn_private_data; knet_h->link_status_change_notify_fn = link_status_change_notify_fn; if (knet_h->link_status_change_notify_fn) { log_debug(knet_h, KNET_SUB_LINK, "link_status_change_notify_fn enabled"); } else { log_debug(knet_h, KNET_SUB_LINK, "link_status_change_notify_fn disabled"); } pthread_rwlock_unlock(&knet_h->global_rwlock); errno = 0; return 0; } 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) { int savederrno = 0, err = 0; struct knet_host *host; struct knet_link *link; if (!_is_valid_handle(knet_h)) { return -1; } if (!ss1) { errno = EINVAL; return -1; } if ((type != CHECK_TYPE_ADDRESS) && (type != CHECK_TYPE_MASK) && (type != CHECK_TYPE_RANGE)) { errno = EINVAL; return -1; } if ((acceptreject != CHECK_ACCEPT) && (acceptreject != CHECK_REJECT)) { errno = EINVAL; return -1; } if ((type != CHECK_TYPE_ADDRESS) && (!ss2)) { errno = EINVAL; return -1; } if ((type == CHECK_TYPE_RANGE) && (ss1->ss_family != ss2->ss_family)) { errno = EINVAL; return -1; } if (link_id >= KNET_MAX_LINK) { errno = EINVAL; return -1; } savederrno = get_global_wrlock(knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_LINK, "Unable to get write lock: %s", strerror(savederrno)); errno = savederrno; return -1; } host = knet_h->host_index[host_id]; if (!host) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "Unable to find host %u: %s", host_id, strerror(savederrno)); goto exit_unlock; } link = &host->link[link_id]; if (!link->configured) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "host %u link %u is not configured: %s", host_id, link_id, strerror(savederrno)); goto exit_unlock; } if (link->dynamic != KNET_LINK_DYNIP) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "host %u link %u is a point to point connection: %s", host_id, link_id, strerror(savederrno)); goto exit_unlock; } err = check_add(knet_h, transport_link_get_acl_fd(knet_h, link), link->transport, -1, ss1, ss2, type, acceptreject); savederrno = errno; exit_unlock: pthread_rwlock_unlock(&knet_h->global_rwlock); errno = savederrno; return err; } 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) { int savederrno = 0, err = 0; struct knet_host *host; struct knet_link *link; if (!_is_valid_handle(knet_h)) { return -1; } if (!ss1) { errno = EINVAL; return -1; } if ((type != CHECK_TYPE_ADDRESS) && (type != CHECK_TYPE_MASK) && (type != CHECK_TYPE_RANGE)) { errno = EINVAL; return -1; } if ((acceptreject != CHECK_ACCEPT) && (acceptreject != CHECK_REJECT)) { errno = EINVAL; return -1; } if ((type != CHECK_TYPE_ADDRESS) && (!ss2)) { errno = EINVAL; return -1; } if ((type == CHECK_TYPE_RANGE) && (ss1->ss_family != ss2->ss_family)) { errno = EINVAL; return -1; } if (link_id >= KNET_MAX_LINK) { errno = EINVAL; return -1; } savederrno = get_global_wrlock(knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_LINK, "Unable to get write lock: %s", strerror(savederrno)); errno = savederrno; return -1; } host = knet_h->host_index[host_id]; if (!host) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "Unable to find host %u: %s", host_id, strerror(savederrno)); goto exit_unlock; } link = &host->link[link_id]; if (!link->configured) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "host %u link %u is not configured: %s", host_id, link_id, strerror(savederrno)); goto exit_unlock; } if (link->dynamic != KNET_LINK_DYNIP) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "host %u link %u is a point to point connection: %s", host_id, link_id, strerror(savederrno)); goto exit_unlock; } err = check_add(knet_h, transport_link_get_acl_fd(knet_h, link), link->transport, index, ss1, ss2, type, acceptreject); savederrno = errno; exit_unlock: pthread_rwlock_unlock(&knet_h->global_rwlock); errno = savederrno; return err; } 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) { int savederrno = 0, err = 0; struct knet_host *host; struct knet_link *link; if (!_is_valid_handle(knet_h)) { return -1; } if (!ss1) { errno = EINVAL; return -1; } if ((type != CHECK_TYPE_ADDRESS) && (type != CHECK_TYPE_MASK) && (type != CHECK_TYPE_RANGE)) { errno = EINVAL; return -1; } if ((acceptreject != CHECK_ACCEPT) && (acceptreject != CHECK_REJECT)) { errno = EINVAL; return -1; } if ((type != CHECK_TYPE_ADDRESS) && (!ss2)) { errno = EINVAL; return -1; } if ((type == CHECK_TYPE_RANGE) && (ss1->ss_family != ss2->ss_family)) { errno = EINVAL; return -1; } if (link_id >= KNET_MAX_LINK) { errno = EINVAL; return -1; } savederrno = get_global_wrlock(knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_LINK, "Unable to get write lock: %s", strerror(savederrno)); errno = savederrno; return -1; } host = knet_h->host_index[host_id]; if (!host) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "Unable to find host %u: %s", host_id, strerror(savederrno)); goto exit_unlock; } link = &host->link[link_id]; if (!link->configured) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "host %u link %u is not configured: %s", host_id, link_id, strerror(savederrno)); goto exit_unlock; } if (link->dynamic != KNET_LINK_DYNIP) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "host %u link %u is a point to point connection: %s", host_id, link_id, strerror(savederrno)); goto exit_unlock; } err = check_rm(knet_h, transport_link_get_acl_fd(knet_h, link), link->transport, ss1, ss2, type, acceptreject); savederrno = errno; exit_unlock: pthread_rwlock_unlock(&knet_h->global_rwlock); errno = savederrno; return err; } int knet_link_clear_acl(knet_handle_t knet_h, knet_node_id_t host_id, uint8_t link_id) { int savederrno = 0, err = 0; struct knet_host *host; struct knet_link *link; if (!_is_valid_handle(knet_h)) { return -1; } if (link_id >= KNET_MAX_LINK) { errno = EINVAL; return -1; } savederrno = get_global_wrlock(knet_h); if (savederrno) { log_err(knet_h, KNET_SUB_LINK, "Unable to get write lock: %s", strerror(savederrno)); errno = savederrno; return -1; } host = knet_h->host_index[host_id]; if (!host) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "Unable to find host %u: %s", host_id, strerror(savederrno)); goto exit_unlock; } link = &host->link[link_id]; if (!link->configured) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "host %u link %u is not configured: %s", host_id, link_id, strerror(savederrno)); goto exit_unlock; } if (link->dynamic != KNET_LINK_DYNIP) { err = -1; savederrno = EINVAL; log_err(knet_h, KNET_SUB_LINK, "host %u link %u is a point to point connection: %s", host_id, link_id, strerror(savederrno)); goto exit_unlock; } check_rmall(knet_h, transport_link_get_acl_fd(knet_h, link), link->transport); exit_unlock: pthread_rwlock_unlock(&knet_h->global_rwlock); errno = savederrno; return err; } diff --git a/libknet/tests/api-test-coverage b/libknet/tests/api-test-coverage index d30c2ff9..c3dbc1a3 100755 --- a/libknet/tests/api-test-coverage +++ b/libknet/tests/api-test-coverage @@ -1,89 +1,164 @@ #!/bin/sh # # Copyright (C) 2016-2021 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')" 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 numapicalls=$((numapicalls + 1)) if [ -f $srcdir/api_${i}.c ]; then found=$((found + 1)) else missing=$((missing + 1)) echo "MISSING: $i" fi done +# Check Rust bindings coverage +rust_found=0 +rust_missing=0 +# These are implemented directly in the Rust code +deliberately_missing="knet_strtoaddr knet_addrtostr knet_get_transport_name_by_id knet_get_transport_id_by_name" +rustapicalls=$numapicalls +for i in $headerapicalls; do + rustcall=`echo $i|awk '{print substr($0, 6)}'` + grep "^pub fn ${rustcall}(" $1/libknet/bindings/rust/src/knet_bindings.rs > /dev/null 2>/dev/null + if [ $? = 0 ] + then + rust_found=$((rust_found+1)) + else + echo $deliberately_missing | grep $i 2>/dev/null >/dev/null + if [ $? != 0 ] + then + echo "$i Missing from Rust API" + rust_missing=$((rust_missing+1)) + else + rustapicalls=$((rustapicalls-1)) + fi + fi +done + + + +# Check Rust test coverage +rust_test_found=0 +rust_test_missing=0 +deliberately_missing="knet_strtoaddr knet_addrtostr knet_get_transport_name_by_id knet_get_transport_id_by_name" +rust_testapicalls=$numapicalls +for i in $headerapicalls; do + rustcall=`echo $i|awk '{print substr($0, 6)}'` + grep "knet::${rustcall}(" $1/libknet/bindings/rust/tests/src/bin/knet-test.rs > /dev/null 2>/dev/null + if [ $? = 0 ] + then + rust_test_found=$((rust_test_found+1)) + else + echo $deliberately_missing | grep $i 2>/dev/null >/dev/null + if [ $? != 0 ] + then + echo "$i Missing from Rust test" + rust_test_missing=$((rust_test_missing+1)) + else + rust_testapicalls=$((rust_testapicalls-1)) + fi + fi +done + + +echo "" 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%" } + +echo +echo "Rust API Summary" +echo "----------------" +echo "Found : $rust_found" +echo "Missing : $rust_missing" +echo "Total : $rustapicalls" +which bc > /dev/null 2>&1 && { + coverage=$(echo "scale=3; $rust_found / $rustapicalls * 100" | bc -l) + echo "Coverage: $coverage%" +} + + +echo +echo "Rust test Summary" +echo "-----------------" +echo "Found : $rust_test_found" +echo "Missing : $rust_test_missing" +echo "Total : $rustapicalls" +which bc > /dev/null 2>&1 && { + coverage=$(echo "scale=3; $rust_test_found / $rust_testapicalls * 100" | bc -l) + echo "Coverage: $coverage%" +} + exit 0 diff --git a/libknet/tests/test-common.c b/libknet/tests/test-common.c index dea112c8..4e0c827f 100644 --- a/libknet/tests/test-common.c +++ b/libknet/tests/test-common.c @@ -1,969 +1,969 @@ /* * Copyright (C) 2016-2021 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 #include #include "libknet.h" #include "test-common.h" static pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER; static int log_init = 0; static pthread_mutex_t log_thread_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_t log_thread; static int log_thread_init = 0; static int log_fds[2]; struct log_thread_data { int logfd; FILE *std; }; static struct log_thread_data data; static char plugin_path[PATH_MAX]; static int _read_pipe(int fd, char **file, size_t *length) { char buf[4096]; int n; int done = 0; *file = NULL; *length = 0; memset(buf, 0, sizeof(buf)); while (!done) { n = read(fd, buf, sizeof(buf)); if (n < 0) { if (errno == EINTR) continue; if (*file) free(*file); return n; } if (n == 0 && (!*length)) return 0; if (n == 0) done = 1; if (*file) *file = realloc(*file, (*length) + n + done); else *file = malloc(n + done); if (!*file) return -1; memmove((*file) + (*length), buf, n); *length += (done + n); } /* Null terminator */ (*file)[(*length) - 1] = 0; return 0; } int execute_shell(const char *command, char **error_string) { pid_t pid; int status, err = 0; int fd[2]; size_t size = 0; if ((command == NULL) || (!error_string)) { errno = EINVAL; return FAIL; } *error_string = NULL; err = pipe(fd); if (err) goto out_clean; pid = fork(); if (pid < 0) { err = pid; goto out_clean; } if (pid) { /* parent */ close(fd[1]); err = _read_pipe(fd[0], error_string, &size); if (err) goto out_clean0; waitpid(pid, &status, 0); if (!WIFEXITED(status)) { err = -1; goto out_clean0; } if (WIFEXITED(status) && WEXITSTATUS(status) != 0) { err = WEXITSTATUS(status); goto out_clean0; } goto out_clean0; } else { /* child */ close(0); close(1); close(2); close(fd[0]); dup2(fd[1], 1); dup2(fd[1], 2); close(fd[1]); execlp("/bin/sh", "/bin/sh", "-c", command, NULL); exit(FAIL); } out_clean: close(fd[1]); out_clean0: close(fd[0]); return err; } int is_memcheck(void) { char *val; val = getenv("KNETMEMCHECK"); if (val) { if (!strncmp(val, "yes", 3)) { return 1; } } return 0; } int is_helgrind(void) { char *val; val = getenv("KNETHELGRIND"); if (val) { if (!strncmp(val, "yes", 3)) { return 1; } } return 0; } void set_scheduler(int policy) { struct sched_param sched_param; int err; err = sched_get_priority_max(policy); if (err < 0) { printf("Could not get maximum scheduler priority\n"); exit(FAIL); } sched_param.sched_priority = err; err = sched_setscheduler(0, policy, &sched_param); if (err < 0) { printf("Could not set priority\n"); exit(FAIL); } return; } int setup_logpipes(int *logfds) { if (pipe2(logfds, O_CLOEXEC | O_NONBLOCK) < 0) { printf("Unable to setup logging pipe\n"); exit(FAIL); } return PASS; } void close_logpipes(int *logfds) { close(logfds[0]); logfds[0] = 0; close(logfds[1]); logfds[1] = 0; } void flush_logs(int logfd, FILE *std) { struct knet_log_msg msg; int len; while (1) { len = read(logfd, &msg, sizeof(msg)); if (len != sizeof(msg)) { /* * clear errno to avoid incorrect propagation */ errno = 0; return; } if (!msg.knet_h) { /* * this is harsh but this function is void * and it is used also inside log_thread. * this is the easiest to get out with an error */ fprintf(std, "NO HANDLE INFO IN LOG MSG!!\n"); abort(); } msg.msg[sizeof(msg.msg) - 1] = 0; fprintf(std, "[knet: %p]: [%s] %s: %.*s\n", msg.knet_h, knet_log_get_loglevel_name(msg.msglevel), knet_log_get_subsystem_name(msg.subsystem), KNET_MAX_LOG_MSG_SIZE, msg.msg); } } static void *_logthread(void *args) { while (1) { int num; struct timeval tv = { 60, 0 }; fd_set rfds; FD_ZERO(&rfds); FD_SET(data.logfd, &rfds); num = select(FD_SETSIZE, &rfds, NULL, NULL, &tv); if (num < 0) { fprintf(data.std, "Unable select over logfd!\nHALTING LOGTHREAD!\n"); return NULL; } if (num == 0) { fprintf(data.std, "[knet]: No logs in the last 60 seconds\n"); continue; } if (FD_ISSET(data.logfd, &rfds)) { flush_logs(data.logfd, data.std); } } } int start_logthread(int logfd, FILE *std) { int savederrno = 0; savederrno = pthread_mutex_lock(&log_thread_mutex); if (savederrno) { printf("Unable to get log_thread mutex lock\n"); return -1; } if (!log_thread_init) { data.logfd = logfd; data.std = std; savederrno = pthread_create(&log_thread, 0, _logthread, NULL); if (savederrno) { printf("Unable to start logging thread: %s\n", strerror(savederrno)); pthread_mutex_unlock(&log_thread_mutex); return -1; } log_thread_init = 1; } pthread_mutex_unlock(&log_thread_mutex); return 0; } int stop_logthread(void) { int savederrno = 0; void *retval; savederrno = pthread_mutex_lock(&log_thread_mutex); if (savederrno) { printf("Unable to get log_thread mutex lock\n"); return -1; } if (log_thread_init) { pthread_cancel(log_thread); pthread_join(log_thread, &retval); log_thread_init = 0; } pthread_mutex_unlock(&log_thread_mutex); return 0; } static void stop_logging(void) { stop_logthread(); flush_logs(log_fds[0], stdout); close_logpipes(log_fds); } int start_logging(FILE *std) { int savederrno = 0; savederrno = pthread_mutex_lock(&log_mutex); if (savederrno) { printf("Unable to get log_mutex lock\n"); return -1; } if (!log_init) { setup_logpipes(log_fds); if (atexit(&stop_logging) != 0) { printf("Unable to register atexit handler to stop logging: %s\n", strerror(errno)); exit(FAIL); } if (start_logthread(log_fds[0], std) < 0) { exit(FAIL); } log_init = 1; } pthread_mutex_unlock(&log_mutex); return log_fds[1]; } static int dir_filter(const struct dirent *dname) { if ( (strcmp(dname->d_name + strlen(dname->d_name)-3, ".so") == 0) && ((strncmp(dname->d_name,"crypto", 6) == 0) || (strncmp(dname->d_name,"compress", 8) == 0))) { return 1; } return 0; } /* Make sure the proposed plugin path has at least 1 of each plugin available - just as a sanity check really */ static int contains_plugins(char *path) { struct dirent **namelist; int n,i; size_t j; struct knet_compress_info compress_list[256]; struct knet_crypto_info crypto_list[256]; size_t num_compress, num_crypto; size_t compress_found = 0; size_t crypto_found = 0; if (knet_get_compress_list(compress_list, &num_compress) == -1) { return 0; } if (knet_get_crypto_list(crypto_list, &num_crypto) == -1) { return 0; } n = scandir(path, &namelist, dir_filter, alphasort); if (n == -1) { return 0; } /* Look for plugins in the list */ for (i=0; id_name) >= 7 && strncmp(crypto_list[j].name, namelist[i]->d_name+7, strlen(crypto_list[j].name)) == 0) { crypto_found++; } } for (j=0; jd_name) >= 9 && strncmp(compress_list[j].name, namelist[i]->d_name+9, strlen(compress_list[j].name)) == 0) { compress_found++; } } free(namelist[i]); } free(namelist); /* If at least one plugin was found (or none were built) */ if ((crypto_found || num_crypto == 0) && (compress_found || num_compress == 0)) { return 1; } else { return 0; } } /* libtool sets LD_LIBRARY_PATH to the build tree when running test in-tree */ -static char *find_plugins_path(void) +char *find_plugins_path(void) { char *ld_libs_env = getenv("LD_LIBRARY_PATH"); if (ld_libs_env) { char *ld_libs = strdup(ld_libs_env); char *str = strtok(ld_libs, ":"); while (str) { if (contains_plugins(str)) { strncpy(plugin_path, str, sizeof(plugin_path)-1); free(ld_libs); printf("Using plugins from %s\n", plugin_path); return plugin_path; } str = strtok(NULL, ":"); } free(ld_libs); } return NULL; } knet_handle_t knet_handle_start(int logfds[2], uint8_t log_level) { knet_handle_t knet_h = knet_handle_new(1, logfds[1], log_level, 0); char *plugins_path; if (knet_h) { printf("knet_handle_new at %p\n", knet_h); plugins_path = find_plugins_path(); /* Use plugins from the build tree */ if (plugins_path) { knet_h->plugin_path = plugins_path; } return knet_h; } else { printf("knet_handle_new failed: %s\n", strerror(errno)); flush_logs(logfds[0], stdout); close_logpipes(logfds); exit(FAIL); } } int knet_handle_reconnect_links(knet_handle_t knet_h) { size_t i, j; knet_node_id_t host_ids[KNET_MAX_HOST]; uint8_t link_ids[KNET_MAX_LINK]; size_t host_ids_entries = 0, link_ids_entries = 0; unsigned int enabled; if (!knet_h) { errno = EINVAL; return -1; } if (knet_host_get_host_list(knet_h, host_ids, &host_ids_entries) < 0) { printf("knet_host_get_host_list failed: %s\n", strerror(errno)); return -1; } for (i = 0; i < host_ids_entries; i++) { if (knet_link_get_link_list(knet_h, host_ids[i], link_ids, &link_ids_entries)) { printf("knet_link_get_link_list failed: %s\n", strerror(errno)); return -1; } for (j = 0; j < link_ids_entries; j++) { if (knet_link_get_enable(knet_h, host_ids[i], link_ids[j], &enabled)) { printf("knet_link_get_enable failed: %s\n", strerror(errno)); return -1; } if (!enabled) { if (knet_link_set_enable(knet_h, host_ids[i], j, 1)) { printf("knet_link_set_enable failed: %s\n", strerror(errno)); return -1; } } } } return 0; } int knet_handle_disconnect_links(knet_handle_t knet_h) { size_t i, j; knet_node_id_t host_ids[KNET_MAX_HOST]; uint8_t link_ids[KNET_MAX_LINK]; size_t host_ids_entries = 0, link_ids_entries = 0; unsigned int enabled; if (!knet_h) { errno = EINVAL; return -1; } if (knet_host_get_host_list(knet_h, host_ids, &host_ids_entries) < 0) { printf("knet_host_get_host_list failed: %s\n", strerror(errno)); return -1; } for (i = 0; i < host_ids_entries; i++) { if (knet_link_get_link_list(knet_h, host_ids[i], link_ids, &link_ids_entries)) { printf("knet_link_get_link_list failed: %s\n", strerror(errno)); return -1; } for (j = 0; j < link_ids_entries; j++) { if (knet_link_get_enable(knet_h, host_ids[i], link_ids[j], &enabled)) { printf("knet_link_get_enable failed: %s\n", strerror(errno)); return -1; } if (enabled) { if (knet_link_set_enable(knet_h, host_ids[i], j, 0)) { printf("knet_link_set_enable failed: %s\n", strerror(errno)); return -1; } } } } return 0; } int knet_handle_stop(knet_handle_t knet_h) { size_t i, j; knet_node_id_t host_ids[KNET_MAX_HOST]; uint8_t link_ids[KNET_MAX_LINK]; size_t host_ids_entries = 0, link_ids_entries = 0; unsigned int enabled; if (!knet_h) { errno = EINVAL; return -1; } if (knet_handle_setfwd(knet_h, 0) < 0) { printf("knet_handle_setfwd failed: %s\n", strerror(errno)); return -1; } if (knet_host_get_host_list(knet_h, host_ids, &host_ids_entries) < 0) { printf("knet_host_get_host_list failed: %s\n", strerror(errno)); return -1; } for (i = 0; i < host_ids_entries; i++) { if (knet_link_get_link_list(knet_h, host_ids[i], link_ids, &link_ids_entries)) { printf("knet_link_get_link_list failed: %s\n", strerror(errno)); return -1; } for (j = 0; j < link_ids_entries; j++) { if (knet_link_get_enable(knet_h, host_ids[i], link_ids[j], &enabled)) { printf("knet_link_get_enable failed: %s\n", strerror(errno)); return -1; } if (enabled) { if (knet_link_set_enable(knet_h, host_ids[i], j, 0)) { printf("knet_link_set_enable failed: %s\n", strerror(errno)); return -1; } } printf("clearing config for: %p host: %u link: %zu\n", knet_h, host_ids[i], j); knet_link_clear_config(knet_h, host_ids[i], j); } if (knet_host_remove(knet_h, host_ids[i]) < 0) { printf("knet_host_remove failed: %s\n", strerror(errno)); return -1; } } if (knet_handle_free(knet_h)) { printf("knet_handle_free failed: %s\n", strerror(errno)); return -1; } return 0; } static int _make_local_sockaddr(struct sockaddr_storage *lo, int offset, int family) { in_port_t port; char portstr[32]; if (offset < 0) { /* * api_knet_link_set_config needs to access the API directly, but * it does not send any traffic, so it´s safe to ask the kernel * for a random port. */ port = 0; } else { /* Use the pid if we can. but makes sure its in a sensible range */ port = (getpid() + offset) % (65536-1024) + 1024; } sprintf(portstr, "%u", port); memset(lo, 0, sizeof(struct sockaddr_storage)); printf("Using port %u\n", port); if (family == AF_INET6) { return knet_strtoaddr("::1", portstr, lo, sizeof(struct sockaddr_storage)); } return knet_strtoaddr("127.0.0.1", portstr, lo, sizeof(struct sockaddr_storage)); } int make_local_sockaddr(struct sockaddr_storage *lo, int offset) { return _make_local_sockaddr(lo, offset, AF_INET); } int make_local_sockaddr6(struct sockaddr_storage *lo, int offset) { return _make_local_sockaddr(lo, offset, AF_INET6); } 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) { int err = 0, savederrno = 0; uint32_t port; char portstr[32]; for (port = 1025; port < 65536; port++) { sprintf(portstr, "%u", port); memset(lo, 0, sizeof(struct sockaddr_storage)); if (family == AF_INET6) { err = knet_strtoaddr("::1", portstr, lo, sizeof(struct sockaddr_storage)); } else { err = knet_strtoaddr("127.0.0.1", portstr, lo, sizeof(struct sockaddr_storage)); } if (err < 0) { printf("Unable to convert loopback to sockaddr: %s\n", strerror(errno)); goto out; } errno = 0; if (dynamic) { err = knet_link_set_config(knet_h, host_id, link_id, transport, lo, NULL, flags); } else { err = knet_link_set_config(knet_h, host_id, link_id, transport, lo, lo, flags); } savederrno = errno; if ((err < 0) && (savederrno != EADDRINUSE)) { printf("Unable to configure link: %s\n", strerror(savederrno)); goto out; } if (!err) { printf("Using port %u\n", port); goto out; } } if (err) { printf("No more ports available\n"); } out: errno = savederrno; return err; } void test_sleep(knet_handle_t knet_h, int seconds) { if (is_memcheck() || is_helgrind()) { printf("Test suite is running under valgrind, adjusting sleep timers\n"); seconds = seconds * 16; } sleep(seconds); } int wait_for_packet(knet_handle_t knet_h, int seconds, int datafd, int logfd, FILE *std) { fd_set rfds; struct timeval tv; int err = 0, i = 0; if (is_memcheck() || is_helgrind()) { printf("Test suite is running under valgrind, adjusting wait_for_packet timeout\n"); seconds = seconds * 16; } try_again: FD_ZERO(&rfds); FD_SET(datafd, &rfds); tv.tv_sec = 1; tv.tv_usec = 0; err = select(datafd+1, &rfds, NULL, NULL, &tv); /* * on slow arches the first call to select can return 0. * pick an arbitrary 10 times loop (multiplied by waiting seconds) * before failing. */ if ((!err) && (i < seconds)) { flush_logs(logfd, std); i++; goto try_again; } if ((err > 0) && (FD_ISSET(datafd, &rfds))) { return 0; } errno = ETIMEDOUT; return -1; } /* * functional tests helpers */ void knet_handle_start_nodes(knet_handle_t knet_h[], uint8_t numnodes, int logfds[2], uint8_t log_level) { uint8_t i; char *plugins_path = find_plugins_path(); for (i = 1; i <= numnodes; i++) { knet_h[i] = knet_handle_new(i, logfds[1], log_level, 0); if (!knet_h[i]) { printf("failed to create handle: %s\n", strerror(errno)); break; } else { printf("knet_h[%u] at %p\n", i, knet_h[i]); } /* Use plugins from the build tree */ if (plugins_path) { knet_h[i]->plugin_path = plugins_path; } } if (i < numnodes) { knet_handle_stop_nodes(knet_h, i); exit(FAIL); } return; } void knet_handle_stop_nodes(knet_handle_t knet_h[], uint8_t numnodes) { uint8_t i; for (i = 1; i <= numnodes; i++) { printf("stopping handle %u at %p\n", i, knet_h[i]); knet_handle_stop(knet_h[i]); } return; } void knet_handle_join_nodes(knet_handle_t knet_h[], uint8_t numnodes, uint8_t numlinks, int family, uint8_t transport) { uint8_t i, x, j; struct sockaddr_storage src, dst; int offset = 0; int res; for (i = 1; i <= numnodes; i++) { for (j = 1; j <= numnodes; j++) { /* * don´t connect to itself */ if (j == i) { continue; } printf("host %u adding host: %u\n", i, j); if (knet_host_add(knet_h[i], j) < 0) { printf("Unable to add host: %s\n", strerror(errno)); knet_handle_stop_nodes(knet_h, numnodes); exit(FAIL); } for (x = 0; x < numlinks; x++) { res = -1; offset = 0; while (i + x + offset++ < 65535 && res != 0) { if (_make_local_sockaddr(&src, i + x + offset, family) < 0) { printf("Unable to convert src to sockaddr: %s\n", strerror(errno)); knet_handle_stop_nodes(knet_h, numnodes); exit(FAIL); } if (_make_local_sockaddr(&dst, j + x + offset, family) < 0) { printf("Unable to convert dst to sockaddr: %s\n", strerror(errno)); knet_handle_stop_nodes(knet_h, numnodes); exit(FAIL); } res = knet_link_set_config(knet_h[i], j, x, transport, &src, &dst, 0); } printf("joining node %u with node %u via link %u src offset: %u dst offset: %u\n", i, j, x, i+x, j+x); if (knet_link_set_enable(knet_h[i], j, x, 1) < 0) { printf("unable to enable link: %s\n", strerror(errno)); knet_handle_stop_nodes(knet_h, numnodes); exit(FAIL); } } } } for (i = 1; i <= numnodes; i++) { wait_for_nodes_state(knet_h[i], numnodes, 1, 600, knet_h[1]->logfd, stdout); } return; } static int target=0; static pthread_mutex_t wait_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t wait_cond = PTHREAD_COND_INITIALIZER; static int count_nodes(knet_handle_t knet_h) { int nodes = 0; int i; for (i=0; i< KNET_MAX_HOST; i++) { if (knet_h->host_index[i] && knet_h->host_index[i]->status.reachable == 1) { nodes++; } } return nodes; } static void nodes_notify_callback(void *private_data, knet_node_id_t host_id, uint8_t reachable, uint8_t remote, uint8_t external) { knet_handle_t knet_h = (knet_handle_t) private_data; int nodes; nodes = count_nodes(knet_h); if (nodes == target) { pthread_cond_signal(&wait_cond); } } static void host_notify_callback(void *private_data, knet_node_id_t host_id, uint8_t reachable, uint8_t remote, uint8_t external) { knet_handle_t knet_h = (knet_handle_t) private_data; if (knet_h->host_index[host_id]->status.reachable == 1) { pthread_cond_signal(&wait_cond); } } /* Wait for a cluster of 'numnodes' to come up/go down */ int wait_for_nodes_state(knet_handle_t knet_h, size_t numnodes, uint8_t state, uint32_t timeout, int logfd, FILE *std) { struct timespec ts; int res; if (state) { target = numnodes-1; /* exclude us */ } else { target = 0; /* Wait for all to go down */ } /* Set this before checking existing status or there's a race condition */ knet_host_enable_status_change_notify(knet_h, (void *)(long)knet_h, nodes_notify_callback); /* Check we haven't already got all the nodes in the correct state */ if (count_nodes(knet_h) == target) { fprintf(stderr, "target already reached\n"); knet_host_enable_status_change_notify(knet_h, (void *)(long)0, NULL); flush_logs(logfd, std); return 0; } clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += timeout; if (pthread_mutex_lock(&wait_mutex)) { fprintf(stderr, "unable to get nodewait mutex: %s\n", strerror(errno)); return -1; } res = pthread_cond_timedwait(&wait_cond, &wait_mutex, &ts); if (res == -1 && errno == ETIMEDOUT) { fprintf(stderr, "Timed-out\n"); } pthread_mutex_unlock(&wait_mutex); knet_host_enable_status_change_notify(knet_h, (void *)(long)0, NULL); flush_logs(logfd, std); return res; } /* Wait for a single node to come up */ int wait_for_host(knet_handle_t knet_h, uint16_t host_id, int seconds, int logfd, FILE *std) { int res; struct timespec ts; if (is_memcheck() || is_helgrind()) { printf("Test suite is running under valgrind, adjusting wait_for_host timeout\n"); seconds = seconds * 16; } /* Set this before checking existing status or there's a race condition */ knet_host_enable_status_change_notify(knet_h, (void *)(long)knet_h, host_notify_callback); /* Check it's not already reachable */ if (knet_h->host_index[host_id]->status.reachable == 1) { knet_host_enable_status_change_notify(knet_h, (void *)(long)0, NULL); flush_logs(logfd, std); return 0; } clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += seconds; if (pthread_mutex_lock(&wait_mutex)) { fprintf(stderr, "unable to get nodewait mutex: %s\n", strerror(errno)); return -1; } res = pthread_cond_timedwait(&wait_cond, &wait_mutex, &ts); if (res == -1 && errno == ETIMEDOUT) { fprintf(stderr, "Timed-out\n"); knet_host_enable_status_change_notify(knet_h, (void *)(long)0, NULL); pthread_mutex_unlock(&wait_mutex); flush_logs(logfd, std); return -1; } pthread_mutex_unlock(&wait_mutex); knet_host_enable_status_change_notify(knet_h, (void *)(long)0, NULL); /* Still wait for it to settle */ flush_logs(logfd, std); test_sleep(knet_h, 1); return 0; } diff --git a/libknet/tests/test-common.h b/libknet/tests/test-common.h index 49051219..f64d5e98 100644 --- a/libknet/tests/test-common.h +++ b/libknet/tests/test-common.h @@ -1,96 +1,97 @@ /* * Copyright (C) 2016-2021 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 /* 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 #endif /* * common facilities */ 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); /* * consider moving this one as official API */ int knet_handle_stop(knet_handle_t knet_h); /* * 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_start_nodes(knet_handle_t knet_h[], uint8_t numnodes, int logfds[2], uint8_t log_level); void knet_handle_stop_nodes(knet_handle_t knet_h[], uint8_t numnodes); void knet_handle_join_nodes(knet_handle_t knet_h[], uint8_t numnodes, uint8_t numlinks, int family, uint8_t transport); int knet_handle_disconnect_links(knet_handle_t knet_h); int knet_handle_reconnect_links(knet_handle_t knet_h); /* * 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); +char *find_plugins_path(void); 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/libnozzle/Makefile.am b/libnozzle/Makefile.am index bb84f24a..750c9d53 100644 --- a/libnozzle/Makefile.am +++ b/libnozzle/Makefile.am @@ -1,49 +1,47 @@ # # Copyright (C) 2010-2021 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 +SUBDIRS = . tests bindings 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) libnozzle_la_LDFLAGS = $(AM_LDFLAGS) \ -Wl,-version-script,$(srcdir)/$(SYMFILE) \ - -version-info $(libversion) + -version-info $(libnozzleversion) libnozzle_la_LIBADD = $(PTHREAD_LIBS) $(libnl_LIBS) $(libnlroute_LIBS) endif diff --git a/libnozzle/bindings/Makefile.am b/libnozzle/bindings/Makefile.am new file mode 100644 index 00000000..f7ef3bcf --- /dev/null +++ b/libnozzle/bindings/Makefile.am @@ -0,0 +1,17 @@ +# +# Copyright (C) 2021 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 + +SUBDIRS = . + +if BUILD_RUST_BINDINGS +SUBDIRS += rust +endif diff --git a/libnozzle/bindings/rust/Cargo.toml.in b/libnozzle/bindings/rust/Cargo.toml.in new file mode 100644 index 00000000..491dafdd --- /dev/null +++ b/libnozzle/bindings/rust/Cargo.toml.in @@ -0,0 +1,29 @@ +# Copyright (C) 2021 Red Hat, Inc. All rights reserved. +# +# Author: Christine Caulfield +# +# This software licensed under LGPL-2.1+ + +[package] +name = "nozzle-bindings" +version = "@libnozzlerustver@" +authors = ["Christine Caulfield "] +edition = "2018" +readme = "README" +license = "LGPL-2.1+" +repository = "https://github.com/kronosnet/kronosnet" +description = "Rust bindings for libnozzle library" +categories = ["api-bindings"] +keywords = ["cluster", "high-availability"] +exclude = [ + "*.in", + "Makefile*", +] + +[dependencies] +bitflags = "1.2.1" +lazy_static = "1.4.0" +os_socketaddr = "0.2.0" +libc = "0.2.93" + + diff --git a/libnozzle/bindings/rust/Makefile.am b/libnozzle/bindings/rust/Makefile.am new file mode 100644 index 00000000..a0a946d9 --- /dev/null +++ b/libnozzle/bindings/rust/Makefile.am @@ -0,0 +1,37 @@ +# +# Copyright (C) 2021 Red Hat, Inc. All rights reserved. +# +# Author: Christine Caulfield +# +# This software licensed under GPL-2.0+ +# + +MAINTAINERCLEANFILES = Makefile.in + +include $(top_srcdir)/build-aux/check.mk +include $(top_srcdir)/build-aux/rust.mk + +# required for make check +localver = $(libnozzlerustver) + +SUBDIRS = . tests + +EXTRA_DIST = \ + $(RUST_COMMON) \ + $(RUST_SHIP_SRCS) \ + README + +RUST_SHIP_SRCS = \ + src/nozzle_bindings.rs \ + src/lib.rs \ + src/sys/mod.rs + +RUST_BUILT_SRCS = \ + src/sys/libnozzle.rs + +src/sys/libnozzle.rs: ../../libnozzle.h + $(top_srcdir)/build-aux/rust-regen.sh $^ $@ NOZZLE + +all-local: cargo-tree-prep target/$(RUST_TARGET_DIR)/nozzle_bindings.rlib + +clean-local: cargo-clean diff --git a/libnozzle/bindings/rust/README b/libnozzle/bindings/rust/README new file mode 100644 index 00000000..de386e60 --- /dev/null +++ b/libnozzle/bindings/rust/README @@ -0,0 +1,10 @@ +# Copyright (C) 2021 Red Hat, Inc. All rights reserved. +# +# Author: Christine Caulfield +# +# This software licensed under GPL-2.0+ + +This crate contains Rust bindings for the libnozzle +library, part of kornosnet: https://kronosnet.org/ + +libnozzle is a commodity library to manage tap (ethernet) interfaces diff --git a/libnozzle/bindings/rust/build.rs.in b/libnozzle/bindings/rust/build.rs.in new file mode 100644 index 00000000..0ad14f77 --- /dev/null +++ b/libnozzle/bindings/rust/build.rs.in @@ -0,0 +1,11 @@ +// Copyright (c) 2021 Red Hat, Inc. +// +// All rights reserved. +// +// Author: Christine Caulfield (ccaulfi@redhat.com) +// + +fn main() { + println!("cargo:rustc-link-search=native=../../../.libs"); + println!("cargo:rustc-link-lib=dylib=nozzle"); +} diff --git a/libnozzle/bindings/rust/src/lib.rs b/libnozzle/bindings/rust/src/lib.rs new file mode 100644 index 00000000..64b6e788 --- /dev/null +++ b/libnozzle/bindings/rust/src/lib.rs @@ -0,0 +1,124 @@ +// Copyright (C) 2021 Red Hat, Inc. All rights reserved. +// +// Authors: Christine Caulfield +// +// This software licensed under LGPL-2.0+ +// + +//! This crate provides access to the kronosnet libraries libknet and libnozzle +//! from Rust. They are a fairly thin layer around the actual API calls but with Rust data types +//! and iterators. +//! +//! No more information about knet itself will be provided here, it is expected that if +//! you feel you need access to the knet API calls, you know what they do :) +//! +//! # Example +//! ``` +//! use nozzle_bindings::nozzle_bindings as nozzle; +//! use std::io::{Result}; +//! use std::env; +//! use std::{thread, time}; +//! +//! fn main() -> Result<()> +//! { +//! let mut nozzle_name = String::from("rustnoz"); +//! let handle = match nozzle::open(&mut nozzle_name, &String::from(env::current_dir().unwrap().to_str().unwrap())) { +//! Ok(h) => { +//! println!("Opened device {}", nozzle_name); +//! h +//! }, +//! Err(e) => { +//! println!("Error from open: {}", e); +//! return Err(e); +//! } +//! }; +//! +//! let if Err(e) = nozzle::add_ip(handle, &"192.160.100.1".to_string(), &"24".to_string()) { +//! println!("Error from add_ip: {}", e); +//! return Err(e); +//! } +//! +//! let if Err(e) = nozzle::set_mtu(handle, 157) { +//! println!("Error from set_mtu: {}", e); +//! return Err(e); +//! } +//! +//! Ok(()) +//! } + + +mod sys; +pub mod nozzle_bindings; + +use std::os::raw::c_char; +use std::ptr::copy_nonoverlapping; +use std::ffi::CString; +use std::io::{Error, Result, ErrorKind}; + +// General internal routine to copy bytes from a C array into a Rust String +fn string_from_bytes(bytes: *const ::std::os::raw::c_char, max_length: usize) -> Result +{ + let mut newbytes = Vec::::new(); + newbytes.resize(max_length, 0u8); + + // Get length of the string in old-fashioned style + let mut length: usize = 0; + let mut count = 0; + let mut tmpbytes = bytes; + while count < max_length || length == 0 { + if unsafe {*tmpbytes} == 0 && length == 0 { + length = count; + break; + } + count += 1; + tmpbytes = unsafe { tmpbytes.offset(1) } + } + + // Cope with an empty string + if length == 0 { + return Ok(String::new()); + } + + unsafe { + // We need to fully copy it, not shallow copy it. + // Messy casting on both parts of the copy here to get it to work on both signed + // and unsigned char machines + copy_nonoverlapping(bytes as *mut i8, newbytes.as_mut_ptr() as *mut i8, length); + } + + + let cs = CString::new(&newbytes[0..length as usize])?; + + // This is just to convert the error type + match cs.into_string() { + Ok(s) => Ok(s), + Err(_) => Err(Error::new(ErrorKind::Other, "Cannot convert to String")), + } +} + +// As below but always returns a string even if there was an error doing the conversion +fn string_from_bytes_safe(bytes: *const ::std::os::raw::c_char, max_length: usize) -> String +{ + match string_from_bytes(bytes, max_length) { + Ok(s) => s, + Err(_)=> "".to_string() + } +} + +fn string_to_bytes(s: &str, bytes: &mut [c_char]) -> Result<()> +{ + let c_name = match CString::new(s) { + Ok(n) => n, + Err(_) => return Err(Error::new(ErrorKind::Other, "Rust conversion error")), + }; + + if c_name.as_bytes().len() > bytes.len() { + return Err(Error::new(ErrorKind::Other, "String too long")); + } + + unsafe { + // NOTE param order is 'wrong-way round' from C + copy_nonoverlapping(c_name.as_ptr(), bytes.as_mut_ptr(), c_name.as_bytes().len()); + } + Ok(()) +} diff --git a/libnozzle/bindings/rust/src/nozzle_bindings.rs b/libnozzle/bindings/rust/src/nozzle_bindings.rs new file mode 100644 index 00000000..f0b0e715 --- /dev/null +++ b/libnozzle/bindings/rust/src/nozzle_bindings.rs @@ -0,0 +1,367 @@ +// libnozzle interface for Rust +// Copyright (c) 2021 Red Hat, Inc. +// +// All rights reserved. +// +// Author: Christine Caulfield (ccaulfi@redhat.com) +// + + +// For the code generated by bindgen +use crate::sys::libnozzle as ffi; + +use std::io::{Result, Error, ErrorKind}; +use std::os::raw::{c_char, c_void}; +use std::ptr::{null_mut}; +use libc::free; +use std::fmt; + +/// A handle into the nozzle library. Returned from [open] and needed for all other calls +#[derive(Copy, Clone, PartialEq)] +pub struct Handle { + nozzle_handle: ffi::nozzle_t, +} + +const IFNAMSZ: usize = 16; + +/// Create a new tap device on the system +pub fn open(devname: &mut String, updownpath: &str) -> Result +{ + let mut c_devname: [c_char; IFNAMSZ] = [0; IFNAMSZ]; + let mut c_updownpath: [c_char; libc::PATH_MAX as usize] = [0; libc::PATH_MAX as usize]; + let c_devname_size = IFNAMSZ; + + crate::string_to_bytes(&devname, &mut c_devname)?; + crate::string_to_bytes(&updownpath, &mut c_updownpath)?; + + let res = unsafe { + ffi::nozzle_open(c_devname.as_mut_ptr(), c_devname_size, c_updownpath.as_ptr()) + }; + + if res.is_null() { + Err(Error::last_os_error()) + } else { + let temp = crate::string_from_bytes(c_devname.as_ptr(), IFNAMSZ)?; + *devname = temp; + Ok(Handle{nozzle_handle: res}) + } +} + +/// Deconfigure and destroy a nozzle device +pub fn close(handle: Handle) -> Result<()> +{ + let res = unsafe { + ffi::nozzle_close(handle.nozzle_handle) + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// Which script to run when [run_updown] is called +pub enum Action { + PreUp, + Up, + Down, + PostDown +} +impl Action { + pub fn to_u8(self: &Action) -> u8 + { + match self { + Action::PreUp => 0, + Action::Up => 1, + Action::Down => 2, + Action::PostDown => 3, + } + } +} + +/// Run an up/down script before/after configuring a device. See [Action] +pub fn run_updown(handle: Handle, action: Action) -> Result +{ + let c_exec_string : *mut *mut ::std::os::raw::c_char = &mut [0;0].as_mut_ptr(); + let c_action = action.to_u8(); + + let res = unsafe { + ffi::nozzle_run_updown(handle.nozzle_handle, c_action, c_exec_string) + }; + + match res { + 0 => { + unsafe { + // This is unsafe because we deference a raw pointer + let resstring = crate::string_from_bytes(*c_exec_string as *mut ::std::os::raw::c_char, libc::PATH_MAX as usize)?; + free(*c_exec_string as *mut c_void); + Ok(resstring) + } + }, + -1 => Err(Error::last_os_error()), + -2 => Err(Error::new(ErrorKind::Other, "error executing shell scripts")), + _ => Err(Error::new(ErrorKind::Other, "unknown error returned from nozzle_tun_updown()")), + } +} + +/// Mark nozzle device as "up" +pub fn set_up(handle: Handle) -> Result<()> +{ + let res = unsafe { + ffi::nozzle_set_up(handle.nozzle_handle) + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// mark nozzle device as "down" +pub fn set_down(handle: Handle) -> Result<()> +{ + let res = unsafe { + ffi::nozzle_set_down(handle.nozzle_handle) + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} +const IPADDR_CHAR_MAX: usize = 128; +const PREFIX_CHAR_MAX: usize = 4; + +/// Add an ip address to a nozzle device. multiple addresses can be added to one device. +/// The prefix is a the number that comes after the ip address when configuring: +/// eg: 192.168.0.1/24 - the prefix is "24" +pub fn add_ip(handle: Handle, ipaddr: &str, prefix: &str) -> Result<()> +{ + let mut c_ipaddr: [c_char; IPADDR_CHAR_MAX] = [0; IPADDR_CHAR_MAX]; + let mut c_prefix: [c_char; PREFIX_CHAR_MAX] = [0; PREFIX_CHAR_MAX]; + + crate::string_to_bytes(&ipaddr, &mut c_ipaddr)?; + crate::string_to_bytes(&prefix, &mut c_prefix)?; + let res = unsafe { + ffi::nozzle_add_ip(handle.nozzle_handle, c_ipaddr.as_ptr(), c_prefix.as_ptr()) + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } + +} + +/// remove an ip address from a nozzle device +pub fn del_ip(handle: Handle, ipaddr: &str, prefix: &str) -> Result<()> +{ + let mut c_ipaddr: [c_char; IPADDR_CHAR_MAX] = [0; IPADDR_CHAR_MAX]; + let mut c_prefix: [c_char; PREFIX_CHAR_MAX] = [0; PREFIX_CHAR_MAX]; + + crate::string_to_bytes(&ipaddr, &mut c_ipaddr)?; + crate::string_to_bytes(&prefix, &mut c_prefix)?; + let res = unsafe { + ffi::nozzle_del_ip(handle.nozzle_handle, c_ipaddr.as_ptr(), c_prefix.as_ptr()) + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// IpV4/IpV6 in the struct returned from [get_ips] +pub enum Domain { + IpV4, + IpV6 +} +impl Domain { + fn new(c_dom: i32) -> Domain + { + match c_dom { + libc::AF_INET => Domain::IpV4, + libc::AF_INET6 => Domain::IpV6, + _ => Domain::IpV4, + } + } +} +impl fmt::Display for Domain { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Domain::IpV4 => write!(f, "IPv4"), + Domain::IpV6 => write!(f, "IPv6"), + } + } +} + +/// IP address info as returned from [get_ips] +pub struct Ip +{ + pub ipaddr: String, + pub prefix: String, + pub domain: Domain, +} +impl Ip { + pub fn new(c_ip: &ffi::nozzle_ip) -> Ip + { + Ip { + ipaddr: crate::string_from_bytes_safe(c_ip.ipaddr.as_ptr(), IPADDR_CHAR_MAX), + prefix: crate::string_from_bytes_safe(c_ip.prefix.as_ptr(), PREFIX_CHAR_MAX), + domain: Domain::new(c_ip.domain) + } + } +} +impl fmt::Display for Ip { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f,"{} {}/{}", self.domain, self.ipaddr, self.prefix)?; + Ok(()) + } +} + +/// Return a Vec of Ip adressess attached to this device +pub fn get_ips(handle: Handle) -> Result> +{ + let mut c_ips : &mut ffi::nozzle_ip = &mut ffi::nozzle_ip{ipaddr: [0;129], prefix: [0;5], domain:0, next: null_mut()}; + let res = unsafe { + ffi::nozzle_get_ips(handle.nozzle_handle, &mut c_ips as *mut _ as *mut *mut ffi::nozzle_ip) + }; + let mut ipvec = Vec::::new(); + if res == 0 { + let mut ips : *mut ffi::nozzle_ip = c_ips; + unsafe { + while !ips.is_null() { + ipvec.push(Ip::new(&*ips)); + ips = (*ips).next; + } + } + Ok(ipvec) + } else { + Err(Error::last_os_error()) + } +} + +/// Get the MTU of the device +pub fn get_mtu(handle: Handle) -> Result +{ + let res = unsafe { + ffi::nozzle_get_mtu(handle.nozzle_handle) + }; + if res != -1 { + Ok(res) + } else { + Err(Error::last_os_error()) + } +} + +/// Set the MTU of the device +pub fn set_mtu(handle: Handle, new_mtu: i32) -> Result<()> +{ + let res = unsafe { + ffi::nozzle_set_mtu(handle.nozzle_handle, new_mtu) + }; + if res != -1 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + + +/// Reset the device's MTU back to the default +pub fn reset_mtu(handle: Handle) -> Result<()> +{ + let res = unsafe { + ffi::nozzle_reset_mtu(handle.nozzle_handle) + }; + if res != -1 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// Returns the MAC address of the device +pub fn get_mac(handle: Handle) -> Result +{ + let mut c_mac: *mut c_char = null_mut(); + let res = unsafe { + ffi::nozzle_get_mac(handle.nozzle_handle, &mut c_mac) + }; + if res == 0 { + let mac = crate::string_from_bytes(c_mac, 24_usize)?; // Needs to be 8byte aligned + unsafe { free(c_mac as *mut c_void); }// Was created with strdup( + Ok(mac) + } else { + Err(Error::last_os_error()) + } +} + +/// Setsthe MAC address of the device +pub fn set_mac(handle: Handle, ether_addr: &str) -> Result<()> +{ + let mut c_mac: [c_char; 24_usize] = [0; 24_usize]; // Needs to be 8byte aligned + crate::string_to_bytes(ðer_addr, &mut c_mac)?; + let res = unsafe { + ffi::nozzle_set_mac(handle.nozzle_handle, c_mac.as_ptr()) + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// Reset the device's MAC address to the defaut +pub fn reset_mac(handle: Handle) -> Result<()> +{ + let res = unsafe { + ffi::nozzle_reset_mac(handle.nozzle_handle) + }; + if res == 0 { + Ok(()) + } else { + Err(Error::last_os_error()) + } +} + +/// Find the nozzle handle of a device by giving its name +pub fn get_handle_by_name(devname: &str) -> Result +{ + let mut c_devname: [c_char; IFNAMSZ] = [0; IFNAMSZ]; + crate::string_to_bytes(&devname, &mut c_devname)?; + let res = unsafe { + ffi::nozzle_get_handle_by_name(c_devname.as_ptr()) + }; + if !res.is_null() { + Ok(Handle{nozzle_handle:res}) + } else { + Err(Error::last_os_error()) + } +} + +/// Return the name of the device +pub fn get_name_by_handle(handle: Handle) -> Result +{ + let res = unsafe { + ffi::nozzle_get_name_by_handle(handle.nozzle_handle) + }; + if !res.is_null() { + crate::string_from_bytes(res, IFNAMSZ) + } else { + Err(Error::last_os_error()) + } +} + +/// Return a unix FD for the device +pub fn get_fd(handle: Handle) -> Result +{ + let res = unsafe { + ffi::nozzle_get_fd(handle.nozzle_handle) + }; + if res != -1 { + Ok(res) + } else { + Err(Error::last_os_error()) + } +} diff --git a/libnozzle/bindings/rust/src/sys/mod.rs b/libnozzle/bindings/rust/src/sys/mod.rs new file mode 100644 index 00000000..6e9df599 --- /dev/null +++ b/libnozzle/bindings/rust/src/sys/mod.rs @@ -0,0 +1,10 @@ +// Copyright (c) 2021 Red Hat, Inc. +// +// All rights reserved. +// +// Author: Christine Caulfield (ccaulfi@redhat.com) +// + +#![allow(non_camel_case_types, non_snake_case, dead_code, improper_ctypes)] + +pub mod libnozzle; diff --git a/libnozzle/bindings/rust/tests/Cargo.toml.in b/libnozzle/bindings/rust/tests/Cargo.toml.in new file mode 100644 index 00000000..32edfa43 --- /dev/null +++ b/libnozzle/bindings/rust/tests/Cargo.toml.in @@ -0,0 +1,24 @@ +# Copyright (C) 2021 Red Hat, Inc. All rights reserved. +# +# Author: Christine Caulfield +# +# This software licensed under GPL-2.0+ + +[package] +name = "nozzle-bindings-tests" +version = "@libnozzlerustver@" +authors = ["Christine Caulfield "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nozzle-bindings = { path = ".." } +libc = "0.2.93" +tempfile = "3" + +[[bin]] +name = "nozzle-test" +test = true +bench = false + diff --git a/libnozzle/bindings/rust/tests/Makefile.am b/libnozzle/bindings/rust/tests/Makefile.am new file mode 100644 index 00000000..8272323a --- /dev/null +++ b/libnozzle/bindings/rust/tests/Makefile.am @@ -0,0 +1,33 @@ +# +# Copyright (C) 2021 Red Hat, Inc. All rights reserved. +# +# Author: Christine Caulfield +# +# This software licensed under GPL-2.0+ +# + +MAINTAINERCLEANFILES = Makefile.in + +include $(top_srcdir)/build-aux/check.mk +include $(top_srcdir)/build-aux/rust.mk + +EXTRA_DIST = \ + $(RUST_COMMON) \ + $(RUST_SHIP_SRCS) + +RUST_SHIP_SRCS = src/bin/nozzle-test.rs + +check_SCRIPTS = target/$(RUST_TARGET_DIR)/nozzle-test + +noinst_SCRIPTS = $(check_SCRIPTS) + +if INSTALL_TESTS +testsuitedir = $(TESTDIR) +testsuite_SCRIPTS = $(check_SCRIPTS) +endif + +AM_TESTS_ENVIRONMENT=LD_LIBRARY_PATH="$(abs_top_builddir)/libnozzle/.libs" + +TESTS = $(check_SCRIPTS) + +clean-local: cargo-clean diff --git a/libnozzle/bindings/rust/tests/build.rs.in b/libnozzle/bindings/rust/tests/build.rs.in new file mode 100644 index 00000000..c9ebc9b2 --- /dev/null +++ b/libnozzle/bindings/rust/tests/build.rs.in @@ -0,0 +1,13 @@ +// Copyright (c) 2021 Red Hat, Inc. +// +// All rights reserved. +// +// Author: Christine Caulfield (ccaulfi@redhat.com) +// + +fn main() { + + // Tell the compiler to use the build-tree libs & headers for compiling + println!("cargo:rustc-link-search=native=../../../.libs/"); + println!("cargo:rustc-link-lib=nozzle"); +} diff --git a/libnozzle/bindings/rust/tests/src/bin/nozzle-test.rs b/libnozzle/bindings/rust/tests/src/bin/nozzle-test.rs new file mode 100644 index 00000000..3e84f287 --- /dev/null +++ b/libnozzle/bindings/rust/tests/src/bin/nozzle-test.rs @@ -0,0 +1,245 @@ +// Testing the Nozzle Rust APIs +// +// Copyright (c) 2021 Red Hat, Inc. +// +// All rights reserved. +// +// Author: Christine Caulfield (ccaulfi@redhat.com) +// + +use nozzle_bindings::nozzle_bindings as nozzle; +use std::io::{Result, Error, ErrorKind, BufWriter, Write}; +use std::fmt::Write as fmtwrite; +use std::{thread, time}; +use std::fs::File; +use std::fs; +use tempfile::tempdir; + +const SKIP: i32 = 77; + +fn main() -> Result<()> +{ + // We must be root + if unsafe { libc::geteuid() != 0 } { + std::process::exit(SKIP); + } + + // Run in a random tmpdir so we don't clash with other instances + let tmp_path = tempdir()?; + let tmp_dir = match tmp_path.path().to_str() { + Some(td) => td, + None => { + println!("Error creating temp path for running"); + return Err(Error::new(ErrorKind::Other, "Error creating temp path")); + } + }; + std::env::set_current_dir(tmp_dir)?; + + // Let the OS generate a tap name + let mut nozzle_name = String::from(""); + let handle = match nozzle::open(&mut nozzle_name, &tmp_dir) { + Ok(h) => { + println!("Opened device {}", nozzle_name); + h + }, + Err(e) => { + println!("Error from open: {}", e); + return Err(e); + } + }; + + // Get default state for checking reset_* calls later + let saved_mtu = match nozzle::get_mtu(handle) { + Ok(m) => m, + Err(e) => { + println!("Error from get_mtu: {}", e); + return Err(e); + } + }; + let saved_mac = match nozzle::get_mac(handle) { + Ok(m) => m, + Err(e) => { + println!("Error from get_mac: {}", e); + return Err(e); + } + }; + + // Play with APIs + if let Err(e) = nozzle::add_ip(handle, &"192.160.100.1", &"24") { + println!("Error from add_ip: {}", e); + return Err(e); + } + if let Err(e) = nozzle::add_ip(handle, &"192.160.100.2", &"24") { + println!("Error from add_ip2: {}", e); + return Err(e); + } + if let Err(e) = nozzle::add_ip(handle, &"192.160.100.3", &"24") { + println!("Error from add_ip3: {}", e); + return Err(e); + } + + if let Err(e) = nozzle::set_mac(handle, &"AA:00:04:00:22:01") { + println!("Error from set_mac: {}", e); + return Err(e); + } + + if let Err(e) = nozzle::set_mtu(handle, 157) { + println!("Error from set_mtu: {}", e); + return Err(e); + } + + if let Err(e) = nozzle::set_up(handle) { + println!("Error from set_up: {}", e); + return Err(e); + } + + // Create the 'up' script so we can test the run_updown() function, + let up_path = std::path::Path::new("up.d"); + if let Err(e) = fs::create_dir_all(up_path) { + eprintln!("Error creating up.d directory: {:?}", e); + return Err(e); + } + + let mut up_filename = String::new(); + if let Err(e) = write!(up_filename, "up.d/{}", nozzle_name) { + eprintln!("Error making up.d filename: {:?}", e); + return Err(Error::new(ErrorKind::Other, "Error making up.d filename")); + } + match File::create(&up_filename) { + Err(e) => { + println!("Cannot create up.d file {}: {}", &up_filename, e); + return Err(e); + } + Ok(fl) => { + let mut f = BufWriter::new(fl); + writeln!(f, "#!/bin/sh\necho 'This is a test of an \"Up\" script'")?; + } + } + // A grotty way to do chmod, but normally this would be distributed by the sysadmin + unsafe { + let up_cstring = std::ffi::CString::new(up_filename.clone()).unwrap(); + libc::chmod(up_cstring.as_ptr(), 0o700); + } + + match nozzle::run_updown(handle, nozzle::Action::Up) { + Ok(s) => println!("Returned from Up script: {}", s), + Err(e) => { + println!("Error from run_updown: {}", e); + return Err(e); + } + } + + // Tidy up after ourself - remove the up.d/tapX file + fs::remove_file(&up_filename)?; + fs::remove_dir(&"up.d")?; + + match nozzle::get_ips(handle) { + Ok(ips) => { + print!("Got IPs:"); + for i in ips { + print!(" {}", i); + } + println!(); + }, + Err(e) => { + println!("Error from get_ips: {}", e); + return Err(e); + } + } + + match nozzle::get_mtu(handle) { + Ok(m) => println!("Got mtu: {}", m), + Err(e) => { + println!("Error from get_ips: {}", e); + return Err(e); + } + } + match nozzle::get_mac(handle) { + Ok(m) => println!("Got mac: {}", m), + Err(e) => { + println!("Error from get_ips: {}", e); + return Err(e); + } + } + + match nozzle::get_fd(handle) { + Ok(f) => println!("Got FD: {}", f), + Err(e) => { + println!("Error from get_fd: {}", e); + return Err(e); + } + } + + match nozzle::get_handle_by_name(&nozzle_name) { + Ok(h) => if h != handle { + return Err(Error::new(ErrorKind::Other, "get_handle_by_name returned wrong value")); + } + Err(e) => { + println!("Error from get_ips: {}", e); + return Err(e); + } + } + + match nozzle::get_name_by_handle(handle) { + Ok(n) => if n != nozzle_name { + println!("n: {}, nozzle_name: {}", n, nozzle_name); + return Err(Error::new(ErrorKind::Other, "get_name_by_handle returned wrong name")); + } + Err(e) => { + println!("Error from get_ips: {}", e); + return Err(e); + } + } + + // Wait a little while in case user wants to check with 'ip' command + thread::sleep(time::Duration::from_millis(1000)); + + if let Err(e) = nozzle::del_ip(handle, &"192.160.100.3", &"24") { + println!("Error from del_ip: {}", e); + return Err(e); + } + + if let Err(e) = nozzle::reset_mtu(handle) { + println!("Error from reset_mtu: {}", e); + return Err(e); + } + match nozzle::get_mtu(handle) { + Ok(m) => { + if m != saved_mtu { + println!("Got default MTU of {}, not {}", m, saved_mtu); + } + } + Err(e) => { + println!("Error from get_ips: {}", e); + return Err(e); + } + } + + if let Err(e) = nozzle::reset_mac(handle) { + println!("Error from reset_mac: {}", e); + return Err(e); + } + match nozzle::get_mac(handle) { + Ok(m) => { + if m != saved_mac { + println!("Got default MAC of {}, not {}", m, saved_mac); + } + } + Err(e) => { + println!("Error from get_ips: {}", e); + return Err(e); + } + } + + + if let Err(e) = nozzle::set_down(handle){ + println!("Error from set_down: {}", e); + return Err(e); + } + + if let Err(e) = nozzle::close(handle) { + println!("Error from open: {}", e); + return Err(e); + } + Ok(()) +} diff --git a/libnozzle/libnozzle.h b/libnozzle/libnozzle.h index d8e644a1..7540c463 100644 --- a/libnozzle/libnozzle.h +++ b/libnozzle/libnozzle.h @@ -1,333 +1,334 @@ /* * Copyright (C) 2010-2021 Red Hat, Inc. All rights reserved. * * Author: Fabio M. Di Nitto * * This software licensed under LGPL-2.0+ */ #ifndef __LIBNOZZLE_H__ #define __LIBNOZZLE_H__ #include #include +#include /** * * @file libnozzle.h * @brief tap interfaces management API include file * @copyright Copyright (C) 2010-2021 Red Hat, Inc. All rights reserved. * * nozzle is a commodity library to manage tap (ethernet) interfaces */ typedef struct nozzle_iface *nozzle_t; /** * nozzle_open * * @brief create a new tap device on the system. * * devname - pointer to device name of at least size IFNAMSIZ. * if the dev strlen is 0, then the system will assign a name automatically. * if a string is specified, the system will try to create a device with * the specified name. * NOTE: on FreeBSD the tap device names can only be tapX where X is a * number from 0 to 255. On Linux such limitation does not apply. * The name must be unique to the system. If an interface with the same * name is already configured on the system, an error will be returned. * * devname_size - length of the buffer provided in dev (has to be at least IFNAMSIZ). * * updownpath - nozzle supports the typical filesystem structure to execute * actions for: down.d post-down.d pre-up.d up.d * in the form of: * updownpath// * updownpath specifies where to find those directories on the * filesystem and it must be an absolute path. * * @return * nozzle_open returns * a pointer to a nozzle struct on success * NULL on error and errno is set. */ nozzle_t nozzle_open(char *devname, size_t devname_size, const char *updownpath); /** * nozzle_close * * @brief deconfigure and destroy a nozzle device * * nozzle - pointer to the nozzle struct to destroy * * @return * 0 on success * -1 on error and errno is set. */ int nozzle_close(nozzle_t nozzle); #define NOZZLE_PREUP 0 #define NOZZLE_UP 1 #define NOZZLE_DOWN 2 #define NOZZLE_POSTDOWN 3 /** * nozzle_run_updown * * @brief execute updown commands associated with a nozzle device. * * nozzle - pointer to the nozzle struct * * action - pre-up.d / up.d / down.d / post-down.d (see defines above) * * exec_string - pointers to string to record executing action stdout/stderr. * The string is malloc'ed, the caller needs to free the buffer. * If the script generates no output this string might be NULL. * * It is the application responsibility to call helper scripts * before or after creating/destroying interfaces or IP addresses. * * @return * 0 on success * -1 on error and errno is set (sanity checks and internal calls. * -2 on error from executing the shell scripts, and no errno is set. */ int nozzle_run_updown(const nozzle_t nozzle, uint8_t action, char **exec_string); /** * nozzle_set_up * * @brief equivalent of ifconfig up * * nozzle - pointer to the nozzle struct * * @return * 0 on success * -1 on error and errno is set. */ int nozzle_set_up(nozzle_t nozzle); /** * nozzle_set_down * * @brief equivalent of ifconfig down * * nozzle - pointer to the nozzle struct * * @return * 0 on success * -1 on error and errno is set. */ int nozzle_set_down(nozzle_t nozzle); /** * nozzle_add_ip * * @brief equivalent of ip addr or ifconfig * * nozzle - pointer to the nozzle struct * * ipaddr - string containing either an IPv4 or an IPv6 address. * Please note that Linux will automatically remove any IPv6 addresses from an interface * with MTU < 1280. libnozzle will cache those IPs and re-instate them when MTU is > 1280. * MTU must be set via nozzle_set_mtu for IPv6 to be re-instated. * * prefix - 24, 64 or any valid network prefix for the requested address. * * @return * 0 on success * -1 on error and errno is set. */ int nozzle_add_ip(nozzle_t nozzle, const char *ipaddr, const char *prefix); /** * nozzle_del_ip * * @brief equivalent of ip addr del or ifconfig del * * nozzle - pointer to the nozzle struct * * ipaddr - string containing either an IPv4 or an IPv6 address. * * prefix - 24, 64 or any valid network prefix for the requested address. * * @return * 0 on success * -1 on error and errno is set. */ int nozzle_del_ip(nozzle_t nozzle, const char *ipaddr, const char *prefix); #define IPADDR_CHAR_MAX 128 #define PREFIX_CHAR_MAX 4 struct nozzle_ip { char ipaddr[IPADDR_CHAR_MAX + 1]; char prefix[PREFIX_CHAR_MAX + 1]; int domain; /* AF_INET or AF_INET6 */ struct nozzle_ip *next; }; /** * nozzle_get_ips * * @brief retrieve the list of all configured ips for a given interface * * nozzle - pointer to the nozzle struct * * nozzle_ip - pointer to the head of a list of nozzle_ip structs. * The last IP will have next = NULL. * nozzle_ip can be NULL if there are no IP addresses * associated with this nozzle device. * *DO NOT* free those structs as they are used internally * for IP address tracking. * * @return * 0 on success * -1 on error and errno is set. * */ int nozzle_get_ips(const nozzle_t nozzle, struct nozzle_ip **nozzle_ip); /** * nozzle_get_mtu * * @brief retrieve mtu on a given nozzle interface * * nozzle - pointer to the nozzle struct * * @return * MTU on success * -1 on error and errno is set. */ int nozzle_get_mtu(const nozzle_t nozzle); /** * nozzle_set_mtu * * @brief set mtu on a given nozzle interface * * nozzle - pointer to the nozzle struct * * mtu - new MTU value * * @return * 0 on success * -1 on error and errno is set. */ int nozzle_set_mtu(nozzle_t nozzle, const int mtu); /** * nozzle_reset_mtu * * @brief reset mtu on a given nozzle interface to the system default * * nozzle - pointer to the nozzle struct * * @return * 0 on success * -1 on error and errno is set. */ int nozzle_reset_mtu(nozzle_t nozzle); /** * nozzle_get_mac * * @brief retrieve mac address on a given nozzle interface * * nozzle - pointer to the nozzle struct * * ether_addr - pointers to string containing the current mac address. * The string is malloc'ed, the caller needs to free this buffer. * @return * 0 on success. * -1 on error and errno is set. */ int nozzle_get_mac(const nozzle_t nozzle, char **ether_addr); /** * nozzle_set_mac * * @brief set mac address on a given nozzle interface * * nozzle - pointer to the nozzle struct * * ether_addr - pointers to string containing the new mac address. * * @return * 0 on success. * -1 on error and errno is set. */ int nozzle_set_mac(nozzle_t nozzle, const char *ether_addr); /** * nozzle_reset_mac * * @brief reset mac address on a given nozzle interface to system default * * nozzle - pointer to the nozzle struct * * @return * 0 on success. * -1 on error and errno is set. */ int nozzle_reset_mac(nozzle_t nozzle); /** * nozzle_get_handle_by_name * * @brief find a nozzle handle by device name * * devname - string containing the name of the interface * * @return * handle on success. * NULL on error and errno is set. */ nozzle_t nozzle_get_handle_by_name(const char *devname); /** * nozzle_get_name_by_handle * * @brief retrieve nozzle interface name by handle * * nozzle - pointer to the nozzle struct * * @return * pointer to the interface name * NULL on error and errno is set. */ const char *nozzle_get_name_by_handle(const nozzle_t nozzle); /** * nozzle_get_fd * * @brief * * nozzle - pointer to the nozzle struct * * @return * fd associated to a given nozzle on success. * -1 on error and errno is set. */ int nozzle_get_fd(const nozzle_t nozzle); #endif diff --git a/libnozzle/tests/api-test-coverage b/libnozzle/tests/api-test-coverage index 0b5b2fc2..9061399a 100755 --- a/libnozzle/tests/api-test-coverage +++ b/libnozzle/tests/api-test-coverage @@ -1,93 +1,162 @@ #!/bin/sh # # Copyright (C) 2016-2021 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')" 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 + +# Check Rust bindings coverage +rust_found=0 +rust_missing=0 +deliberately_missing="" +rustapicalls=$numapicalls +for i in $headerapicalls; do + rustcall=`echo $i|awk '{print substr($0, 8)}'` + grep "^pub fn ${rustcall}(" $1/libnozzle/bindings/rust/src/nozzle_bindings.rs > /dev/null 2>/dev/null + if [ $? = 0 ] + then + rust_found=$((rust_found+1)) + else + echo $deliberately_missing | grep $i 2>/dev/null >/dev/null + if [ $? != 0 ] + then + echo "$i Missing from Rust API" + rust_missing=$((rust_missing+1)) + else + rustapicalls=$((rustapicalls-1)) + fi + fi +done + +# Check Rust test coverage +rust_test_found=0 +rust_test_missing=0 +deliberately_missing="" +rust_testapicalls=$numapicalls +for i in $headerapicalls; do + rustcall=`echo $i|awk '{print substr($0, 8)}'` + grep "nozzle::${rustcall}(" $1/libnozzle/bindings/rust/tests/src/bin/nozzle-test.rs > /dev/null 2>/dev/null + if [ $? = 0 ] + then + rust_test_found=$((rust_test_found+1)) + else + echo $deliberately_missing | grep $i 2>/dev/null >/dev/null + if [ $? != 0 ] + then + echo "$i Missing from Rust test" + rust_test_missing=$((rust_test_missing+1)) + else + rust_testapicalls=$((rust_testapicalls-1)) + fi + fi +done + +echo "" 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 +echo +echo "Rust API Summary" +echo "----------------" +echo "Found : $rust_found" +echo "Missing : $rust_missing" +echo "Total : $rustapicalls" +which bc > /dev/null 2>&1 && { + coverage=$(echo "scale=3; $rust_found / $rustapicalls * 100" | bc -l) + echo "Coverage: $coverage%" +} + + +echo +echo "Rust test Summary" +echo "-----------------" +echo "Found : $rust_test_found" +echo "Missing : $rust_test_missing" +echo "Total : $rustapicalls" +which bc > /dev/null 2>&1 && { + coverage=$(echo "scale=3; $rust_test_found / $rust_testapicalls * 100" | bc -l) + echo "Coverage: $coverage%" +} exit 0 diff --git a/m4/pkg_check_var.m4 b/m4/pkg_check_var.m4 index ae1bf222..fa3f5df3 100644 --- a/m4/pkg_check_var.m4 +++ b/m4/pkg_check_var.m4 @@ -1,14 +1,20 @@ +# Copyright (C) 2020-2021 Red Hat, Inc. All rights reserved. +# +# Author: Fabio M. Di Nitto +# +# This software licensed under GPL-2.0+ + # PKG_CHECK_VAR(VARIABLE, MODULE, CONFIG-VARIABLE, # [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) # ------------------------------------------- # Retrieves the value of the pkg-config variable for the given module. m4_ifndef([PKG_CHECK_VAR], [AC_DEFUN([PKG_CHECK_VAR], [AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl AC_ARG_VAR([$1], [value of $3 for $2, overriding pkg-config])dnl _PKG_CONFIG([$1], [variable="][$3]["], [$2]) AS_VAR_COPY([$1], [pkg_cv_][$1]) AS_VAR_IF([$1], [""], [$5], [$4])dnl ])# PKG_CHECK_VAR ])