Coverage Report

Created: 2026-07-30 06:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/systemd/src/libsystemd/sd-netlink/netlink-socket.c
Line
Count
Source
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3
#include "sd-netlink.h"
4
5
#include "alloc-util.h"
6
#include "errno-util.h"
7
#include "hashmap.h"
8
#include "iovec-util.h"
9
#include "log.h"
10
#include "netlink-internal.h"
11
#include "netlink-types.h"
12
#include "ordered-set.h"
13
#include "socket-util.h"
14
15
23.7k
static int broadcast_groups_get(sd_netlink *nl) {
16
23.7k
        _cleanup_free_ uint32_t *groups = NULL;
17
23.7k
        size_t len;
18
23.7k
        int r;
19
20
23.7k
        assert(nl);
21
23.7k
        assert(nl->fd >= 0);
22
23
23.7k
        r = netlink_socket_get_multicast_groups(nl->fd, &len, &groups);
24
23.7k
        if (r < 0)
25
0
                return r;
26
27
23.7k
        for (size_t i = 0; i < len; i++)
28
0
                for (unsigned j = 0; j < sizeof(uint32_t) * 8; j++)
29
0
                        if (groups[i] & (1U << j)) {
30
0
                                unsigned group = i * sizeof(uint32_t) * 8 + j + 1;
31
32
0
                                r = hashmap_ensure_put(&nl->broadcast_group_refs, NULL, UINT_TO_PTR(group), UINT_TO_PTR(1));
33
0
                                if (r < 0)
34
0
                                        return r;
35
0
                        }
36
37
23.7k
        return 0;
38
23.7k
}
39
40
23.7k
int socket_bind(sd_netlink *nl) {
41
23.7k
        socklen_t addrlen;
42
23.7k
        int r;
43
44
23.7k
        r = setsockopt_int(nl->fd, SOL_NETLINK, NETLINK_PKTINFO, true);
45
23.7k
        if (r < 0)
46
0
                return r;
47
48
23.7k
        addrlen = sizeof(nl->sockaddr);
49
50
        /* ignore EINVAL to allow binding an already bound socket */
51
23.7k
        if (bind(nl->fd, &nl->sockaddr.sa, addrlen) < 0 && errno != EINVAL)
52
0
                return -errno;
53
54
23.7k
        if (getsockname(nl->fd, &nl->sockaddr.sa, &addrlen) < 0)
55
0
                return -errno;
56
57
23.7k
        return broadcast_groups_get(nl);
58
23.7k
}
59
60
0
static unsigned broadcast_group_get_ref(sd_netlink *nl, unsigned group) {
61
0
        assert(nl);
62
63
0
        return PTR_TO_UINT(hashmap_get(nl->broadcast_group_refs, UINT_TO_PTR(group)));
64
0
}
65
66
0
static int broadcast_group_set_ref(sd_netlink *nl, unsigned group, unsigned n_ref) {
67
0
        assert(nl);
68
69
0
        return hashmap_ensure_replace(&nl->broadcast_group_refs, NULL, UINT_TO_PTR(group), UINT_TO_PTR(n_ref));
70
0
}
71
72
0
static int broadcast_group_join(sd_netlink *nl, unsigned group) {
73
0
        assert(nl);
74
0
        assert(nl->fd >= 0);
75
0
        assert(group > 0);
76
77
        /* group is "unsigned", but netlink(7) says the argument for NETLINK_ADD_MEMBERSHIP is "int" */
78
0
        return setsockopt_int(nl->fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, group);
79
0
}
80
81
0
int socket_broadcast_group_ref(sd_netlink *nl, unsigned group) {
82
0
        unsigned n_ref;
83
0
        int r;
84
85
0
        assert(nl);
86
87
0
        n_ref = broadcast_group_get_ref(nl, group);
88
89
0
        n_ref++;
90
91
0
        r = broadcast_group_set_ref(nl, group, n_ref);
92
0
        if (r < 0)
93
0
                return r;
94
95
0
        if (n_ref > 1)
96
                /* already in the group */
97
0
                return 0;
98
99
0
        return broadcast_group_join(nl, group);
100
0
}
101
102
0
int socket_broadcast_group_unref(sd_netlink *nl, unsigned group) {
103
0
        unsigned n_ref;
104
0
        int r;
105
106
0
        assert(nl);
107
108
0
        n_ref = broadcast_group_get_ref(nl, group);
109
0
        if (n_ref == 0)
110
0
                return 0;
111
112
0
        n_ref--;
113
114
0
        r = broadcast_group_set_ref(nl, group, n_ref);
115
0
        if (r < 0)
116
0
                return r;
117
118
0
        if (n_ref > 0)
119
                /* still refs left */
120
0
                return 0;
121
122
        /* group is "unsigned", but netlink(7) says the argument for NETLINK_DROP_MEMBERSHIP is "int" */
123
0
        return setsockopt_int(nl->fd, SOL_NETLINK, NETLINK_DROP_MEMBERSHIP, group);
124
0
}
125
126
/* returns the number of bytes sent, or a negative error code */
127
39.8k
int socket_write_message(sd_netlink *nl, sd_netlink_message *m) {
128
39.8k
        union sockaddr_union addr = {
129
39.8k
                .nl.nl_family = AF_NETLINK,
130
39.8k
        };
131
39.8k
        ssize_t k;
132
133
39.8k
        assert(nl);
134
39.8k
        assert(m);
135
39.8k
        assert(m->hdr);
136
137
39.8k
        k = sendto(nl->fd, m->hdr, m->hdr->nlmsg_len, 0, &addr.sa, sizeof(addr));
138
39.8k
        if (k < 0)
139
0
                return -errno;
140
141
39.8k
        return k;
142
39.8k
}
143
144
79.7k
static int socket_recv_message(int fd, void *buf, size_t buf_size, uint32_t *ret_mcast_group, bool peek) {
145
79.7k
        struct iovec iov = IOVEC_MAKE(buf, buf_size);
146
79.7k
        union sockaddr_union sender;
147
79.7k
        CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct nl_pktinfo))) control;
