From b039c9c9d2b8dcdd9accd1cba35a5119d48babf2 Mon Sep 17 00:00:00 2001 From: Corubba Smith Date: Thu, 27 Mar 2025 00:06:18 +0100 Subject: ulogd: add linux namespace helper 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 Signed-off-by: Florian Westphal --- configure.ac | 22 +++++ include/ulogd/Makefile.am | 4 +- include/ulogd/namespace.h | 8 ++ src/Makefile.am | 3 +- src/namespace.c | 224 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 259 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 55e6bc6..daaf69f 100644 --- a/configure.ac +++ b/configure.ac @@ -236,6 +236,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 + #include + ]]) +]) +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"], @@ -283,6 +304,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..d91f1e6 --- /dev/null +++ b/src/namespace.c @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: GPL-2.0 +/* ulogd namespace helper + * + * (C) 2025 The netfilter project + * + * 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 +#include +#include +#include +#include + +#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. + */ +#ifndef CLONE_NEWNET +#define CLONE_NEWNET -1 +#endif + +#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, writes 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, writes 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, writes 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, writes 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); +} -- cgit v1.2.3