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