Due to udevd rebroadcasting uevents, the receiver's socket buffer may
overrun and the kernel has to drop messages. Instead of failing on the
overrun, continue listening for the remaining copies if we expect a
uevent until killed by the timeout.

For each subtest do_test() of the uevent_filtering test case, the sender
triggers uevents that a receiver should listen for. These uevents may
also be received and rebroadcasted by udevd. This can happen with
some delay, so uevents from an earlier do_test() subtest may generate
rebroadcasted uevents for a subsequent subtest. Both, uevents triggered
by the sender and rebroadcasted ones are queued by the kernel to the
receiver socket buffer. The buffer may overrun forcing the kernel to
drop uevents. This causes the recvmsg() call by the receiver to fail
with ENOBUFS. To fix this, do not treat ENOBUFS as a fatal receive error
and, if we expect a uevent, keep listening for one of the ten triggered
by the sender. If the receiver is in a namespace where it should not
receive any uevents, a dropped package may be the one it should never
reach. So we defensively fail in this case. The wait is bounded by the
sender that kills the receiver after a timeout.

The rebroadcast can be observed in the udev logs:
udevd[115]:  seq 6774 queued, 'add' 'mem'
udevd[2436]: seq 6774 running
udevd[2436]: handling device node '/dev/full', devnum=c1:7
udevd[2436]: passed 200 byte device to netlink monitor
udevd[2436]: seq 6774 processed

Signed-off-by: Chris Gellermann <[email protected]>
---
 .../selftests/uevent/uevent_filtering.c       | 24 +++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/tools/testing/selftests/uevent/uevent_filtering.c 
b/tools/testing/selftests/uevent/uevent_filtering.c
index 974b076f9235..7414ce86700d 100644
--- a/tools/testing/selftests/uevent/uevent_filtering.c
+++ b/tools/testing/selftests/uevent/uevent_filtering.c
@@ -158,6 +158,30 @@ static int uevent_listener(unsigned long post_flags, bool 
expect_uevent,
                ssize_t r;
 
                r = recvmsg(sk_fd, &hdr, 0);
+               if (r < 0 && errno == ENOBUFS) {
+                       /*
+                        * The socket receive buffer overran and the kernel
+                        * dropped uevents. This may happen due to udev
+                        * rebroadcasting kernel uevents that end up in the
+                        * receive buffer as we are subscribed to all groups.
+                        *
+                        * When a uevent is expected, we trigger it multiple
+                        * times to tolerate drops, so keep listening for one
+                        * of the remaining copies. The parent bounds this
+                        * wait by killing us on a timeout.
+                        *
+                        * When no uevent is expected, our socket sits in a
+                        * namespace that should not receive any, so a drop
+                        * means traffic reached it, or that we can no longer
+                        * prove it did not. Rather fail.
+                        */
+                       if (!expect_uevent) {
+                               fprintf(stderr, "Unexpected buffer overrun\n");
+                               ret = -1;
+                               break;
+                       }
+                       continue;
+               }
                if (r <= 0) {
                        fprintf(stderr, "%s - Failed to receive uevent\n", 
strerror(errno));
                        ret = -1;
-- 
2.47.3


Reply via email to