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