rsyslog/tests/omfwd-srv-discovery-udp.sh
Rainer Gerhards c780d062c6
omfwd: add DNS SRV discovery (targetSrv)
Simplify large-scale configs by auto-discovering receivers via DNS SRV
records. This reduces per-host configuration and helps enterprise and
container setups where target pools change over time.

Impact: new param `targetSrv`; config now errors on conflicts or empty
SRV answers; feature depends on resolver support.

Before: omfwd required a static host/port list via `target`/`port`.
After: `targetSrv` resolves `_syslog._udp|_tcp.<domain>` to build the
target pool, honoring RFC 2782 priority/weight and reusing existing
pool/load-balance logic.

Technically, add action param `targetSrv` (mutually exclusive with
`target`). During action init, perform SRV query via resolver
(`res_nquery`, `ns_initparse`) and translate answers into host/port
pairs. Preserve priority; randomly order same-priority entries using
weights. If explicit ports were set, warn and ignore when `targetSrv`
is used. Link rsyslogd with libresolv when available; configure checks
for headers and `ns_initparse`. Provide clear error paths (config check
fails) for missing support or empty SRV response. Docs cover usage and
env overrides `RSYSLOG_DNS_SERVER`/`RSYSLOG_DNS_PORT`. Tests add a
minimal UDP DNS server and cases for TCP/UDP success and error paths.

Fixes: https://github.com/rsyslog/rsyslog/issues/6314

With the help of AI Agent: ChatGPT Codex
2026-01-02 10:16:40 +01:00

91 lines
2.2 KiB
Bash
Executable File

#!/bin/bash
## @brief Validate SRV discovery for omfwd when resolving targets via DNS SRV over UDP.
. ${srcdir:=.}/diag.sh init
check_command_available python3
export NUMMESSAGES=40
DNS_RECORDS="$RSYSLOG_DYNNAME.srv.json"
UDP_PORT=$(get_free_port)
UDP_OUT="$RSYSLOG_OUT_LOG"
DNS_PORT_FILE="$RSYSLOG_DYNNAME.dns_port"
cat >"$DNS_RECORDS" <<EOFJSON
{
"SRV": {
"_syslog._udp.example.test.": [
{"priority": 5, "weight": 0, "port": $UDP_PORT, "target": "127.0.0.1."}
]
}
}
EOFJSON
python3 "$srcdir/dns_srv_mock.py" --port 0 --port-file "$DNS_PORT_FILE" --records "$DNS_RECORDS" &
DNS_PID=$!
wait_file_exists "$DNS_PORT_FILE"
DNS_PORT=$(cat "$DNS_PORT_FILE")
export RSYSLOG_DNS_SERVER=127.0.0.1
export RSYSLOG_DNS_PORT="$DNS_PORT"
export RES_OPTIONS="attempts:1 timeout:1"
trap 'kill $DNS_PID 2>/dev/null; kill $UDP_PID 2>/dev/null; rm -f "$DNS_PORT_FILE"' EXIT
python3 - <<'PY' "$UDP_PORT" "$UDP_OUT" "$NUMMESSAGES" &
import socket
import sys
import time
port = int(sys.argv[1])
outfile = sys.argv[2]
expected = int(sys.argv[3])
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(("127.0.0.1", port))
sock.settimeout(15)
received = 0
end_time = time.time() + 20
with open(outfile, "w", encoding="utf-8", errors="ignore") as f:
while received < expected and time.time() < end_time:
try:
data, _ = sock.recvfrom(65535)
except socket.timeout:
continue
message = data.decode(errors="ignore")
message = message.rstrip("\n")
f.write(message + "\n")
f.flush()
received += 1
sys.exit(0)
PY
UDP_PID=$!
sleep 1
generate_conf
add_conf '
$MainMsgQueueTimeoutShutdown 10000
template(name="outfmt" type="string" string="%msg:F,58:2%\n")
module(load="builtin:omfwd" template="outfmt")
if $msg contains "msgnum:" then {
action(type="omfwd" targetSrv="example.test" protocol="udp")
}
'
startup
injectmsg 0 $NUMMESSAGES
shutdown_when_empty
wait_shutdown
kill $DNS_PID 2>/dev/null
kill $UDP_PID 2>/dev/null
rm -f "$DNS_PORT_FILE"
unset RSYSLOG_DNS_SERVER RSYSLOG_DNS_PORT
unset RES_OPTIONS
trap - EXIT
if [ "$(wc -l < "$UDP_OUT")" -ne "$NUMMESSAGES" ]; then
echo "Unexpected UDP message count"
exit 1
fi
exit_test