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