1 /* SPDX-License-Identifier: LGPL-2.1+ */
4 #include <linux/sockios.h>
8 #include "alloc-util.h"
10 #include "lldp-internal.h"
11 #include "lldp-neighbor.h"
12 #include "lldp-network.h"
13 #include "socket-util.h"
14 #include "ether-addr-util.h"
16 #define LLDP_DEFAULT_NEIGHBORS_MAX 128U
18 static void lldp_flush_neighbors(sd_lldp
*lldp
) {
23 while ((n
= hashmap_first(lldp
->neighbor_by_id
)))
24 lldp_neighbor_unlink(n
);
27 static void lldp_callback(sd_lldp
*lldp
, sd_lldp_event event
, sd_lldp_neighbor
*n
) {
30 log_lldp("Invoking callback for '%c'.", event
);
35 lldp
->callback(lldp
, event
, n
, lldp
->userdata
);
38 static int lldp_make_space(sd_lldp
*lldp
, size_t extra
) {
39 usec_t t
= USEC_INFINITY
;
44 /* Remove all entries that are past their TTL, and more until at least the specified number of extra entries
48 _cleanup_(sd_lldp_neighbor_unrefp
) sd_lldp_neighbor
*n
= NULL
;
50 n
= prioq_peek(lldp
->neighbor_by_expiry
);
54 sd_lldp_neighbor_ref(n
);
56 if (hashmap_size(lldp
->neighbor_by_id
) > LESS_BY(lldp
->neighbors_max
, extra
))
59 if (t
== USEC_INFINITY
)
60 t
= now(clock_boottime_or_monotonic());
66 lldp_neighbor_unlink(n
);
67 lldp_callback(lldp
, SD_LLDP_EVENT_REMOVED
, n
);
74 static bool lldp_keep_neighbor(sd_lldp
*lldp
, sd_lldp_neighbor
*n
) {
78 /* Don't keep data with a zero TTL */
82 /* Filter out data from the filter address */
83 if (!ether_addr_is_null(&lldp
->filter_address
) &&
84 ether_addr_equal(&lldp
->filter_address
, &n
->source_address
))
87 /* Only add if the neighbor has a capability we are interested in. Note that we also store all neighbors with
88 * no caps field set. */
89 if (n
->has_capabilities
&&
90 (n
->enabled_capabilities
& lldp
->capability_mask
) == 0)
93 /* Keep everything else */
97 static int lldp_start_timer(sd_lldp
*lldp
, sd_lldp_neighbor
*neighbor
);
99 static int lldp_add_neighbor(sd_lldp
*lldp
, sd_lldp_neighbor
*n
) {
100 _cleanup_(sd_lldp_neighbor_unrefp
) sd_lldp_neighbor
*old
= NULL
;
108 keep
= lldp_keep_neighbor(lldp
, n
);
110 /* First retrieve the old entry for this MSAP */
111 old
= hashmap_get(lldp
->neighbor_by_id
, &n
->id
);
113 sd_lldp_neighbor_ref(old
);
116 lldp_neighbor_unlink(old
);
117 lldp_callback(lldp
, SD_LLDP_EVENT_REMOVED
, old
);
121 if (lldp_neighbor_equal(n
, old
)) {
122 /* Is this equal, then restart the TTL counter, but don't do anyting else. */
123 old
->timestamp
= n
->timestamp
;
124 lldp_start_timer(lldp
, old
);
125 lldp_callback(lldp
, SD_LLDP_EVENT_REFRESHED
, old
);
129 /* Data changed, remove the old entry, and add a new one */
130 lldp_neighbor_unlink(old
);
135 /* Then, make room for at least one new neighbor */
136 lldp_make_space(lldp
, 1);
138 r
= hashmap_put(lldp
->neighbor_by_id
, &n
->id
, n
);
142 r
= prioq_put(lldp
->neighbor_by_expiry
, n
, &n
->prioq_idx
);
144 assert_se(hashmap_remove(lldp
->neighbor_by_id
, &n
->id
) == n
);
150 lldp_start_timer(lldp
, n
);
151 lldp_callback(lldp
, old
? SD_LLDP_EVENT_UPDATED
: SD_LLDP_EVENT_ADDED
, n
);
157 lldp_callback(lldp
, SD_LLDP_EVENT_REMOVED
, old
);
162 static int lldp_handle_datagram(sd_lldp
*lldp
, sd_lldp_neighbor
*n
) {
168 r
= lldp_neighbor_parse(n
);
169 if (r
== -EBADMSG
) /* Ignore bad messages */
174 r
= lldp_add_neighbor(lldp
, n
);
176 log_lldp_errno(r
, "Failed to add datagram. Ignoring.");
180 log_lldp("Successfully processed LLDP datagram.");
184 static int lldp_receive_datagram(sd_event_source
*s
, int fd
, uint32_t revents
, void *userdata
) {
185 _cleanup_(sd_lldp_neighbor_unrefp
) sd_lldp_neighbor
*n
= NULL
;
186 ssize_t space
, length
;
187 sd_lldp
*lldp
= userdata
;
193 space
= next_datagram_size_fd(fd
);
195 return log_lldp_errno(space
, "Failed to determine datagram size to read: %m");
197 n
= lldp_neighbor_new(space
);
201 length
= recv(fd
, LLDP_NEIGHBOR_RAW(n
), n
->raw_size
, MSG_DONTWAIT
);
203 if (IN_SET(errno
, EAGAIN
, EINTR
))
206 return log_lldp_errno(errno
, "Failed to read LLDP datagram: %m");
209 if ((size_t) length
!= n
->raw_size
) {
210 log_lldp("Packet size mismatch.");
214 /* Try to get the timestamp of this packet if it is known */
215 if (ioctl(fd
, SIOCGSTAMPNS
, &ts
) >= 0)
216 triple_timestamp_from_realtime(&n
->timestamp
, timespec_load(&ts
));
218 triple_timestamp_get(&n
->timestamp
);
220 return lldp_handle_datagram(lldp
, n
);
223 static void lldp_reset(sd_lldp
*lldp
) {
226 lldp
->timer_event_source
= sd_event_source_unref(lldp
->timer_event_source
);
227 lldp
->io_event_source
= sd_event_source_unref(lldp
->io_event_source
);
228 lldp
->fd
= safe_close(lldp
->fd
);
231 _public_
int sd_lldp_start(sd_lldp
*lldp
) {
234 assert_return(lldp
, -EINVAL
);
235 assert_return(lldp
->event
, -EINVAL
);
236 assert_return(lldp
->ifindex
> 0, -EINVAL
);
241 assert(!lldp
->io_event_source
);
243 lldp
->fd
= lldp_network_bind_raw_socket(lldp
->ifindex
);
247 r
= sd_event_add_io(lldp
->event
, &lldp
->io_event_source
, lldp
->fd
, EPOLLIN
, lldp_receive_datagram
, lldp
);
251 r
= sd_event_source_set_priority(lldp
->io_event_source
, lldp
->event_priority
);
255 (void) sd_event_source_set_description(lldp
->io_event_source
, "lldp-io");
257 log_lldp("Started LLDP client");
265 _public_
int sd_lldp_stop(sd_lldp
*lldp
) {
266 assert_return(lldp
, -EINVAL
);
271 log_lldp("Stopping LLDP client");
274 lldp_flush_neighbors(lldp
);
279 _public_
int sd_lldp_attach_event(sd_lldp
*lldp
, sd_event
*event
, int64_t priority
) {
282 assert_return(lldp
, -EINVAL
);
283 assert_return(lldp
->fd
< 0, -EBUSY
);
284 assert_return(!lldp
->event
, -EBUSY
);
287 lldp
->event
= sd_event_ref(event
);
289 r
= sd_event_default(&lldp
->event
);
294 lldp
->event_priority
= priority
;
299 _public_
int sd_lldp_detach_event(sd_lldp
*lldp
) {
301 assert_return(lldp
, -EINVAL
);
302 assert_return(lldp
->fd
< 0, -EBUSY
);
304 lldp
->event
= sd_event_unref(lldp
->event
);
308 _public_ sd_event
* sd_lldp_get_event(sd_lldp
*lldp
) {
309 assert_return(lldp
, NULL
);
314 _public_
int sd_lldp_set_callback(sd_lldp
*lldp
, sd_lldp_callback_t cb
, void *userdata
) {
315 assert_return(lldp
, -EINVAL
);
318 lldp
->userdata
= userdata
;
323 _public_
int sd_lldp_set_ifindex(sd_lldp
*lldp
, int ifindex
) {
324 assert_return(lldp
, -EINVAL
);
325 assert_return(ifindex
> 0, -EINVAL
);
326 assert_return(lldp
->fd
< 0, -EBUSY
);
328 lldp
->ifindex
= ifindex
;
332 static sd_lldp
* lldp_free(sd_lldp
*lldp
) {
336 sd_lldp_detach_event(lldp
);
337 lldp_flush_neighbors(lldp
);
339 hashmap_free(lldp
->neighbor_by_id
);
340 prioq_free(lldp
->neighbor_by_expiry
);
344 DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_lldp
, sd_lldp
, lldp_free
);
346 _public_
int sd_lldp_new(sd_lldp
**ret
) {
347 _cleanup_(sd_lldp_unrefp
) sd_lldp
*lldp
= NULL
;
350 assert_return(ret
, -EINVAL
);
352 lldp
= new0(sd_lldp
, 1);
358 lldp
->neighbors_max
= LLDP_DEFAULT_NEIGHBORS_MAX
;
359 lldp
->capability_mask
= (uint16_t) -1;
361 lldp
->neighbor_by_id
= hashmap_new(&lldp_neighbor_id_hash_ops
);
362 if (!lldp
->neighbor_by_id
)
365 r
= prioq_ensure_allocated(&lldp
->neighbor_by_expiry
, lldp_neighbor_prioq_compare_func
);
369 *ret
= TAKE_PTR(lldp
);
374 static int neighbor_compare_func(sd_lldp_neighbor
* const *a
, sd_lldp_neighbor
* const *b
) {
375 return lldp_neighbor_id_hash_ops
.compare(&(*a
)->id
, &(*b
)->id
);
378 static int on_timer_event(sd_event_source
*s
, uint64_t usec
, void *userdata
) {
379 sd_lldp
*lldp
= userdata
;
382 r
= lldp_make_space(lldp
, 0);
384 return log_lldp_errno(r
, "Failed to make space: %m");
386 r
= lldp_start_timer(lldp
, NULL
);
388 return log_lldp_errno(r
, "Failed to restart timer: %m");
393 static int lldp_start_timer(sd_lldp
*lldp
, sd_lldp_neighbor
*neighbor
) {
400 lldp_neighbor_start_ttl(neighbor
);
402 n
= prioq_peek(lldp
->neighbor_by_expiry
);
405 if (lldp
->timer_event_source
)
406 return sd_event_source_set_enabled(lldp
->timer_event_source
, SD_EVENT_OFF
);
411 if (lldp
->timer_event_source
) {
412 r
= sd_event_source_set_time(lldp
->timer_event_source
, n
->until
);
416 return sd_event_source_set_enabled(lldp
->timer_event_source
, SD_EVENT_ONESHOT
);
422 r
= sd_event_add_time(lldp
->event
, &lldp
->timer_event_source
, clock_boottime_or_monotonic(), n
->until
, 0, on_timer_event
, lldp
);
426 r
= sd_event_source_set_priority(lldp
->timer_event_source
, lldp
->event_priority
);
430 (void) sd_event_source_set_description(lldp
->timer_event_source
, "lldp-timer");
434 _public_
int sd_lldp_get_neighbors(sd_lldp
*lldp
, sd_lldp_neighbor
***ret
) {
435 sd_lldp_neighbor
**l
= NULL
, *n
;
439 assert_return(lldp
, -EINVAL
);
440 assert_return(ret
, -EINVAL
);
442 if (hashmap_isempty(lldp
->neighbor_by_id
)) { /* Special shortcut */
447 l
= new0(sd_lldp_neighbor
*, hashmap_size(lldp
->neighbor_by_id
));
451 r
= lldp_start_timer(lldp
, NULL
);
457 HASHMAP_FOREACH(n
, lldp
->neighbor_by_id
, i
)
458 l
[k
++] = sd_lldp_neighbor_ref(n
);
460 assert((size_t) k
== hashmap_size(lldp
->neighbor_by_id
));
462 /* Return things in a stable order */
463 typesafe_qsort(l
, k
, neighbor_compare_func
);
469 _public_
int sd_lldp_set_neighbors_max(sd_lldp
*lldp
, uint64_t m
) {
470 assert_return(lldp
, -EINVAL
);
471 assert_return(m
<= 0, -EINVAL
);
473 lldp
->neighbors_max
= m
;
474 lldp_make_space(lldp
, 0);
479 _public_
int sd_lldp_match_capabilities(sd_lldp
*lldp
, uint16_t mask
) {
480 assert_return(lldp
, -EINVAL
);
481 assert_return(mask
!= 0, -EINVAL
);
483 lldp
->capability_mask
= mask
;
488 _public_
int sd_lldp_set_filter_address(sd_lldp
*lldp
, const struct ether_addr
*addr
) {
489 assert_return(lldp
, -EINVAL
);
491 /* In order to deal nicely with bridges that send back our own packets, allow one address to be filtered, so
492 * that our own can be filtered out here. */
495 lldp
->filter_address
= *addr
;
497 zero(lldp
->filter_address
);