]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd-network/sd-lldp.c
tree-wide: use UINT64_MAX or friends
[thirdparty/systemd.git] / src / libsystemd-network / sd-lldp.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <arpa/inet.h>
4 #include <linux/sockios.h>
5 #include <sys/ioctl.h>
6
7 #include "sd-lldp.h"
8
9 #include "alloc-util.h"
10 #include "ether-addr-util.h"
11 #include "event-util.h"
12 #include "fd-util.h"
13 #include "lldp-internal.h"
14 #include "lldp-neighbor.h"
15 #include "lldp-network.h"
16 #include "memory-util.h"
17 #include "socket-util.h"
18 #include "sort-util.h"
19 #include "string-table.h"
20
21 #define LLDP_DEFAULT_NEIGHBORS_MAX 128U
22
23 static const char * const lldp_event_table[_SD_LLDP_EVENT_MAX] = {
24 [SD_LLDP_EVENT_ADDED] = "added",
25 [SD_LLDP_EVENT_REMOVED] = "removed",
26 [SD_LLDP_EVENT_UPDATED] = "updated",
27 [SD_LLDP_EVENT_REFRESHED] = "refreshed",
28 };
29
30 DEFINE_STRING_TABLE_LOOKUP(lldp_event, sd_lldp_event_t);
31
32 static void lldp_flush_neighbors(sd_lldp *lldp) {
33 assert(lldp);
34
35 hashmap_clear(lldp->neighbor_by_id);
36 }
37
38 static void lldp_callback(sd_lldp *lldp, sd_lldp_event_t event, sd_lldp_neighbor *n) {
39 assert(lldp);
40 assert(event >= 0 && event < _SD_LLDP_EVENT_MAX);
41
42 if (!lldp->callback) {
43 log_lldp("Received '%s' event.", lldp_event_to_string(event));
44 return;
45 }
46
47 log_lldp("Invoking callback for '%s' event.", lldp_event_to_string(event));
48 lldp->callback(lldp, event, n, lldp->userdata);
49 }
50
51 static int lldp_make_space(sd_lldp *lldp, size_t extra) {
52 usec_t t = USEC_INFINITY;
53 bool changed = false;
54
55 assert(lldp);
56
57 /* Remove all entries that are past their TTL, and more until at least the specified number of extra entries
58 * are free. */
59
60 for (;;) {
61 _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
62
63 n = prioq_peek(lldp->neighbor_by_expiry);
64 if (!n)
65 break;
66
67 sd_lldp_neighbor_ref(n);
68
69 if (hashmap_size(lldp->neighbor_by_id) > LESS_BY(lldp->neighbors_max, extra))
70 goto remove_one;
71
72 if (t == USEC_INFINITY)
73 t = now(clock_boottime_or_monotonic());
74
75 if (n->until > t)
76 break;
77
78 remove_one:
79 lldp_neighbor_unlink(n);
80 lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, n);
81 changed = true;
82 }
83
84 return changed;
85 }
86
87 static bool lldp_keep_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) {
88 assert(lldp);
89 assert(n);
90
91 /* Don't keep data with a zero TTL */
92 if (n->ttl <= 0)
93 return false;
94
95 /* Filter out data from the filter address */
96 if (!ether_addr_is_null(&lldp->filter_address) &&
97 ether_addr_equal(&lldp->filter_address, &n->source_address))
98 return false;
99
100 /* Only add if the neighbor has a capability we are interested in. Note that we also store all neighbors with
101 * no caps field set. */
102 if (n->has_capabilities &&
103 (n->enabled_capabilities & lldp->capability_mask) == 0)
104 return false;
105
106 /* Keep everything else */
107 return true;
108 }
109
110 static int lldp_start_timer(sd_lldp *lldp, sd_lldp_neighbor *neighbor);
111
112 static int lldp_add_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) {
113 _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *old = NULL;
114 bool keep;
115 int r;
116
117 assert(lldp);
118 assert(n);
119 assert(!n->lldp);
120
121 keep = lldp_keep_neighbor(lldp, n);
122
123 /* First retrieve the old entry for this MSAP */
124 old = hashmap_get(lldp->neighbor_by_id, &n->id);
125 if (old) {
126 sd_lldp_neighbor_ref(old);
127
128 if (!keep) {
129 lldp_neighbor_unlink(old);
130 lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, old);
131 return 0;
132 }
133
134 if (lldp_neighbor_equal(n, old)) {
135 /* Is this equal, then restart the TTL counter, but don't do anything else. */
136 old->timestamp = n->timestamp;
137 lldp_start_timer(lldp, old);
138 lldp_callback(lldp, SD_LLDP_EVENT_REFRESHED, old);
139 return 0;
140 }
141
142 /* Data changed, remove the old entry, and add a new one */
143 lldp_neighbor_unlink(old);
144
145 } else if (!keep)
146 return 0;
147
148 /* Then, make room for at least one new neighbor */
149 lldp_make_space(lldp, 1);
150
151 r = hashmap_put(lldp->neighbor_by_id, &n->id, n);
152 if (r < 0)
153 goto finish;
154
155 r = prioq_put(lldp->neighbor_by_expiry, n, &n->prioq_idx);
156 if (r < 0) {
157 assert_se(hashmap_remove(lldp->neighbor_by_id, &n->id) == n);
158 goto finish;
159 }
160
161 n->lldp = lldp;
162
163 lldp_start_timer(lldp, n);
164 lldp_callback(lldp, old ? SD_LLDP_EVENT_UPDATED : SD_LLDP_EVENT_ADDED, n);
165
166 return 1;
167
168 finish:
169 if (old)
170 lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, old);
171
172 return r;
173 }
174
175 static int lldp_handle_datagram(sd_lldp *lldp, sd_lldp_neighbor *n) {
176 int r;
177
178 assert(lldp);
179 assert(n);
180
181 r = lldp_neighbor_parse(n);
182 if (r == -EBADMSG) /* Ignore bad messages */
183 return 0;
184 if (r < 0)
185 return r;
186
187 r = lldp_add_neighbor(lldp, n);
188 if (r < 0) {
189 log_lldp_errno(r, "Failed to add datagram. Ignoring.");
190 return 0;
191 }
192
193 log_lldp("Successfully processed LLDP datagram.");
194 return 0;
195 }
196
197 static int lldp_receive_datagram(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
198 _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
199 ssize_t space, length;
200 sd_lldp *lldp = userdata;
201 struct timespec ts;
202
203 assert(fd >= 0);
204 assert(lldp);
205
206 space = next_datagram_size_fd(fd);
207 if (space < 0)
208 return log_lldp_errno(space, "Failed to determine datagram size to read: %m");
209
210 n = lldp_neighbor_new(space);
211 if (!n)
212 return -ENOMEM;
213
214 length = recv(fd, LLDP_NEIGHBOR_RAW(n), n->raw_size, MSG_DONTWAIT);
215 if (length < 0) {
216 if (IN_SET(errno, EAGAIN, EINTR))
217 return 0;
218
219 return log_lldp_errno(errno, "Failed to read LLDP datagram: %m");
220 }
221
222 if ((size_t) length != n->raw_size) {
223 log_lldp("Packet size mismatch.");
224 return -EINVAL;
225 }
226
227 /* Try to get the timestamp of this packet if it is known */
228 if (ioctl(fd, SIOCGSTAMPNS, &ts) >= 0)
229 triple_timestamp_from_realtime(&n->timestamp, timespec_load(&ts));
230 else
231 triple_timestamp_get(&n->timestamp);
232
233 return lldp_handle_datagram(lldp, n);
234 }
235
236 static void lldp_reset(sd_lldp *lldp) {
237 assert(lldp);
238
239 (void) event_source_disable(lldp->timer_event_source);
240 lldp->io_event_source = sd_event_source_unref(lldp->io_event_source);
241 lldp->fd = safe_close(lldp->fd);
242 }
243
244 _public_ int sd_lldp_start(sd_lldp *lldp) {
245 int r;
246
247 assert_return(lldp, -EINVAL);
248 assert_return(lldp->event, -EINVAL);
249 assert_return(lldp->ifindex > 0, -EINVAL);
250
251 if (lldp->fd >= 0)
252 return 0;
253
254 assert(!lldp->io_event_source);
255
256 lldp->fd = lldp_network_bind_raw_socket(lldp->ifindex);
257 if (lldp->fd < 0)
258 return lldp->fd;
259
260 r = sd_event_add_io(lldp->event, &lldp->io_event_source, lldp->fd, EPOLLIN, lldp_receive_datagram, lldp);
261 if (r < 0)
262 goto fail;
263
264 r = sd_event_source_set_priority(lldp->io_event_source, lldp->event_priority);
265 if (r < 0)
266 goto fail;
267
268 (void) sd_event_source_set_description(lldp->io_event_source, "lldp-io");
269
270 log_lldp("Started LLDP client");
271 return 1;
272
273 fail:
274 lldp_reset(lldp);
275 return r;
276 }
277
278 _public_ int sd_lldp_stop(sd_lldp *lldp) {
279 if (!lldp)
280 return 0;
281
282 if (lldp->fd < 0)
283 return 0;
284
285 log_lldp("Stopping LLDP client");
286
287 lldp_reset(lldp);
288 lldp_flush_neighbors(lldp);
289
290 return 1;
291 }
292
293 _public_ int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int64_t priority) {
294 int r;
295
296 assert_return(lldp, -EINVAL);
297 assert_return(lldp->fd < 0, -EBUSY);
298 assert_return(!lldp->event, -EBUSY);
299
300 if (event)
301 lldp->event = sd_event_ref(event);
302 else {
303 r = sd_event_default(&lldp->event);
304 if (r < 0)
305 return r;
306 }
307
308 lldp->event_priority = priority;
309
310 return 0;
311 }
312
313 _public_ int sd_lldp_detach_event(sd_lldp *lldp) {
314
315 assert_return(lldp, -EINVAL);
316 assert_return(lldp->fd < 0, -EBUSY);
317
318 lldp->event = sd_event_unref(lldp->event);
319 return 0;
320 }
321
322 _public_ sd_event* sd_lldp_get_event(sd_lldp *lldp) {
323 assert_return(lldp, NULL);
324
325 return lldp->event;
326 }
327
328 _public_ int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *userdata) {
329 assert_return(lldp, -EINVAL);
330
331 lldp->callback = cb;
332 lldp->userdata = userdata;
333
334 return 0;
335 }
336
337 _public_ int sd_lldp_set_ifindex(sd_lldp *lldp, int ifindex) {
338 assert_return(lldp, -EINVAL);
339 assert_return(ifindex > 0, -EINVAL);
340 assert_return(lldp->fd < 0, -EBUSY);
341
342 lldp->ifindex = ifindex;
343 return 0;
344 }
345
346 static sd_lldp* lldp_free(sd_lldp *lldp) {
347 assert(lldp);
348
349 lldp->timer_event_source = sd_event_source_unref(lldp->timer_event_source);
350
351 lldp_reset(lldp);
352 sd_lldp_detach_event(lldp);
353 lldp_flush_neighbors(lldp);
354
355 hashmap_free(lldp->neighbor_by_id);
356 prioq_free(lldp->neighbor_by_expiry);
357 return mfree(lldp);
358 }
359
360 DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_lldp, sd_lldp, lldp_free);
361
362 _public_ int sd_lldp_new(sd_lldp **ret) {
363 _cleanup_(sd_lldp_unrefp) sd_lldp *lldp = NULL;
364 int r;
365
366 assert_return(ret, -EINVAL);
367
368 lldp = new(sd_lldp, 1);
369 if (!lldp)
370 return -ENOMEM;
371
372 *lldp = (sd_lldp) {
373 .n_ref = 1,
374 .fd = -1,
375 .neighbors_max = LLDP_DEFAULT_NEIGHBORS_MAX,
376 .capability_mask = UINT16_MAX,
377 };
378
379 lldp->neighbor_by_id = hashmap_new(&lldp_neighbor_hash_ops);
380 if (!lldp->neighbor_by_id)
381 return -ENOMEM;
382
383 r = prioq_ensure_allocated(&lldp->neighbor_by_expiry, lldp_neighbor_prioq_compare_func);
384 if (r < 0)
385 return r;
386
387 *ret = TAKE_PTR(lldp);
388
389 return 0;
390 }
391
392 static int neighbor_compare_func(sd_lldp_neighbor * const *a, sd_lldp_neighbor * const *b) {
393 return lldp_neighbor_id_compare_func(&(*a)->id, &(*b)->id);
394 }
395
396 static int on_timer_event(sd_event_source *s, uint64_t usec, void *userdata) {
397 sd_lldp *lldp = userdata;
398 int r;
399
400 r = lldp_make_space(lldp, 0);
401 if (r < 0)
402 return log_lldp_errno(r, "Failed to make space: %m");
403
404 r = lldp_start_timer(lldp, NULL);
405 if (r < 0)
406 return log_lldp_errno(r, "Failed to restart timer: %m");
407
408 return 0;
409 }
410
411 static int lldp_start_timer(sd_lldp *lldp, sd_lldp_neighbor *neighbor) {
412 sd_lldp_neighbor *n;
413
414 assert(lldp);
415
416 if (neighbor)
417 lldp_neighbor_start_ttl(neighbor);
418
419 n = prioq_peek(lldp->neighbor_by_expiry);
420 if (!n)
421 return event_source_disable(lldp->timer_event_source);
422
423 if (!lldp->event)
424 return 0;
425
426 return event_reset_time(lldp->event, &lldp->timer_event_source,
427 clock_boottime_or_monotonic(),
428 n->until, 0,
429 on_timer_event, lldp,
430 lldp->event_priority, "lldp-timer", true);
431 }
432
433 _public_ int sd_lldp_get_neighbors(sd_lldp *lldp, sd_lldp_neighbor ***ret) {
434 sd_lldp_neighbor **l = NULL, *n;
435 int k = 0, r;
436
437 assert_return(lldp, -EINVAL);
438 assert_return(ret, -EINVAL);
439
440 if (hashmap_isempty(lldp->neighbor_by_id)) { /* Special shortcut */
441 *ret = NULL;
442 return 0;
443 }
444
445 l = new0(sd_lldp_neighbor*, hashmap_size(lldp->neighbor_by_id));
446 if (!l)
447 return -ENOMEM;
448
449 r = lldp_start_timer(lldp, NULL);
450 if (r < 0) {
451 free(l);
452 return r;
453 }
454
455 HASHMAP_FOREACH(n, lldp->neighbor_by_id)
456 l[k++] = sd_lldp_neighbor_ref(n);
457
458 assert((size_t) k == hashmap_size(lldp->neighbor_by_id));
459
460 /* Return things in a stable order */
461 typesafe_qsort(l, k, neighbor_compare_func);
462 *ret = l;
463
464 return k;
465 }
466
467 _public_ int sd_lldp_set_neighbors_max(sd_lldp *lldp, uint64_t m) {
468 assert_return(lldp, -EINVAL);
469 assert_return(m > 0, -EINVAL);
470
471 lldp->neighbors_max = m;
472 lldp_make_space(lldp, 0);
473
474 return 0;
475 }
476
477 _public_ int sd_lldp_match_capabilities(sd_lldp *lldp, uint16_t mask) {
478 assert_return(lldp, -EINVAL);
479 assert_return(mask != 0, -EINVAL);
480
481 lldp->capability_mask = mask;
482
483 return 0;
484 }
485
486 _public_ int sd_lldp_set_filter_address(sd_lldp *lldp, const struct ether_addr *addr) {
487 assert_return(lldp, -EINVAL);
488
489 /* In order to deal nicely with bridges that send back our own packets, allow one address to be filtered, so
490 * that our own can be filtered out here. */
491
492 if (addr)
493 lldp->filter_address = *addr;
494 else
495 zero(lldp->filter_address);
496
497 return 0;
498 }