1 /* SPDX-License-Identifier: LGPL-2.1+ */
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
);
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 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
) {
279 assert_return(lldp
, -EINVAL
);
284 log_lldp("Stopping LLDP client");
287 lldp_flush_neighbors(lldp
);
292 _public_
int sd_lldp_attach_event(sd_lldp
*lldp
, sd_event
*event
, int64_t priority
) {
295 assert_return(lldp
, -EINVAL
);
296 assert_return(lldp
->fd
< 0, -EBUSY
);
297 assert_return(!lldp
->event
, -EBUSY
);
300 lldp
->event
= sd_event_ref(event
);
302 r
= sd_event_default(&lldp
->event
);
307 lldp
->event_priority
= priority
;
312 _public_
int sd_lldp_detach_event(sd_lldp
*lldp
) {
314 assert_return(lldp
, -EINVAL
);
315 assert_return(lldp
->fd
< 0, -EBUSY
);
317 lldp
->event
= sd_event_unref(lldp
->event
);
321 _public_ sd_event
* sd_lldp_get_event(sd_lldp
*lldp
) {
322 assert_return(lldp
, NULL
);
327 _public_
int sd_lldp_set_callback(sd_lldp
*lldp
, sd_lldp_callback_t cb
, void *userdata
) {
328 assert_return(lldp
, -EINVAL
);
331 lldp
->userdata
= userdata
;
336 _public_
int sd_lldp_set_ifindex(sd_lldp
*lldp
, int ifindex
) {
337 assert_return(lldp
, -EINVAL
);
338 assert_return(ifindex
> 0, -EINVAL
);
339 assert_return(lldp
->fd
< 0, -EBUSY
);
341 lldp
->ifindex
= ifindex
;
345 static sd_lldp
* lldp_free(sd_lldp
*lldp
) {
348 lldp
->timer_event_source
= sd_event_source_unref(lldp
->timer_event_source
);
351 sd_lldp_detach_event(lldp
);
352 lldp_flush_neighbors(lldp
);
354 hashmap_free(lldp
->neighbor_by_id
);
355 prioq_free(lldp
->neighbor_by_expiry
);
359 DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_lldp
, sd_lldp
, lldp_free
);
361 _public_
int sd_lldp_new(sd_lldp
**ret
) {
362 _cleanup_(sd_lldp_unrefp
) sd_lldp
*lldp
= NULL
;
365 assert_return(ret
, -EINVAL
);
367 lldp
= new(sd_lldp
, 1);
374 .neighbors_max
= LLDP_DEFAULT_NEIGHBORS_MAX
,
375 .capability_mask
= (uint16_t) -1,
378 lldp
->neighbor_by_id
= hashmap_new(&lldp_neighbor_hash_ops
);
379 if (!lldp
->neighbor_by_id
)
382 r
= prioq_ensure_allocated(&lldp
->neighbor_by_expiry
, lldp_neighbor_prioq_compare_func
);
386 *ret
= TAKE_PTR(lldp
);
391 static int neighbor_compare_func(sd_lldp_neighbor
* const *a
, sd_lldp_neighbor
* const *b
) {
392 return lldp_neighbor_id_compare_func(&(*a
)->id
, &(*b
)->id
);
395 static int on_timer_event(sd_event_source
*s
, uint64_t usec
, void *userdata
) {
396 sd_lldp
*lldp
= userdata
;
399 r
= lldp_make_space(lldp
, 0);
401 return log_lldp_errno(r
, "Failed to make space: %m");
403 r
= lldp_start_timer(lldp
, NULL
);
405 return log_lldp_errno(r
, "Failed to restart timer: %m");
410 static int lldp_start_timer(sd_lldp
*lldp
, sd_lldp_neighbor
*neighbor
) {
416 lldp_neighbor_start_ttl(neighbor
);
418 n
= prioq_peek(lldp
->neighbor_by_expiry
);
420 return event_source_disable(lldp
->timer_event_source
);
425 return event_reset_time(lldp
->event
, &lldp
->timer_event_source
,
426 clock_boottime_or_monotonic(),
428 on_timer_event
, lldp
,
429 lldp
->event_priority
, "lldp-timer", true);
432 _public_
int sd_lldp_get_neighbors(sd_lldp
*lldp
, sd_lldp_neighbor
***ret
) {
433 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
, i
)
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
);