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