1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
4 #include <linux/sockios.h>
9 #include "alloc-util.h"
10 #include "ether-addr-util.h"
11 #include "event-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"
21 #define LLDP_DEFAULT_NEIGHBORS_MAX 128U
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",
30 DEFINE_STRING_TABLE_LOOKUP(lldp_event
, sd_lldp_event_t
);
32 static void lldp_flush_neighbors(sd_lldp
*lldp
) {
35 hashmap_clear(lldp
->neighbor_by_id
);
38 static void lldp_callback(sd_lldp
*lldp
, sd_lldp_event_t event
, sd_lldp_neighbor
*n
) {
40 assert(event
>= 0 && event
< _SD_LLDP_EVENT_MAX
);
42 if (!lldp
->callback
) {
43 log_lldp("Received '%s' event.", lldp_event_to_string(event
));
47 log_lldp("Invoking callback for '%s' event.", lldp_event_to_string(event
));
48 lldp
->callback(lldp
, event
, n
, lldp
->userdata
);
51 static int lldp_make_space(sd_lldp
*lldp
, size_t extra
) {
52 usec_t t
= USEC_INFINITY
;
57 /* Remove all entries that are past their TTL, and more until at least the specified number of extra entries
61 _cleanup_(sd_lldp_neighbor_unrefp
) sd_lldp_neighbor
*n
= NULL
;
63 n
= prioq_peek(lldp
->neighbor_by_expiry
);
67 sd_lldp_neighbor_ref(n
);
69 if (hashmap_size(lldp
->neighbor_by_id
) > LESS_BY(lldp
->neighbors_max
, extra
))
72 if (t
== USEC_INFINITY
)
73 t
= now(clock_boottime_or_monotonic());
79 lldp_neighbor_unlink(n
);
80 lldp_callback(lldp
, SD_LLDP_EVENT_REMOVED
, n
);
87 static bool lldp_keep_neighbor(sd_lldp
*lldp
, sd_lldp_neighbor
*n
) {
91 /* Don't keep data with a zero TTL */
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
))
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)
106 /* Keep everything else */
110 static int lldp_start_timer(sd_lldp
*lldp
, sd_lldp_neighbor
*neighbor
);
112 static int lldp_add_neighbor(sd_lldp
*lldp
, sd_lldp_neighbor
*n
) {
113 _cleanup_(sd_lldp_neighbor_unrefp
) sd_lldp_neighbor
*old
= NULL
;
121 keep
= lldp_keep_neighbor(lldp
, n
);
123 /* First retrieve the old entry for this MSAP */
124 old
= hashmap_get(lldp
->neighbor_by_id
, &n
->id
);
126 sd_lldp_neighbor_ref(old
);
129 lldp_neighbor_unlink(old
);
130 lldp_callback(lldp
, SD_LLDP_EVENT_REMOVED
, old
);
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
);
142 /* Data changed, remove the old entry, and add a new one */
143 lldp_neighbor_unlink(old
);
148 /* Then, make room for at least one new neighbor */
149 lldp_make_space(lldp
, 1);
151 r
= hashmap_put(lldp
->neighbor_by_id
, &n
->id
, n
);
155 r
= prioq_put(lldp
->neighbor_by_expiry
, n
, &n
->prioq_idx
);
157 assert_se(hashmap_remove(lldp
->neighbor_by_id
, &n
->id
) == n
);
163 lldp_start_timer(lldp
, n
);
164 lldp_callback(lldp
, old
? SD_LLDP_EVENT_UPDATED
: SD_LLDP_EVENT_ADDED
, n
);
170 lldp_callback(lldp
, SD_LLDP_EVENT_REMOVED
, old
);
175 static int lldp_handle_datagram(sd_lldp
*lldp
, sd_lldp_neighbor
*n
) {
181 r
= lldp_neighbor_parse(n
);
182 if (r
== -EBADMSG
) /* Ignore bad messages */
187 r
= lldp_add_neighbor(lldp
, n
);
189 log_lldp_errno(r
, "Failed to add datagram. Ignoring.");
193 log_lldp("Successfully processed LLDP datagram.");
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
;
206 space
= next_datagram_size_fd(fd
);
208 return log_lldp_errno(space
, "Failed to determine datagram size to read: %m");
210 n
= lldp_neighbor_new(space
);
214 length
= recv(fd
, LLDP_NEIGHBOR_RAW(n
), n
->raw_size
, MSG_DONTWAIT
);
216 if (IN_SET(errno
, EAGAIN
, EINTR
))
219 return log_lldp_errno(errno
, "Failed to read LLDP datagram: %m");
222 if ((size_t) length
!= n
->raw_size
) {
223 log_lldp("Packet size mismatch.");
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
));
231 triple_timestamp_get(&n
->timestamp
);
233 return lldp_handle_datagram(lldp
, n
);
236 static void lldp_reset(sd_lldp
*lldp
) {
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
);
244 _public_
int sd_lldp_start(sd_lldp
*lldp
) {
247 assert_return(lldp
, -EINVAL
);
248 assert_return(lldp
->event
, -EINVAL
);
249 assert_return(lldp
->ifindex
> 0, -EINVAL
);
254 assert(!lldp
->io_event_source
);
256 lldp
->fd
= lldp_network_bind_raw_socket(lldp
->ifindex
);
260 r
= sd_event_add_io(lldp
->event
, &lldp
->io_event_source
, lldp
->fd
, EPOLLIN
, lldp_receive_datagram
, lldp
);
264 r
= sd_event_source_set_priority(lldp
->io_event_source
, lldp
->event_priority
);
268 (void) sd_event_source_set_description(lldp
->io_event_source
, "lldp-io");
270 log_lldp("Started LLDP client");
278 _public_
int sd_lldp_stop(sd_lldp
*lldp
) {
285 log_lldp("Stopping LLDP client");
288 lldp_flush_neighbors(lldp
);
293 _public_
int sd_lldp_attach_event(sd_lldp
*lldp
, sd_event
*event
, int64_t priority
) {
296 assert_return(lldp
, -EINVAL
);
297 assert_return(lldp
->fd
< 0, -EBUSY
);
298 assert_return(!lldp
->event
, -EBUSY
);
301 lldp
->event
= sd_event_ref(event
);
303 r
= sd_event_default(&lldp
->event
);
308 lldp
->event_priority
= priority
;
313 _public_
int sd_lldp_detach_event(sd_lldp
*lldp
) {
315 assert_return(lldp
, -EINVAL
);
316 assert_return(lldp
->fd
< 0, -EBUSY
);
318 lldp
->event
= sd_event_unref(lldp
->event
);
322 _public_ sd_event
* sd_lldp_get_event(sd_lldp
*lldp
) {
323 assert_return(lldp
, NULL
);
328 _public_
int sd_lldp_set_callback(sd_lldp
*lldp
, sd_lldp_callback_t cb
, void *userdata
) {
329 assert_return(lldp
, -EINVAL
);
332 lldp
->userdata
= userdata
;
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
);
342 lldp
->ifindex
= ifindex
;
346 static sd_lldp
* lldp_free(sd_lldp
*lldp
) {
349 lldp
->timer_event_source
= sd_event_source_unref(lldp
->timer_event_source
);
352 sd_lldp_detach_event(lldp
);
353 lldp_flush_neighbors(lldp
);
355 hashmap_free(lldp
->neighbor_by_id
);
356 prioq_free(lldp
->neighbor_by_expiry
);
360 DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_lldp
, sd_lldp
, lldp_free
);
362 _public_
int sd_lldp_new(sd_lldp
**ret
) {
363 _cleanup_(sd_lldp_unrefp
) sd_lldp
*lldp
= NULL
;
366 assert_return(ret
, -EINVAL
);
368 lldp
= new(sd_lldp
, 1);
375 .neighbors_max
= LLDP_DEFAULT_NEIGHBORS_MAX
,
376 .capability_mask
= (uint16_t) -1,
379 lldp
->neighbor_by_id
= hashmap_new(&lldp_neighbor_hash_ops
);
380 if (!lldp
->neighbor_by_id
)
383 r
= prioq_ensure_allocated(&lldp
->neighbor_by_expiry
, lldp_neighbor_prioq_compare_func
);
387 *ret
= TAKE_PTR(lldp
);
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
);
396 static int on_timer_event(sd_event_source
*s
, uint64_t usec
, void *userdata
) {
397 sd_lldp
*lldp
= userdata
;
400 r
= lldp_make_space(lldp
, 0);
402 return log_lldp_errno(r
, "Failed to make space: %m");
404 r
= lldp_start_timer(lldp
, NULL
);
406 return log_lldp_errno(r
, "Failed to restart timer: %m");
411 static int lldp_start_timer(sd_lldp
*lldp
, sd_lldp_neighbor
*neighbor
) {
417 lldp_neighbor_start_ttl(neighbor
);
419 n
= prioq_peek(lldp
->neighbor_by_expiry
);
421 return event_source_disable(lldp
->timer_event_source
);
426 return event_reset_time(lldp
->event
, &lldp
->timer_event_source
,
427 clock_boottime_or_monotonic(),
429 on_timer_event
, lldp
,
430 lldp
->event_priority
, "lldp-timer", true);
433 _public_
int sd_lldp_get_neighbors(sd_lldp
*lldp
, sd_lldp_neighbor
***ret
) {
434 sd_lldp_neighbor
**l
= NULL
, *n
;
437 assert_return(lldp
, -EINVAL
);
438 assert_return(ret
, -EINVAL
);
440 if (hashmap_isempty(lldp
->neighbor_by_id
)) { /* Special shortcut */
445 l
= new0(sd_lldp_neighbor
*, hashmap_size(lldp
->neighbor_by_id
));
449 r
= lldp_start_timer(lldp
, NULL
);
455 HASHMAP_FOREACH(n
, lldp
->neighbor_by_id
)
456 l
[k
++] = sd_lldp_neighbor_ref(n
);
458 assert((size_t) k
== hashmap_size(lldp
->neighbor_by_id
));
460 /* Return things in a stable order */
461 typesafe_qsort(l
, k
, neighbor_compare_func
);
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
);
471 lldp
->neighbors_max
= m
;
472 lldp_make_space(lldp
, 0);
477 _public_
int sd_lldp_match_capabilities(sd_lldp
*lldp
, uint16_t mask
) {
478 assert_return(lldp
, -EINVAL
);
479 assert_return(mask
!= 0, -EINVAL
);
481 lldp
->capability_mask
= mask
;
486 _public_
int sd_lldp_set_filter_address(sd_lldp
*lldp
, const struct ether_addr
*addr
) {
487 assert_return(lldp
, -EINVAL
);
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. */
493 lldp
->filter_address
= *addr
;
495 zero(lldp
->filter_address
);