/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 | } |