Compare commits

...

3 Commits

Author SHA1 Message Date
Rainer Gerhards
8dc8a489ae
maintain ChangeLog 2025-11-28 13:12:18 +01:00
Rainer Gerhards
e6db7c9fc7
Merge pull request #5519 from billie-alsup/dev/balsup/imtcp-netns
imtcp support for NetworkNamespace
2025-11-28 13:07:06 +01:00
Billie Alsup
367c47e38c imtcp support for NetworkNamespace
This builds on "PR#6121 net: Add NetworkNamespace APIS"
to add Network Namespace support to imtcp module.  This
extends imtcp to support a wider range of Unix/Linux
environments (or any environment supporting network
namespaces).

The imtcp module is enhanced to accept a NetworkNamespace
parameter, both as a default at the module level, and
on a per-instance basis.

The tcpsrv module is enhanced to allow the NetworkNamespace
to be applied to a listener's configuration parameters.

Finally, the netstrm module is enhanced to switch namespaces
before invoking the downstream (driver specific) LstnInit
function.

A new test imtcp-netns (and associated imtcp-netns-vg) is
added to test this functionality.  This must be run as root
(technically it must be run by a user with CAP_SYS_ADMIN
capabilities, as network namespace creating/change is
required).

A slight change to diag.sh is made to allow passing $RS_REDIR
to valgrind (as $RS_REDIR is used in the imtcp-netns.sh
test for some negative cases).

Signed-off-by: Billie Alsup <balsup@cisco.com>
2025-09-14 08:16:33 -07:00
8 changed files with 258 additions and 8 deletions

View File

@ -1,6 +1,30 @@
----------------------------------------------------------------------------------------
Scheduled Release 8.2512.0 (aka 2025.10) 2025-12-0?
- overall improved documentation via a large set of topic updates.
- 2025-11-28: imtcp: add support for NetworkNamespace
This builds on
https://github.com/rsyslog/rsyslog/pull/6121
to add Network Namespace support to imtcp module. This
extends imtcp to support a wider range of Unix/Linux
environments (or any environment supporting network
namespaces).
The imtcp module is enhanced to accept a NetworkNamespace
parameter, both as a default at the module level, and
on a per-instance basis.
The tcpsrv module is enhanced to allow the NetworkNamespace
to be applied to a listener's configuration parameters.
Finally, the netstrm module is enhanced to switch namespaces
before invoking the downstream (driver specific) LstnInit
function.
A new test imtcp-netns (and associated imtcp-netns-vg) is
added to test this functionality. This must be run as root
(technically it must be run by a user with CAP_SYS_ADMIN
capabilities, as network namespace creating/change is
required).
A slight change to diag.sh is made to allow passing $RS_REDIR
to valgrind (as $RS_REDIR is used in the imtcp-netns.sh
test for some negative cases).
Thanks to Billie Alsup for the patch.
- 2025-11-25: mmanon: fixed data race issue in pseudonymization handling
Thanks to Jan Gerhards for the patch.
- 2025-11-12: omhttp: added explicit splunk HEC profile

View File