148
79.7k
        struct msghdr msg = {
149
79.7k
                .msg_iov = &iov,
150
79.7k
                .msg_iovlen = 1,
151
79.7k
                .msg_name = &sender,
152
79.7k
                .msg_namelen = sizeof(sender),
153
79.7k
                .msg_control = &control,
154
79.7k
                .msg_controllen = sizeof(control),
155
79.7k
        };
156
79.7k
        ssize_t n;
157
158
79.7k
        assert(fd >= 0);
159
79.7k
        assert(peek || (buf && buf_size > 0));
160
161
        /* Note: this might return successfully, but with a zero size under some transient conditions, such
162
         * as the reception of a non-kernel message. In such a case the passed buffer might or might not be
163
         * modified. Caller must treat a zero return as "no message, but also not an error". */
164
165
79.7k
        n = recvmsg_safe(fd, &msg, peek ? (MSG_PEEK|MSG_TRUNC) : 0);
166
79.7k
        if (ERRNO_IS_NEG_TRANSIENT(n))
167
0
                goto transient;
168
79.7k
        if (n == -ENOBUFS)
169
0
                return log_debug_errno(n, "sd-netlink: kernel receive buffer overrun");
170
79.7k
        if (n == -ECHRNG)
171
0
                return log_debug_errno(n, "sd-netlink: got truncated control message");
172
79.7k
        if (n == -EXFULL)
173
0
                return log_debug_errno(n, "sd-netlink: got truncated payload message");
174
79.7k
        if (n < 0)
175
0
                return (int) n;
176
177
79.7k
        if (sender.nl.nl_pid != 0) {
178
                /* not from the kernel, ignore */
179
0
                log_debug("sd-netlink: ignoring message from PID %"PRIu32, sender.nl.nl_pid);
180
181
0
                if (peek) {
182
                        /* Drop the message. Note that we ignore ECHRNG/EXFULL errors here, which
183
                         * recvmsg_safe() returns in case the payload or cdata is truncated. Given we just
184
                         * want to drop the message we also don't care if its payload or cdata was
185
                         * truncated. */
186
0
                        n = recvmsg_safe(fd, &msg, 0);
187
0
                        if (n < 0 && !IN_SET(n, -ECHRNG, -EXFULL))
188
0
                                return (int) n;
189
0
                }
190
191
0
                goto transient;
192
0
        }
193
194
79.7k
        if (ret_mcast_group) {
195
39.8k
                struct nl_pktinfo *pi;
196
197
39.8k
                pi = CMSG_FIND_DATA(&msg, SOL_NETLINK, NETLINK_PKTINFO, struct nl_pktinfo);
198
39.8k
                if (pi)
199
39.8k
                        *ret_mcast_group = pi->group;
200
0
                else
201
0
                        *ret_mcast_group = 0;
202
39.8k
        }
203
204
79.7k
        return (int) n;
205
206
0
transient:
207
0
        if (ret_mcast_group)
208
0
                *ret_mcast_group = 0;
209
210
0
        return 0;
211
79.7k
}
212
213
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
214
        netlink_message_hash_ops,
215
        void, trivial_hash_func, trivial_compare_func,
216
        sd_netlink_message, sd_netlink_message_unref);
