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