sd-netlink: also read the reply for NFNL_MSG_BATCH_BEGIN message

When we send a batch of nfnl messages, but e.g. without sufficient
privilege, the kernel may only return an error message for
NFNL_MSG_BATCH_BEGIN and ignore all later messages.
So, we need to read the response for the NFNL_MSG_BATCH_BEGIN,
and if it is an error ignore the replies for the rest.
This commit is contained in:
Yu Watanabe
2025-12-02 19:27:56 +09:00
parent ea97ca9a06
commit 691d63dbdd

View File

@@ -7,7 +7,6 @@
#include "sd-netlink.h"
#include "alloc-util.h"
#include "errno-util.h"
#include "iovec-util.h"
#include "netlink-internal.h"
#include "netlink-util.h"
@@ -119,7 +118,7 @@ int sd_nfnl_send_batch(
return -ENOMEM;
if (ret_serials) {
serials = new(uint32_t, n_messages);
serials = new(uint32_t, n_messages + 2);
if (!serials)
return -ENOMEM;
}
@@ -133,6 +132,9 @@ int sd_nfnl_send_batch(
return r;
netlink_seal_message(nfnl, batch_begin);
if (serials)
serials[c] = message_get_serial(batch_begin);
iovs[c++] = IOVEC_MAKE(batch_begin->hdr, batch_begin->hdr->nlmsg_len);
for (size_t i = 0; i < n_messages; i++) {
@@ -147,7 +149,7 @@ int sd_nfnl_send_batch(
netlink_seal_message(nfnl, messages[i]);
if (serials)
serials[i] = message_get_serial(messages[i]);
serials[c] = message_get_serial(messages[i]);
/* It seems that the kernel accepts an arbitrary number. Let's set the lower 16 bits of the
* serial of the first message. */
@@ -161,6 +163,9 @@ int sd_nfnl_send_batch(
return r;
netlink_seal_message(nfnl, batch_end);
if (serials)
serials[c] = message_get_serial(batch_end);
iovs[c++] = IOVEC_MAKE(batch_end->hdr, batch_end->hdr->nlmsg_len);
assert(c == n_messages + 2);
@@ -192,12 +197,36 @@ int sd_nfnl_call_batch(
if (r < 0)
return r;
for (size_t i = 0; i < n_messages; i++)
RET_GATHER(r, sd_netlink_read(nfnl, serials[i], usec, /* ret= */ NULL));
if (r < 0)
return r;
for (size_t i = 1; i <= n_messages; i++) {
/* If we have received an error, kernel may not send replies for later messages. Let's ignore
* remaining replies. */
if (r < 0) {
(void) sd_netlink_ignore_serial(nfnl, serials[i], usec);
continue;
}
return 0;
r = sd_netlink_read(nfnl, serials[i], usec, /* ret= */ NULL);
if (r != -ETIMEDOUT)
continue;
/* The kernel returns some errors, e.g. unprivileged, to the BATCH_BEGIN. Hence, if we have
* not received any replies for the batch body, try to read an error in the reply for the
* batch begin. Note, since v6.10 (bf2ac490d28c21a349e9eef81edc45320fca4a3c), we can expect
* that the kernel always replies the batch begin and end. When we bump the kernel baseline,
* we can read the reply for the batch begin at first. */
int k = sd_netlink_read(nfnl, serials[0], usec, /* ret= */ NULL);
if (k < 0)
r = k;
serials[0] = 0; /* indicates that we have read the reply. */
}
/* Ignore replies for batch begin and end if we have not read them. */
if (serials[0] != 0)
(void) sd_netlink_ignore_serial(nfnl, serials[0], usec);
(void) sd_netlink_ignore_serial(nfnl, serials[n_messages + 1], usec);
return r;
}
int sd_nfnl_nft_message_new_basechain(