]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sd-netlink: also read the reply for NFNL_MSG_BATCH_BEGIN message
authorYu Watanabe <watanabe.yu+github@gmail.com>
Tue, 2 Dec 2025 10:27:56 +0000 (19:27 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 3 Dec 2025 23:24:28 +0000 (08:24 +0900)
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.

src/libsystemd/sd-netlink/netlink-message-nfnl.c

index 8708aac102307abc65beda4bf435ec1816951b09..a485fd096fd617e6951811efbdeaa751258ffab6 100644 (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;
+                }
+
+                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. */
+        }
 
-        return 0;
+        /* 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(