@ -63,7 +63,6 @@
#include "tcpsrv.h"
#include "ruleset.h"
#include "rainerscript.h"
#include "net.h"
#include "parserif.h"
MODULE_TYPE_INPUT;
@ -143,6 +142,7 @@ struct instanceConf_s {
int bEmitMsgOnOpen;
int bPreserveCase;
int iSynBacklog;
char *pszNetworkNamespace; /**< optional network name to use */
uchar *pszStrmDrvrName; /* stream driver to use */
int iStrmDrvrMode;
uchar *pszStrmDrvrAuthMode;
@ -188,6 +188,7 @@ struct modConfData_s {
sbool bEmitMsgOnClose; /* emit an informational message on close by remote peer */
sbool bEmitMsgOnOpen; /* emit an informational message on close by remote peer */
uchar *gnutlsPriorityString;
char *pszNetworkNamespace; /**< default network namespace to use */
uchar *pszStrmDrvrName; /* stream driver to use */
uchar *pszStrmDrvrAuthMode; /* authentication mode to use */
uchar *pszStrmDrvrPermitExpiredCerts; /* control how to handly expired certificates */
@ -235,7 +236,8 @@ static struct cnfparamdescr modpdescr[] = {{"flowcontrol", eCmdHdlrBinary, 0},
{"keepalive.time", eCmdHdlrNonNegInt, 0},
{"keepalive.interval", eCmdHdlrNonNegInt, 0},
{"gnutlsprioritystring", eCmdHdlrString, 0},
{"preservecase", eCmdHdlrBinary, 0}};
{"preservecase", eCmdHdlrBinary, 0},
{"networknamespace", eCmdHdlrString, 0}};
static struct cnfparamblk modpblk = {CNFPARAMBLK_VERSION, sizeof(modpdescr) / sizeof(struct cnfparamdescr), modpdescr};
/* input instance parameters */
@ -278,7 +280,8 @@ static struct cnfparamdescr inppdescr[] = {{"port", eCmdHdlrString, CNFPARAM_REQ
{"ratelimit.interval", eCmdHdlrInt, 0},
{"framingfix.cisco.asa", eCmdHdlrBinary, 0},
{"ratelimit.burst", eCmdHdlrInt, 0},
{"socketbacklog", eCmdHdlrNonNegInt, 0}};
{"socketbacklog", eCmdHdlrNonNegInt, 0},
{"networknamespace", eCmdHdlrString, 0}};
static struct cnfparamblk inppblk = {CNFPARAMBLK_VERSION, sizeof(inppdescr) / sizeof(struct cnfparamdescr), inppdescr};
#include "im-helper.h" /* must be included AFTER the type definitions! */
@ -362,6 +365,7 @@ static rsRetVal createInstance(instanceConf_t **pinst) {
inst->ratelimitInterval = 0;
inst->ratelimitBurst = 10000;
inst->pszNetworkNamespace = NULL;
inst->pszStrmDrvrName = NULL;
inst->pszStrmDrvrAuthMode = NULL;
inst->pszStrmDrvrPermitExpiredCerts = NULL;
@ -413,7 +417,7 @@ finalize_it:
}
/* This function is called when a new listener instace shall be added to
/* This function is called when a new listener instance shall be added to
* the current config object via the legacy config system. It just shuffles
* all parameters to the listener in-memory instance.
* rgerhards, 2011-05-04
@ -473,6 +477,7 @@ finalize_it:
static rsRetVal addListner(modConfData_t *modConf, instanceConf_t *inst) {
DEFiRet;
uchar *psz; /* work variable */
char *ns; /**< network namespace */
permittedPeers_t *peers;
tcpsrv_t *pOurTcpsrv;
@ -542,6 +547,10 @@ static rsRetVal addListner(modConfData_t *modConf, instanceConf_t *inst) {
/* initialized, now add socket and listener params */
DBGPRINTF("imtcp: trying to add port *:%s\n", inst->cnf_params->pszPort);
inst->cnf_params->pRuleset = inst->pBindRuleset;
ns = (inst->pszNetworkNamespace == NULL) ? modConf->pszNetworkNamespace : inst->pszNetworkNamespace;
CHKiRet(tcpsrv.SetNetworkNamespace(pOurTcpsrv, inst->cnf_params, ns));
CHKiRet(tcpsrv.SetInputName(pOurTcpsrv, inst->cnf_params,
inst->pszInputName == NULL ? UCHAR_CONSTANT("imtcp") : inst->pszInputName));
CHKiRet(tcpsrv.SetOrigin(pOurTcpsrv, (uchar *)"imtcp"));
@ -596,6 +605,8 @@ BEGINnewInpInst
if (!pvals[i].bUsed) continue;
if (!strcmp(inppblk.descr[i].name, "port")) {
inst->cnf_params->pszPort = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL);
} else if (!strcmp(inppblk.descr[i].name, "networknamespace")) {
inst->pszNetworkNamespace = es_str2cstr(pvals[i].val.d.estr, NULL);
} else if (!strcmp(inppblk.descr[i].name, "address")) {
inst->cnf_params->pszAddr = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL);
} else if (!strcmp(inppblk.descr[i].name, "name")) {
@ -729,6 +740,7 @@ BEGINbeginCnfLoad
loadModConf->bDisableLFDelim = 0;
loadModConf->discardTruncatedMsg = 0;
loadModConf->gnutlsPriorityString = NULL;
loadModConf->pszNetworkNamespace = NULL;
loadModConf->pszStrmDrvrName = NULL;
loadModConf->pszStrmDrvrAuthMode = NULL;
loadModConf->pszStrmDrvrPermitExpiredCerts = NULL;
@ -808,6 +820,8 @@ BEGINsetModCnf
loadModConf->iKeepAliveIntvl = (int)pvals[i].val.d.n;
} else if (!strcmp(modpblk.descr[i].name, "gnutlsprioritystring")) {
loadModConf->gnutlsPriorityString = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL);
} else if (!strcmp(modpblk.descr[i].name, "networknamespace")) {
loadModConf->pszNetworkNamespace = es_str2cstr(pvals[i].val.d.estr, NULL);
} else if (!strcmp(modpblk.descr[i].name, "streamdriver.mode")) {
loadModConf->iStrmDrvrMode = (int)pvals[i].val.d.n;
} else if (!strcmp(modpblk.descr[i].name, "streamdriver.CheckExtendedKeyPurpose")) {
@ -950,6 +964,7 @@ BEGINfreeCnf
instanceConf_t *inst, *del;
CODESTARTfreeCnf;
free(pModConf->gnutlsPriorityString);
free(pModConf->pszNetworkNamespace);
free(pModConf->pszStrmDrvrName);
free(pModConf->pszStrmDrvrAuthMode);
free(pModConf->pszStrmDrvrPermitExpiredCerts);
@ -964,6 +979,7 @@ BEGINfreeCnf
for (inst = pModConf->root; inst != NULL;) {
free((void *)inst->pszBindRuleset);
free((void *)inst->pszStrmDrvrAuthMode);
free((void *)inst->pszNetworkNamespace);
free((void *)inst->pszStrmDrvrName);
free((void *)inst->pszStrmDrvrPermitExpiredCerts);
free((void *)inst->pszStrmDrvrCAFile);

View File

@ -58,7 +58,7 @@
/* static data */
DEFobjStaticHelpers;
DEFobjCurrIf(netstrms)
DEFobjCurrIf(net) DEFobjCurrIf(netstrms)
/* Standard-Constructor */
@ -145,14 +145,31 @@ static rsRetVal ATTR_NONNULL(1, 3, 5) LstnInit(netstrms_t *pNS,
const int iSessMax,
const tcpLstnParams_t *const cnf_params) {
DEFiRet;
const char *ns = cnf_params->pszNetworkNamespace;
int netns_fd = -1;
ISOBJ_TYPE_assert(pNS, netstrms);
assert(fAddLstn != NULL);
assert(cnf_params->pszPort != NULL);
#ifdef HAVE_SETNS
if (ns) {
CHKiRet(net.netns_save(&netns_fd));
CHKiRet(net.netns_switch(ns));
}
#endif // ndef HAVE_SETNS
CHKiRet(pNS->Drvr.LstnInit(pNS, pUsr, fAddLstn, iSessMax, cnf_params));
finalize_it:
#ifdef HAVE_SETNS
if (ns) {
// netns_restore will log a message on failure
// but there's really nothing we can do about it
(void)net.netns_restore(&netns_fd);
}
#endif // ndef HAVE_SETNS
RETiRet;
}
@ -493,6 +510,7 @@ BEGINObjClassExit(netstrm, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END M
CODESTARTObjClassExit(netstrm);
/* release objects we no longer need */
objRelease(netstrms, DONT_LOAD_LIB);
objRelease(net, LM_NET_FILENAME);
ENDObjClassExit(netstrm)
@ -502,6 +520,7 @@ ENDObjClassExit(netstrm)
*/
BEGINAbstractObjClassInit(netstrm, 1, OBJ_IS_CORE_MODULE) /* class, version */
/* request objects we use */
CHKiRet(objUse(net, LM_NET_FILENAME));
/* set our own handlers */
ENDObjClassInit(netstrm)

View File

@ -536,6 +536,7 @@ static void ATTR_NONNULL() deinit_tcp_listener(tcpsrv_t *const pThis) {
free((void *)pEntry->cnf_params->pszPort);
free((void *)pEntry->cnf_params->pszAddr);
free((void *)pEntry->cnf_params->pszLstnPortFileName);
free((void *)pEntry->cnf_params->pszNetworkNamespace);
free((void *)pEntry->cnf_params);
ratelimitDestruct(pEntry->ratelimiter);
statsobj.Destruct(&(pEntry->stats));
@ -604,12 +605,15 @@ static rsRetVal ATTR_NONNULL() create_tcp_socket(tcpsrv_t *const pThis) {
while (pEntry != NULL) {
localRet = initTCPListener(pThis, pEntry);
if (localRet != RS_RET_OK) {
char *ns = pEntry->cnf_params->pszNetworkNamespace;
LogError(
0, localRet,
"Could not create tcp listener, ignoring port "
"%s bind-address %s.",
"%s bind-address %s%s%s.",
(pEntry->cnf_params->pszPort == NULL) ? "**UNSPECIFIED**" : (const char *)pEntry->cnf_params->pszPort,
(pEntry->cnf_params->pszAddr == NULL) ? "**UNSPECIFIED**" : (const char *)pEntry->cnf_params->pszAddr);
(pEntry->cnf_params->pszAddr == NULL) ? "**UNSPECIFIED**" : (const char *)pEntry->cnf_params->pszAddr,
(ns == NULL) ? "" : " namespace ", (ns == NULL) ? "" : ns);
}
pEntry = pEntry->pNext;
}
@ -1978,6 +1982,25 @@ finalize_it:
RETiRet;
}
static rsRetVal SetNetworkNamespace(tcpsrv_t *pThis __attribute__((unused)),
tcpLstnParams_t *const cnf_params,
const char *const networkNamespace) {
DEFiRet;
ISOBJ_TYPE_assert(pThis, tcpsrv);
free(cnf_params->pszNetworkNamespace);
if (!networkNamespace || !*networkNamespace) {
cnf_params->pszNetworkNamespace = NULL;
} else {
#ifdef HAVE_SETNS
CHKmalloc(cnf_params->pszNetworkNamespace = strdup(networkNamespace));
#else // ndef HAVE_SETNS
LogError(0, RS_RET_VALUE_NOT_SUPPORTED, "Namespaces are not supported");
ABORT_FINALIZE(RS_RET_VALUE_NOT_SUPPORTED);
#endif // #else ndef HAVE_SETNS
}
finalize_it:
RETiRet;
}
/* Set the linux-like ratelimiter settings */
static rsRetVal ATTR_NONNULL(1)
@ -2207,6 +2230,7 @@ BEGINobjQueryInterface(tcpsrv)
pIf->create_tcp_socket = create_tcp_socket;
pIf->Run = Run;
pIf->SetNetworkNamespace = SetNetworkNamespace;
pIf->SetKeepAlive = SetKeepAlive;
pIf->SetKeepAliveIntvl = SetKeepAliveIntvl;
pIf->SetKeepAliveProbes = SetKeepAliveProbes;

View File

@ -47,6 +47,7 @@ struct tcpLstnParams_s {
sbool bSPFramingFix; /**< support work-around for broken Cisco ASA framing? */
sbool bPreserveCase; /**< preserve case in fromhost */
const uchar *pszLstnPortFileName; /**< File in which the dynamic port is written */
char *pszNetworkNamespace; /**< network namespace to use */
uchar *pszStrmDrvrName; /**< stream driver to use */
uchar *pszInputName; /**< value to be used as input name */
prop_t *pInputName;
@ -280,8 +281,27 @@ BEGINinterface(tcpsrv) /* name must also be changed in ENDinterface macro! */
/* added v28 */
rsRetVal (*SetNumWrkr)(tcpsrv_t *pThis, int);
rsRetVal (*SetStarvationMaxReads)(tcpsrv_t *pThis, unsigned int);
/* added v29 */
/*
* @brief Set the Network Namespace into the listener parameters
* @param pThis The associated TCP Server instance
* @param cnf_params The listener parameters to configure
* @param networkNamespace The namespace parameter to set into the
* listener configuration parameters
* @return RS_RET_OK on success, otherwise a failure code.
* @details For platforms that do not support network namespaces,
* this function should fail for any non-null and non-empty
* namespace passed. Note that the empty string is treated
* the same as a NULL, i.e. you are not allowed to actually
* use a network namespace with the empty string. So both
* a NULL and an empty string "" both mean to use the
* original startup network namespace.
*/
rsRetVal (*SetNetworkNamespace)(tcpsrv_t *pThis, tcpLstnParams_t *const cnf_params,
const char *const networkNamespace);
ENDinterface(tcpsrv)
#define tcpsrvCURR_IF_VERSION 28 /* increment whenever you change the interface structure! */
#define tcpsrvCURR_IF_VERSION 29 /* increment whenever you change the interface structure! */
/* change for v4:
* - SetAddtlFrameDelim() added -- rgerhards, 2008-12-10
* - SetInputName() added -- rgerhards, 2008-12-10

View File

@ -650,6 +650,7 @@ TESTS += \
imtcp-maxFrameSize.sh \
imtcp-msg-truncation-on-number.sh \
imtcp-msg-truncation-on-number2.sh \
imtcp-netns.sh \
imtcp-NUL.sh \
imtcp-NUL-rawmsg.sh \
imtcp_incomplete_frame_at_end.sh \
@ -1503,6 +1504,7 @@ TESTS += \
imptcp-oversize-message-display.sh \
imptcp-msg-truncation-on-number.sh \
imptcp-msg-truncation-on-number2.sh \
imtcp-netns.sh \
imptcp-maxFrameSize-parameter.sh \
mmjsonparse_cim.sh \
mmjsonparse_cim2.sh \
@ -2549,6 +2551,7 @@ EXTRA_DIST= \
imptcp-oversize-message-display.sh \
imptcp-msg-truncation-on-number.sh \
imptcp-msg-truncation-on-number2.sh \
imtcp-netns.sh \
imptcp-maxFrameSize-parameter.sh \
mmjsonparse_cim.sh \
mmjsonparse_cim2.sh \
@ -2638,6 +2641,7 @@ EXTRA_DIST= \
imtcp-maxFrameSize.sh \
imtcp-msg-truncation-on-number.sh \
imtcp-msg-truncation-on-number2.sh \
imtcp-netns.sh \
imtcp-NUL.sh \
imtcp-NUL-rawmsg.sh \
imtcp-tls-gtls-x509fingerprint-invld.sh \

3
tests/imtcp-netns-vg.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
export USE_VALGRIND="YES"
source ${srcdir:-.}/imtcp-netns.sh

140
tests/imtcp-netns.sh Executable file
View File

@ -0,0 +1,140 @@
#!/bin/bash
# This test tests listening on ports in another network namespace.
echo ===============================================================================
echo \[imtcp-netns.sh\]: test for listening in another network namespace
echo This test must be run with CAP_SYS_ADMIN capabilities [network namespace creation/change required]
if [ "$EUID" -ne 0 ]; then
exit 77 # Not root, skip this test
fi
. ${srcdir:=.}/diag.sh init
generate_conf
NS_PREFIX=$(basename ${RSYSLOG_DYNNAME})
NS_DEFAULT="${NS_PREFIX}.rsyslog_ns_default"
NS_FAIL=(
"${NS_PREFIX}.rsyslog_ns_a_fail"
"${NS_PREFIX}.rsyslog_ns_b_fail"
)
NS_GOOD=(
"${NS_PREFIX}.rsyslog_ns_c"
"${NS_PREFIX}.rsyslog_ns_d"
)
add_conf '
$MainMsgQueueTimeoutShutdown 10000
module(
load="../plugins/imtcp/.libs/imtcp"
NetworkNamespace="'${NS_DEFAULT}'"
)
input(
Type="imtcp"
Port="0"
Address="127.0.0.1"
ListenPortFileName="'${NS_DEFAULT}'.port"
)
'
for ns in "${NS_GOOD[@]}" "${NS_FAIL[@]}"; do
add_conf '
input(
Type="imtcp"
Port="0"
Address="127.0.0.1"
NetworkNamespace="'${ns}'"
ListenPortFileName="'${ns}'.port"
)
'
done
add_conf '
input(
Type="imtcp"
Port="0"
Address="127.0.0.1"
NetworkNamespace=""
ListenPortFileName="'$RSYSLOG_DYNNAME'.port"
)
template(name="outfmt" type="string" string="%msg:%\n")
:msg, contains, "imtcp-netns:" action(
type="omfile"
file="'${RSYSLOG_OUT_LOG}'"
template="outfmt"
)
'
#
# create network namespace and bring it up
NS=(
"${NS_DEFAULT}"
"${NS_GOOD[@]}"
)
for ns in "${NS[@]}"; do
ip netns add "${ns}"
ip netns exec "${ns}" ip link set dev lo up
done
for ns in "${NS_FAIL[@]}"; do
ip netns delete "${ns}" > /dev/null 2>&1
done
# now do the usual run
RS_REDIR="> ${RSYSLOG_DYNNAME}.log 2>&1"
startup
wait_file_exists "${RSYSLOG_DYNNAME}.port"
for ns in "${NS[@]}"; do
wait_file_exists "${ns}.port"
done
MSGS=()
function logmsg() {
local ns=$1
local msg=$2
local logger_cmd=()
local port_file=${RSYSLOG_DYNNAME}.port
if [[ -n "${ns}" ]]; then
logger_cmd+=(ip netns exec "${ns}")
port_file="${ns}.port"
fi
logger_cmd+=(
logger
--tcp
--server 127.0.0.1
--octet-count
--tag "$0"
--port "$(cat ${port_file})"
--
"imtcp-netns: ${msg}"
)
"${logger_cmd[@]}"
MSGS+=("imtcp-netns: ${msg}")
}
# Inject a few messages
logmsg "" "start message from local namespace"
for ns in "${NS[@]}"; do
logmsg "${ns}" "test message from namespace ${ns}"
# Try to keep them ordered
sleep 1
done
logmsg "" "end message from local namespace"
shutdown_when_empty
wait_shutdown
# remove network namespaces
for ns in "${NS[@]}"; do
ip netns delete "${ns}"
done
EXPECTED=$(printf "%s\n" "${MSGS[@]}")
cmp_exact
# Verify we have error messages for the missing namespaces
# We don't redirect with valgrind, so skip this check with valgrind
if [[ "${USE_VALGRIND}" != "YES" ]]; then
for ns in "${NS_FAIL[@]}"; do
content_check "netns_switch: could not open namespace '${ns}':" ${RSYSLOG_DYNNAME}.log
done
fi
exit_test