217
218
39.8k
static int netlink_queue_received_message(sd_netlink *nl, sd_netlink_message *m) {
219
39.8k
        uint32_t serial;
220
39.8k
        int r;
221
222
39.8k
        assert(nl);
223
39.8k
        assert(m);
224
225
39.8k
        serial = message_get_serial(m);
226
39.8k
        if (serial != 0) {
227
39.8k
                NetlinkIgnoredSerial *s = hashmap_remove(nl->ignored_serials, UINT32_TO_PTR(serial));
228
39.8k
                if (s) {
229
                        /* We are not interested in the message anymore. */
230
0
                        free(s);
231
0
                        return 0;
232
0
                }
233
39.8k
        }
234
235
39.8k
        if (ordered_set_size(nl->rqueue) >= NETLINK_RQUEUE_MAX)
236
0
                return log_debug_errno(SYNTHETIC_ERRNO(ENOBUFS),
237
39.8k
                                       "sd-netlink: exhausted the read queue size (%d)", NETLINK_RQUEUE_MAX);
238
239
39.8k
        r = ordered_set_ensure_put(&nl->rqueue, &netlink_message_hash_ops, m);
240
39.8k
        if (r < 0)
241
0
                return r;
242
243
39.8k
        sd_netlink_message_ref(m);
244
245
39.8k
        if (sd_netlink_message_is_broadcast(m))
246
0
                return 0;
247
248
39.8k
        if (serial == 0)
249
0
                return 0;
250
251
39.8k
        if (sd_netlink_message_get_errno(m) < 0) {
252
33.8k
                _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *old = NULL;
253
254
33.8k
                old = hashmap_remove(nl->rqueue_by_serial, UINT32_TO_PTR(serial));
255
33.8k
                if (old)
256
33.8k
                        log_debug("sd-netlink: received error message with serial %"PRIu32", but another message with "
257
33.8k
                                  "the same serial is already stored in the read queue, replacing.", serial);
258
33.8k
        }
259
260
39.8k
        r = hashmap_ensure_put(&nl->rqueue_by_serial, &netlink_message_hash_ops, UINT32_TO_PTR(serial), m);
261
39.8k
        if (r == -EEXIST) {
262
0
                if (!sd_netlink_message_is_error(m))
263
0
                        log_debug("sd-netlink: received message with serial %"PRIu32", but another message with "
264
0
                                  "the same serial is already stored in the read queue, ignoring.", serial);
265
0
                return 0;
266
0
        }
267
39.8k
        if (r < 0) {
268
0
                sd_netlink_message_unref(ordered_set_remove(nl->rqueue, m));
269
0
                return r;
270
0
        }
271
272
39.8k
        sd_netlink_message_ref(m);
273
39.8k
        return 0;
274
39.8k
}
275
276
0
static int netlink_queue_partially_received_message(sd_netlink *nl, sd_netlink_message *m) {
277
0
        uint32_t serial;
278
0
        int r;
279
280
0
        assert(nl);
281
0
        assert(m);
282
0
        assert(m->hdr->nlmsg_flags & NLM_F_MULTI);
283
284
0
        if (hashmap_size(nl->rqueue_partial_by_serial) >= NETLINK_RQUEUE_MAX)
285
0
                return log_debug_errno(SYNTHETIC_ERRNO(ENOBUFS),
286
0
                                       "sd-netlink: exhausted the partial read queue size (%d)", NETLINK_RQUEUE_MAX);
287
288
0
        serial = message_get_serial(m);
289
0
        r = hashmap_ensure_put(&nl->rqueue_partial_by_serial, &netlink_message_hash_ops, UINT32_TO_PTR(serial), m);
290
0
        if (r < 0)
291
0
                return r;
292
293
0
        sd_netlink_message_ref(m);
294
0
        return 0;
295
0
}
296
297
39.8k
static int parse_message_one(sd_netlink *nl, uint32_t group, const struct nlmsghdr *hdr, sd_netlink_message **ret) {
298
39.8k
        _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
299
39.8k
        size_t size;
300
39.8k
        int r;
301
302
39.8k
        assert(nl);
303
39.8k
        assert(hdr);
304
39.8k
        assert(ret);
305
306
        /* not broadcast and not for us */
307
39.8k
        if (group == 0 && hdr->nlmsg_pid != nl->sockaddr.nl.nl_pid)
308
0
                goto finalize;
309
310
        /* silently drop noop messages */
311
39.8k
        if (hdr->nlmsg_type == NLMSG_NOOP)
312
0
                goto finalize;
313
314
        /* check that we support this message type */
315
39.8k
        r = netlink_get_policy_set_and_header_size(nl, hdr->nlmsg_type, hdr->nlmsg_flags, NULL, &size);
316
39.8k
        if (r == -EOPNOTSUPP) {
317
0
                log_debug("sd-netlink: ignored message with unknown type: %i", hdr->nlmsg_type);
318
0
                goto finalize;
319
0
        }
320
39.8k
        if (r < 0)
321
0
                return r;
322
323
        /* check that the size matches the message type */
324
39.8k
        if (hdr->nlmsg_len < NLMSG_LENGTH(size)) {
325
0
                log_debug("sd-netlink: message is shorter than expected, dropping.");
326
0
                goto finalize;
327
0
        }
328
329
39.8k
        r = message_new_empty(nl, &m);
330
39.8k
        if (r < 0)
331
0
                return r;
332
333
39.8k
        m->multicast_group = group;
334
39.8k
        m->hdr = memdup(hdr, hdr->nlmsg_len);
335
39.8k
        if (!m->hdr)
336
0
                return -ENOMEM;
337
338
        /* seal and parse the top-level message */
339
39.8k
        r = sd_netlink_message_rewind(m, nl);
340
39.8k
        if (r < 0)
341
0
                return r;
342
343
39.8k
        *ret = TAKE_PTR(m);
344
39.8k
        return 1;
345
346
0
finalize:
347
0
        *ret = NULL;
348
0
        return 0;
349
39.8k
}
350
351
/* On success, the number of bytes received is returned and *ret points to the received message
352
 * which has a valid header and the correct size.
353
 * If nothing useful was received 0 is returned.
354
 * On failure, a negative error code is returned.
355
 */
