The new namespace helper provides an internal stable interface for plugins to use for switching various linux namespaces. Currently only network namespaces are supported/implemented, but can easily be extended if needed. autoconf will enable it automatically if the required symbols are available. If ulogd is compiled without namespace support, the functions will simply return an error, there is no need for conditional compilation or special handling in plugin code. Signed-off-by: Corubba Smith <corubba@xxxxxx> --- Changes in v2: - Split the single patch into multiple - Moved the namespace code to a dedicated helper (Florian Westphal) - Implemented network namespace support for NFCT polling mode, NFLOG and NFACCT plugins. I skipped ULOG because it's removed from the kernel since 7200135bc1e6 ("netfilter: kill ulog targets") aka v3.17 - Link to v1: https://lore.kernel.org/netfilter-devel/7d1478b6-ec25-4286-a365-ce28293f4a40@xxxxxx/ configure.ac | 22 ++++ include/ulogd/Makefile.am | 4 +- include/ulogd/namespace.h | 8 ++ src/Makefile.am | 3 +- src/namespace.c | 237 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 272 insertions(+), 2 deletions(-) create mode 100644 include/ulogd/namespace.h create mode 100644 src/namespace.c diff --git a/configure.ac b/configure.ac index 3c9249e..2b193d8 100644 --- a/configure.ac +++ b/configure.ac @@ -243,6 +243,27 @@ AS_IF([test "x$enable_json" != "xno"], AS_IF([test "x$libjansson_LIBS" != "x"], [enable_json=yes], [enable_json=no]) AM_CONDITIONAL([HAVE_JANSSON], [test "x$libjansson_LIBS" != "x"]) +AC_ARG_ENABLE([namespace], + [AS_HELP_STRING([--enable-namespace], [Enable linux namespace functionality in plugins supporting it [default=test]])]) +AS_IF([test "x$enable_namespace" != "xno"], [ + AC_CHECK_DECLS([setns, CLONE_NEWNET], [ + enable_namespace=yes + ], [ + AS_IF([test "x$enable_namespace" = "xyes"], [ + AC_MSG_ERROR([linux namespace support enabled, but required symbols not available]) + ], [ + enable_namespace=no + ]) + ], [[ + #define _GNU_SOURCE 1 + #include <fcntl.h> + #include <sched.h> + ]]) +]) +AS_IF([test "x$enable_namespace" = "xyes"], [ + AC_DEFINE([ENABLE_NAMESPACE], [1], [Define to 1 if you want linux namespace support.]) +]) + AC_ARG_WITH([ulogd2libdir], [AS_HELP_STRING([--with-ulogd2libdir=PATH], [Default directory to load ulogd2 plugin from [[LIBDIR/ulogd]]])], [ulogd2libdir="$withval"], @@ -293,6 +314,7 @@ EXPAND_VARIABLE(ulogd2libdir, e_ulogd2libdir) echo " Ulogd configuration: Default plugins directory: ${e_ulogd2libdir} + Linux namespace support: ${enable_namespace} Input plugins: NFLOG plugin: ${enable_nflog} NFCT plugin: ${enable_nfct} diff --git a/include/ulogd/Makefile.am b/include/ulogd/Makefile.am index e4b41c4..65d74ba 100644 --- a/include/ulogd/Makefile.am +++ b/include/ulogd/Makefile.am @@ -1 +1,3 @@ -noinst_HEADERS = conffile.h db.h ipfix_protocol.h linuxlist.h ulogd.h printpkt.h printflow.h common.h linux_rbtree.h timer.h slist.h hash.h jhash.h addr.h +noinst_HEADERS = addr.h common.h conffile.h db.h hash.h ipfix_protocol.h \ + jhash.h linux_rbtree.h linuxlist.h namespace.h printflow.h \ + printpkt.h slist.h timer.h ulogd.h diff --git a/include/ulogd/namespace.h b/include/ulogd/namespace.h new file mode 100644 index 0000000..48e2e9a --- /dev/null +++ b/include/ulogd/namespace.h @@ -0,0 +1,8 @@ +#ifndef _NAMESPACE_H_ +#define _NAMESPACE_H_ + +int join_netns_fd(const int target_netns_fd, int *const source_netns_fd_ptr); +int join_netns_path(const char *const target_netns_path, + int *const source_netns_fd_ptr); + +#endif diff --git a/src/Makefile.am b/src/Makefile.am index 7a12a72..4004c2b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -6,6 +6,7 @@ AM_CPPFLAGS += -DULOGD_CONFIGFILE='"$(sysconfdir)/ulogd.conf"' \ sbin_PROGRAMS = ulogd -ulogd_SOURCES = ulogd.c select.c timer.c rbtree.c conffile.c hash.c addr.c +ulogd_SOURCES = ulogd.c select.c timer.c rbtree.c conffile.c hash.c \ + addr.c namespace.c ulogd_LDADD = ${libdl_LIBS} ${libpthread_LIBS} ulogd_LDFLAGS = -export-dynamic diff --git a/src/namespace.c b/src/namespace.c new file mode 100644 index 0000000..f9f23d4 --- /dev/null +++ b/src/namespace.c @@ -0,0 +1,237 @@ +/* namespace helper + * + * userspace logging daemon for the netfilter subsystem + * + * (C) 2025 The netfilter project + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Description: + * Helper library to switch linux namespaces, primarily network. Provides + * ulogd-internally a stable api regardless whether namespace support is + * compiled in. Library-internally uses conditional compilation to allow the + * wanted level (full/none) of namespace support. Namespaces can be specified + * as open file descriptor or file path. + */ + +#include "config.h" + +/* Enable GNU extension */ +#define _GNU_SOURCE 1 + +#include <errno.h> +#include <fcntl.h> +#include <sched.h> +#include <string.h> +#include <unistd.h> + +#include "ulogd/ulogd.h" +#include "ulogd/namespace.h" + + +#ifdef ENABLE_NAMESPACE +/** + * open_namespace_path() - Open a namespace link by path. + * @ns_path: Path of the file to open. + * + * Effectively just a wrapper around the open() syscall with fixed flags + * suitable for namespaces. + * + * Return: Open fd on success, -1 on error (and set errno). + */ +static int open_namespace_path(const char *const ns_path) { + return open(ns_path, O_RDONLY | O_CLOEXEC); +} + +/** + * SELF_NAMESPACE_PATH() - Path for own current namespace. + * @x: Name of the namespace link. + * + * Return: String-constant of the absolute path to the namespace link. + */ +#define SELF_NAMESPACE_PATH(x) "/proc/self/ns/" #x + +/** + * open_source_namespace() - Get file descriptor to current namespace. + * @nstype: Namespace type, use one of the CLONE_NEW* constants. + * + * Return: Open fd on success, -1 on error. + */ +static int open_source_namespace(const int nstype) { + const char *ns_path = NULL; + int ns_fd = -1; + + switch (nstype) { + case CLONE_NEWNET: + ns_path = SELF_NAMESPACE_PATH(net); + break; + default: + ulogd_log(ULOGD_FATAL, + "unsupported namespace type: %d\n", nstype); + return -1; + } + + ns_fd = open_namespace_path(ns_path); + if (ns_fd < 0) { + ulogd_log(ULOGD_FATAL, + "error opening namespace '%s': %s\n", + ns_path, strerror(errno)); + return -1; + } + + return ns_fd; +} +#else + +/* These constants are used by the nstype-specific functions, and need to be + * defined even when no namespace support is available because only the generic + * functions will error. + */ +#define CLONE_NEWNET -1 + +#endif /* ENABLE_NAMESPACE */ + +/** + * join_namespace_fd() - Join a namespace by file descriptor. + * @nstype: Namespace type, use one of the CLONE_NEW* constants. + * @target_ns_fd: Open file descriptor of the namespace to join. Will be closed + * after successful join. + * @source_ns_fd_ptr: If not NULL, write an open fd of the previous namespace to + * it if join was successful. May point to negative value + * after return. + * + * Return: ULOGD_IRET_OK on success, ULOGD_IRET_ERR otherwise. + */ +static int join_namespace_fd(const int nstype, const int target_ns_fd, + int *const source_ns_fd_ptr) +{ +#ifdef ENABLE_NAMESPACE + if (target_ns_fd < 0) { + ulogd_log(ULOGD_DEBUG, "invalid target namespace fd\n"); + return ULOGD_IRET_ERR; + } + + if (source_ns_fd_ptr != NULL) { + *source_ns_fd_ptr = open_source_namespace(nstype); + if (*source_ns_fd_ptr < 0) { + ulogd_log(ULOGD_FATAL, + "error opening source namespace\n"); + return ULOGD_IRET_ERR; + } + } + + if (setns(target_ns_fd, nstype) < 0) { + ulogd_log(ULOGD_FATAL, "error joining target namespace: %s\n", + strerror(errno)); + + if (source_ns_fd_ptr != NULL) { + if (close(*source_ns_fd_ptr) < 0) { + ulogd_log(ULOGD_NOTICE, + "error closing source namespace: %s\n", + strerror(errno)); + } + *source_ns_fd_ptr = -1; + } + + return ULOGD_IRET_ERR; + } + ulogd_log(ULOGD_DEBUG, "successfully switched namespace\n"); + + if (close(target_ns_fd) < 0) { + ulogd_log(ULOGD_NOTICE, "error closing target namespace: %s\n", + strerror(errno)); + } + + return ULOGD_IRET_OK; +#else + if (source_ns_fd_ptr != NULL) { + *source_ns_fd_ptr = -1; + } + ulogd_log(ULOGD_FATAL, + "ulogd was compiled without linux namespace support.\n"); + return ULOGD_IRET_ERR; +#endif /* ENABLE_NAMESPACE */ +} + +/** + * join_namespace_path() - Join a namespace by path. + * @nstype: Namespace type, use one of the CLONE_NEW* constants. + * @target_ns_path: Path of the namespace to join. + * @source_ns_fd_ptr: If not NULL, write an open fd of the previous namespace to + * it if join was successful. May point to negative value + * after return. + * + * Return: ULOGD_IRET_OK on success, ULOGD_IRET_ERR otherwise. + */ +static int join_namespace_path(const int nstype, const char *const target_ns_path, + int *const source_ns_fd_ptr) +{ +#ifdef ENABLE_NAMESPACE + int target_ns_fd, ret; + + target_ns_fd = open_namespace_path(target_ns_path); + if (target_ns_fd < 0) { + ulogd_log(ULOGD_FATAL, "error opening target namespace: %s\n", + strerror(errno)); + return ULOGD_IRET_ERR; + } + + ret = join_namespace_fd(nstype, target_ns_fd, source_ns_fd_ptr); + if (ret != ULOGD_IRET_OK) { + if (close(target_ns_fd) < 0) { + ulogd_log(ULOGD_NOTICE, + "error closing target namespace: %s\n", + strerror(errno)); + } + return ULOGD_IRET_ERR; + } + + return ULOGD_IRET_OK; +#else + return join_namespace_fd(nstype, -1, source_ns_fd_ptr); +#endif /* ENABLE_NAMESPACE */ +} + + +/** + * join_netns_fd() - Join a network namespace by file descriptor. + * @target_netns_fd: Open file descriptor of the network namespace to join. Will + * be closed after successful join. + * @source_netns_fd_ptr: If not NULL, write an open fd of the previous network + * namespace to it if join was successful. May point to + * negative value after return. + * + * Return: ULOGD_IRET_OK on success, ULOGD_IRET_ERR otherwise. + */ +int join_netns_fd(const int target_netns_fd, int *const source_netns_fd_ptr) +{ + return join_namespace_fd(CLONE_NEWNET, target_netns_fd, + source_netns_fd_ptr); +} + +/** + * join_netns_path() - Join a network namespace by path. + * @target_netns_path: Path of the network namespace to join. + * @source_netns_fd_ptr: If not NULL, write an open fd of the previous network + * namespace to it if join was successful. May point to + * negative value after return. + * + * Return: ULOGD_IRET_OK on success, ULOGD_IRET_ERR otherwise. + */ +int join_netns_path(const char *const target_netns_path, + int *const source_netns_fd_ptr) +{ + return join_namespace_path(CLONE_NEWNET, target_netns_path, + source_netns_fd_ptr); +} -- 2.49.0