selftests: netfilter: Test nf_tables audit logging
Compare NETFILTER_CFG type audit logs emitted from kernel upon ruleset modifications against expected output. Signed-off-by: Phil Sutter <phil@nwl.cc> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
This commit is contained in:
parent
7fb818f248
commit
e8dbde59ca
|
@ -1,3 +1,4 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
nf-queue
|
||||
connect_close
|
||||
audit_logread
|
||||
|
|
|
@ -6,13 +6,13 @@ TEST_PROGS := nft_trans_stress.sh nft_fib.sh nft_nat.sh bridge_brouter.sh \
|
|||
nft_concat_range.sh nft_conntrack_helper.sh \
|
||||
nft_queue.sh nft_meta.sh nf_nat_edemux.sh \
|
||||
ipip-conntrack-mtu.sh conntrack_tcp_unreplied.sh \
|
||||
conntrack_vrf.sh nft_synproxy.sh rpath.sh
|
||||
conntrack_vrf.sh nft_synproxy.sh rpath.sh nft_audit.sh
|
||||
|
||||
HOSTPKG_CONFIG := pkg-config
|
||||
|
||||
CFLAGS += $(shell $(HOSTPKG_CONFIG) --cflags libmnl 2>/dev/null)
|
||||
LDLIBS += $(shell $(HOSTPKG_CONFIG) --libs libmnl 2>/dev/null || echo -lmnl)
|
||||
|
||||
TEST_GEN_FILES = nf-queue connect_close
|
||||
TEST_GEN_FILES = nf-queue connect_close audit_logread
|
||||
|
||||
include ../lib.mk
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#include <linux/audit.h>
|
||||
#include <linux/netlink.h>
|
||||
|
||||
static int fd;
|
||||
|
||||
#define MAX_AUDIT_MESSAGE_LENGTH 8970
|
||||
struct audit_message {
|
||||
struct nlmsghdr nlh;
|
||||
union {
|
||||
struct audit_status s;
|
||||
char data[MAX_AUDIT_MESSAGE_LENGTH];
|
||||
} u;
|
||||
};
|
||||
|
||||
int audit_recv(int fd, struct audit_message *rep)
|
||||
{
|
||||
struct sockaddr_nl addr;
|
||||
socklen_t addrlen = sizeof(addr);
|
||||
int ret;
|
||||
|
||||
do {
|
||||
ret = recvfrom(fd, rep, sizeof(*rep), 0,
|
||||
(struct sockaddr *)&addr, &addrlen);
|
||||
} while (ret < 0 && errno == EINTR);
|
||||
|
||||
if (ret < 0 ||
|
||||
addrlen != sizeof(addr) ||
|
||||
addr.nl_pid != 0 ||
|
||||
rep->nlh.nlmsg_type == NLMSG_ERROR) /* short-cut for now */
|
||||
return -1;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int audit_send(int fd, uint16_t type, uint32_t key, uint32_t val)
|
||||
{
|
||||
static int seq = 0;
|
||||
struct audit_message msg = {
|
||||
.nlh = {
|
||||
.nlmsg_len = NLMSG_SPACE(sizeof(msg.u.s)),
|
||||
.nlmsg_type = type,
|
||||
.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK,
|
||||
.nlmsg_seq = ++seq,
|
||||
},
|
||||
.u.s = {
|
||||
.mask = key,
|
||||
.enabled = key == AUDIT_STATUS_ENABLED ? val : 0,
|
||||
.pid = key == AUDIT_STATUS_PID ? val : 0,
|
||||
}
|
||||
};
|
||||
struct sockaddr_nl addr = {
|
||||
.nl_family = AF_NETLINK,
|
||||
};
|
||||
int ret;
|
||||
|
||||
do {
|
||||
ret = sendto(fd, &msg, msg.nlh.nlmsg_len, 0,
|
||||
(struct sockaddr *)&addr, sizeof(addr));
|
||||
} while (ret < 0 && errno == EINTR);
|
||||
|
||||
if (ret != (int)msg.nlh.nlmsg_len)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int audit_set(int fd, uint32_t key, uint32_t val)
|
||||
{
|
||||
struct audit_message rep = { 0 };
|
||||
int ret;
|
||||
|
||||
ret = audit_send(fd, AUDIT_SET, key, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = audit_recv(fd, &rep);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int readlog(int fd)
|
||||
{
|
||||
struct audit_message rep = { 0 };
|
||||
int ret = audit_recv(fd, &rep);
|
||||
const char *sep = "";
|
||||
char *k, *v;
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (rep.nlh.nlmsg_type != AUDIT_NETFILTER_CFG)
|
||||
return 0;
|
||||
|
||||
/* skip the initial "audit(...): " part */
|
||||
strtok(rep.u.data, " ");
|
||||
|
||||
while ((k = strtok(NULL, "="))) {
|
||||
v = strtok(NULL, " ");
|
||||
|
||||
/* these vary and/or are uninteresting, ignore */
|
||||
if (!strcmp(k, "pid") ||
|
||||
!strcmp(k, "comm") ||
|
||||
!strcmp(k, "subj"))
|
||||
continue;
|
||||
|
||||
/* strip the varying sequence number */
|
||||
if (!strcmp(k, "table"))
|
||||
*strchrnul(v, ':') = '\0';
|
||||
|
||||
printf("%s%s=%s", sep, k, v);
|
||||
sep = " ";
|
||||
}
|
||||
if (*sep) {
|
||||
printf("\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void cleanup(int sig)
|
||||
{
|
||||
audit_set(fd, AUDIT_STATUS_ENABLED, 0);
|
||||
close(fd);
|
||||
if (sig)
|
||||
exit(0);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
struct sigaction act = {
|
||||
.sa_handler = cleanup,
|
||||
};
|
||||
|
||||
fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_AUDIT);
|
||||
if (fd < 0) {
|
||||
perror("Can't open netlink socket");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (sigaction(SIGTERM, &act, NULL) < 0 ||
|
||||
sigaction(SIGINT, &act, NULL) < 0) {
|
||||
perror("Can't set signal handler");
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
audit_set(fd, AUDIT_STATUS_ENABLED, 1);
|
||||
audit_set(fd, AUDIT_STATUS_PID, getpid());
|
||||
|
||||
while (1)
|
||||
readlog(fd);
|
||||
}
|
|
@ -6,3 +6,4 @@ CONFIG_NFT_REDIR=m
|
|||
CONFIG_NFT_MASQ=m
|
||||
CONFIG_NFT_FLOW_OFFLOAD=m
|
||||
CONFIG_NF_CT_NETLINK=m
|
||||
CONFIG_AUDIT=y
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Check that audit logs generated for nft commands are as expected.
|
||||
|
||||
SKIP_RC=4
|
||||
RC=0
|
||||
|
||||
nft --version >/dev/null 2>&1 || {
|
||||
echo "SKIP: missing nft tool"
|
||||
exit $SKIP_RC
|
||||
}
|
||||
|
||||
logfile=$(mktemp)
|
||||
echo "logging into $logfile"
|
||||
./audit_logread >"$logfile" &
|
||||
logread_pid=$!
|
||||
trap 'kill $logread_pid; rm -f $logfile' EXIT
|
||||
exec 3<"$logfile"
|
||||
|
||||
do_test() { # (cmd, log)
|
||||
echo -n "testing for cmd: $1 ... "
|
||||
cat <&3 >/dev/null
|
||||
$1 >/dev/null || exit 1
|
||||
sleep 0.1
|
||||
res=$(diff -a -u <(echo "$2") - <&3)
|
||||
[ $? -eq 0 ] && { echo "OK"; return; }
|
||||
echo "FAIL"
|
||||
echo "$res"
|
||||
((RC++))
|
||||
}
|
||||
|
||||
nft flush ruleset
|
||||
|
||||
for table in t1 t2; do
|
||||
do_test "nft add table $table" \
|
||||
"table=$table family=2 entries=1 op=nft_register_table"
|
||||
|
||||
do_test "nft add chain $table c1" \
|
||||
"table=$table family=2 entries=1 op=nft_register_chain"
|
||||
|
||||
do_test "nft add chain $table c2; add chain $table c3" \
|
||||
"table=$table family=2 entries=2 op=nft_register_chain"
|
||||
|
||||
cmd="add rule $table c1 counter"
|
||||
|
||||
do_test "nft $cmd" \
|
||||
"table=$table family=2 entries=1 op=nft_register_rule"
|
||||
|
||||
do_test "nft $cmd; $cmd" \
|
||||
"table=$table family=2 entries=2 op=nft_register_rule"
|
||||
|
||||
cmd=""
|
||||
sep=""
|
||||
for chain in c2 c3; do
|
||||
for i in {1..3}; do
|
||||
cmd+="$sep add rule $table $chain counter"
|
||||
sep=";"
|
||||
done
|
||||
done
|
||||
do_test "nft $cmd" \
|
||||
"table=$table family=2 entries=6 op=nft_register_rule"
|
||||
done
|
||||
|
||||
do_test 'nft reset rules t1 c2' \
|
||||
'table=t1 family=2 entries=3 op=nft_reset_rule'
|
||||
|
||||
do_test 'nft reset rules table t1' \
|
||||
'table=t1 family=2 entries=3 op=nft_reset_rule
|
||||
table=t1 family=2 entries=3 op=nft_reset_rule
|
||||
table=t1 family=2 entries=3 op=nft_reset_rule'
|
||||
|
||||
do_test 'nft reset rules' \
|
||||
'table=t1 family=2 entries=3 op=nft_reset_rule
|
||||
table=t1 family=2 entries=3 op=nft_reset_rule
|
||||
table=t1 family=2 entries=3 op=nft_reset_rule
|
||||
table=t2 family=2 entries=3 op=nft_reset_rule
|
||||
table=t2 family=2 entries=3 op=nft_reset_rule
|
||||
table=t2 family=2 entries=3 op=nft_reset_rule'
|
||||
|
||||
for ((i = 0; i < 500; i++)); do
|
||||
echo "add rule t2 c3 counter accept comment \"rule $i\""
|
||||
done | do_test 'nft -f -' \
|
||||
'table=t2 family=2 entries=500 op=nft_register_rule'
|
||||
|
||||
do_test 'nft reset rules t2 c3' \
|
||||
'table=t2 family=2 entries=189 op=nft_reset_rule
|
||||
table=t2 family=2 entries=188 op=nft_reset_rule
|
||||
table=t2 family=2 entries=126 op=nft_reset_rule'
|
||||
|
||||
do_test 'nft reset rules t2' \
|
||||
'table=t2 family=2 entries=3 op=nft_reset_rule
|
||||
table=t2 family=2 entries=3 op=nft_reset_rule
|
||||
table=t2 family=2 entries=186 op=nft_reset_rule
|
||||
table=t2 family=2 entries=188 op=nft_reset_rule
|
||||
table=t2 family=2 entries=129 op=nft_reset_rule'
|
||||
|
||||
do_test 'nft reset rules' \
|
||||
'table=t1 family=2 entries=3 op=nft_reset_rule
|
||||
table=t1 family=2 entries=3 op=nft_reset_rule
|
||||
table=t1 family=2 entries=3 op=nft_reset_rule
|
||||
table=t2 family=2 entries=3 op=nft_reset_rule
|
||||
table=t2 family=2 entries=3 op=nft_reset_rule
|
||||
table=t2 family=2 entries=180 op=nft_reset_rule
|
||||
table=t2 family=2 entries=188 op=nft_reset_rule
|
||||
table=t2 family=2 entries=135 op=nft_reset_rule'
|
||||
|
||||
exit $RC
|
Loading…
Reference in New Issue