356
39.8k
int socket_read_message(sd_netlink *nl) {
357
39.8k
        bool done = false;
358
39.8k
        uint32_t group;
359
39.8k
        size_t len;
360
39.8k
        int r;
361
362
39.8k
        assert(nl);
363
364
        /* read nothing, just get the pending message size */
365
39.8k
        r = socket_recv_message(nl->fd, NULL, 0, NULL, true);
366
39.8k
        if (r <= 0)
367
0
                return r;
368
39.8k
        len = (size_t) r;
369
370
        /* make room for the pending message */
371
39.8k
        if (!greedy_realloc((void**) &nl->rbuffer, len, sizeof(uint8_t)))
372
0
                return -ENOMEM;
373
374
        /* read the pending message */
375
39.8k
        r = socket_recv_message(nl->fd, nl->rbuffer, MALLOC_SIZEOF_SAFE(nl->rbuffer), &group, false);
376
39.8k
        if (r <= 0)
377
0
                return r;
378
39.8k
        len = (size_t) r;
379
380
39.8k
        if (!NLMSG_OK(nl->rbuffer, len)) {
381
0
                log_debug("sd-netlink: received invalid message, discarding %zu bytes of incoming message", len);
382
0
                return 0;
383
0
        }
384
385
39.8k
        for (struct nlmsghdr *hdr = nl->rbuffer; NLMSG_OK(hdr, len); hdr = NLMSG_NEXT(hdr, len)) {
386
39.8k
                _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
387
388
39.8k
                r = parse_message_one(nl, group, hdr, &m);
389
39.8k
                if (r < 0)
390
0
                        return r;
391
39.8k
                if (r == 0)
392
0
                        continue;
393
394
39.8k
                if (hdr->nlmsg_flags & NLM_F_MULTI) {
395
0
                        if (hdr->nlmsg_type == NLMSG_DONE) {
396
0
                                _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *existing = NULL;
397
398
                                /* finished reading multi-part message */
399
0
                                existing = hashmap_remove(nl->rqueue_partial_by_serial, UINT32_TO_PTR(hdr->nlmsg_seq));
400
401
                                /* if we receive only NLMSG_DONE, put it into the receive queue. */
402
0
                                r = netlink_queue_received_message(nl, existing ?: m);
403
0
                                if (r < 0)
404
0
                                        return r;
405
406
0
                                done = true;
407
0
                        } else {
408
0
                                sd_netlink_message *existing;
409
410
0
                                existing = hashmap_get(nl->rqueue_partial_by_serial, UINT32_TO_PTR(hdr->nlmsg_seq));
411
0
                                if (existing) {
412
                                        /* This is the continuation of the previously read messages.
413
                                         * Let's append this message at the end. */
414
0
                                        while (existing->next)
415
0
                                                existing = existing->next;
416
0
                                        existing->next = TAKE_PTR(m);
417
0
                                } else {
418
                                        /* This is the first message. Put it into the queue for partially
419
                                         * received messages. */
420
0
                                        r = netlink_queue_partially_received_message(nl, m);
421
0
                                        if (r < 0)
422
0
                                                return r;
423
0
                                }
424
0
                        }
425
426
39.8k
                } else {
427
39.8k
                        r = netlink_queue_received_message(nl, m);
428
39.8k
                        if (r < 0)
429
0
                                return r;
430
431
39.8k
                        done = true;
432
39.8k
                }
433
39.8k
        }
434
435
39.8k
        if (len > 0)
436
39.8k
                log_debug("sd-netlink: discarding trailing %zu bytes of incoming message", len);
437
438
39.8k
        return done;
439
39.8k
}