]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/libsystemd-network/sd-lldp.c
lldp: move TLV related functions to lldp-tlv.c
[thirdparty/systemd.git] / src / libsystemd-network / sd-lldp.c
CommitLineData
ad1ad5c8
SS
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright (C) 2014 Tom Gundersen
7 Copyright (C) 2014 Susant Sahani
8
9 systemd is free software; you can redistribute it and/or modify it
10 under the terms of the GNU Lesser General Public License as published by
11 the Free Software Foundation; either version 2.1 of the License, or
12 (at your option) any later version.
13
14 systemd is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
18
19 You should have received a copy of the GNU Lesser General Public License
20 along with systemd; If not, see <http://www.gnu.org/licenses/>.
21***/
22
23#include <arpa/inet.h>
24
25#include "siphash24.h"
26#include "hashmap.h"
ad1ad5c8
SS
27
28#include "lldp-tlv.h"
29#include "lldp-port.h"
30#include "sd-lldp.h"
31#include "prioq.h"
32#include "lldp-internal.h"
7a6f1457 33#include "lldp-util.h"
49699bac
SS
34
35typedef enum LLDPAgentRXState {
36 LLDP_AGENT_RX_WAIT_PORT_OPERATIONAL = 4,
37 LLDP_AGENT_RX_DELETE_AGED_INFO,
38 LLDP_AGENT_RX_LLDP_INITIALIZE,
39 LLDP_AGENT_RX_WAIT_FOR_FRAME,
40 LLDP_AGENT_RX_RX_FRAME,
41 LLDP_AGENT_RX_DELETE_INFO,
42 LLDP_AGENT_RX_UPDATE_INFO,
43 _LLDP_AGENT_RX_STATE_MAX,
44 _LLDP_AGENT_RX_INVALID = -1,
45} LLDPAgentRXState;
ad1ad5c8
SS
46
47/* Section 10.5.2.2 Reception counters */
6b3fd9a1 48struct lldp_agent_statistics {
ad1ad5c8
SS
49 uint64_t stats_ageouts_total;
50 uint64_t stats_frames_discarded_total;
51 uint64_t stats_frames_in_errors_total;
52 uint64_t stats_frames_in_total;
53 uint64_t stats_tlvs_discarded_total;
54 uint64_t stats_tlvs_unrecognized_total;
55};
56
57struct sd_lldp {
58 lldp_port *port;
59
60 Prioq *by_expiry;
61 Hashmap *neighbour_mib;
62
49699bac
SS
63 sd_lldp_cb_t cb;
64
65 void *userdata;
66
67 LLDPAgentRXState rx_state;
6b3fd9a1 68 lldp_agent_statistics statistics;
ad1ad5c8
SS
69};
70
71static unsigned long chassis_id_hash_func(const void *p,
72 const uint8_t hash_key[HASH_KEY_SIZE]) {
73 uint64_t u;
74 const lldp_chassis_id *id = p;
75
76 assert(id);
77
78 siphash24((uint8_t *) &u, id->data, id->length, hash_key);
79
80 return (unsigned long) u;
81}
82
83static int chassis_id_compare_func(const void *_a, const void *_b) {
84 const lldp_chassis_id *a, *b;
85
86 a = _a;
87 b = _b;
88
89 assert(!a->length || a->data);
90 assert(!b->length || b->data);
91
92 if (a->type != b->type)
93 return -1;
94
95 if (a->length != b->length)
96 return a->length < b->length ? -1 : 1;
97
98 return memcmp(a->data, b->data, a->length);
99}
100
101static const struct hash_ops chassis_id_hash_ops = {
102 .hash = chassis_id_hash_func,
103 .compare = chassis_id_compare_func
104};
105
106static void lldp_mib_delete_objects(sd_lldp *lldp);
49699bac
SS
107static void lldp_set_state(sd_lldp *lldp, LLDPAgentRXState state);
108static void lldp_run_state_machine(sd_lldp *ll);
ad1ad5c8
SS
109
110static int lldp_receive_frame(sd_lldp *lldp, tlv_packet *tlv) {
111 int r;
112
113 assert(lldp);
114 assert(tlv);
115
116 /* Remove expired packets */
49699bac
SS
117 if (prioq_size(lldp->by_expiry) > 0) {
118
119 lldp_set_state(lldp, LLDP_AGENT_RX_DELETE_INFO);
120
ad1ad5c8 121 lldp_mib_delete_objects(lldp);
49699bac 122 }
ad1ad5c8
SS
123
124 r = lldp_mib_add_objects(lldp->by_expiry, lldp->neighbour_mib, tlv);
125 if (r < 0)
126 goto out;
127
49699bac
SS
128 lldp_set_state(lldp, LLDP_AGENT_RX_UPDATE_INFO);
129
ad1ad5c8
SS
130 log_lldp("Packet added. MIB size: %d , PQ size: %d",
131 hashmap_size(lldp->neighbour_mib),
132 prioq_size(lldp->by_expiry));
133
6b3fd9a1 134 lldp->statistics.stats_frames_in_total ++;
ad1ad5c8 135
ad1ad5c8
SS
136 out:
137 if (r < 0)
138 log_lldp("Receive frame failed: %s", strerror(-r));
139
49699bac
SS
140 lldp_set_state(lldp, LLDP_AGENT_RX_WAIT_FOR_FRAME);
141
ad1ad5c8
SS
142 return 0;
143}
144
145/* 10.3.2 LLDPDU validation: rxProcessFrame() */
146int lldp_handle_packet(tlv_packet *tlv, uint16_t length) {
147 uint16_t type, len, i, l, t;
148 bool chassis_id = false;
149 bool malformed = false;
150 bool port_id = false;
151 bool ttl = false;
152 bool end = false;
153 lldp_port *port;
154 uint8_t *p, *q;
155 sd_lldp *lldp;
156 int r;
157
158 assert(tlv);
159 assert(length > 0);
160
161 port = (lldp_port *) tlv->userdata;
162 lldp = (sd_lldp *) port->userdata;
163
164 if (lldp->port->status == LLDP_PORT_STATUS_DISABLED) {
165 log_lldp("Port is disabled : %s . Dropping ...",
166 lldp->port->ifname);
167 goto out;
168 }
169
49699bac
SS
170 lldp_set_state(lldp, LLDP_AGENT_RX_RX_FRAME);
171
ad1ad5c8
SS
172 p = tlv->pdu;
173 p += sizeof(struct ether_header);
174
175 for (i = 1, l = 0; l <= length; i++) {
176
177 memcpy(&t, p, sizeof(uint16_t));
178
179 type = ntohs(t) >> 9;
180 len = ntohs(t) & 0x01ff;
181
182 if (type == LLDP_TYPE_END) {
183 if (len != 0) {
184 log_lldp("TLV type end is not length 0. Length:%d received . Dropping ...",
185 len);
186
187 malformed = true;
188 goto out;
189 }
190
191 end = true;
192
193 break;
194 } else if (type >=_LLDP_TYPE_MAX) {
195 log_lldp("TLV type not recognized %d . Dropping ...",
196 type);
197
198 malformed = true;
199 goto out;
200 }
201
202 /* skip type and lengh encoding */
203 p += 2;
204 q = p;
205
206 p += len;
207 l += (len + 2);
208
209 if (i <= 3) {
210 if (i != type) {
211 log_lldp("TLV missing or out of order. Dropping ...");
212
213 malformed = true;
214 goto out;
215 }
216 }
217
218 switch(type) {
219 case LLDP_TYPE_CHASSIS_ID:
220
221 if (len < 2) {
222 log_lldp("Received malformed Chassis ID TLV len = %d. Dropping",
223 len);
224
225 malformed = true;
226 goto out;
227 }
228
229 if (chassis_id) {
230 log_lldp("Duplicate Chassis ID TLV found. Dropping ...");
231
232 malformed = true;
233 goto out;
234 }
235
236 /* Look what subtype it has */
237 if (*q == LLDP_CHASSIS_SUBTYPE_RESERVED ||
238 *q > LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED) {
239 log_lldp("Unknown subtype: %d found in Chassis ID TLV . Dropping ...",
240 *q);
241
242 malformed = true;
243 goto out;
244
245 }
246
247 chassis_id = true;
248
249 break;
250 case LLDP_TYPE_PORT_ID:
251
252 if (len < 2) {
253 log_lldp("Received malformed Port ID TLV len = %d. Dropping",
254 len);
255
256 malformed = true;
257 goto out;
258 }
259
260 if (port_id) {
261 log_lldp("Duplicate Port ID TLV found. Dropping ...");
262
263 malformed = true;
264 goto out;
265 }
266
267 /* Look what subtype it has */
268 if (*q == LLDP_PORT_SUBTYPE_RESERVED ||
269 *q > LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED) {
270 log_lldp("Unknown subtype: %d found in Port ID TLV . Dropping ...",
271 *q);
272
273 malformed = true;
274 goto out;
275
276 }
277
278 port_id = true;
279
280 break;
281 case LLDP_TYPE_TTL:
282
283 if(len != 2) {
284 log_lldp(
285 "Received invalid lenth: %d TTL TLV. Dropping ...",
286 len);
287
288 malformed = true;
289 goto out;
290 }
291
292 if (ttl) {
293 log_lldp("Duplicate TTL TLV found. Dropping ...");
294
295 malformed = true;
296 goto out;
297 }
298
299 ttl = true;
300
301 break;
302 default:
303
304 if (len == 0) {
305 log_lldp("TLV type = %d's, length 0 received . Dropping ...",
306 type);
307
308 malformed = true;
309 goto out;
310 }
311 break;
312 }
313 }
314
315 if(!chassis_id || !port_id || !ttl || !end) {
316 log_lldp( "One or more mandotory TLV missing . Dropping ...");
317
318 malformed = true;
319 goto out;
320
321 }
322
323 r = tlv_packet_parse_pdu(tlv, length);
324 if (r < 0) {
325 log_lldp( "Failed to parse the TLV. Dropping ...");
326
327 malformed = true;
328 goto out;
329 }
330
331 return lldp_receive_frame(lldp, tlv);
332
333 out:
49699bac
SS
334 lldp_set_state(lldp, LLDP_AGENT_RX_WAIT_FOR_FRAME);
335
ad1ad5c8 336 if (malformed) {
6b3fd9a1
TH
337 lldp->statistics.stats_frames_discarded_total ++;
338 lldp->statistics.stats_frames_in_errors_total ++;
ad1ad5c8
SS
339 }
340
176c355b 341 sd_lldp_packet_unref(tlv);
ad1ad5c8
SS
342
343 return 0;
344}
345
346static int ttl_expiry_item_prioq_compare_func(const void *a, const void *b) {
347 const lldp_neighbour_port *p = a, *q = b;
348
349 if (p->until < q->until)
350 return -1;
351
352 if (p->until > q->until)
353 return 1;
354
355 return 0;
356}
357
49699bac
SS
358static void lldp_set_state(sd_lldp *lldp, LLDPAgentRXState state) {
359
360 assert(lldp);
361 assert(state < _LLDP_AGENT_RX_STATE_MAX);
362
363 lldp->rx_state = state;
364
365 lldp_run_state_machine(lldp);
366}
367
368static void lldp_run_state_machine(sd_lldp *lldp) {
9ef61f2e
DH
369 if (!lldp->cb)
370 return;
371
372 switch (lldp->rx_state) {
373 case LLDP_AGENT_RX_UPDATE_INFO:
374 lldp->cb(lldp, SD_LLDP_EVENT_UPDATE_INFO, lldp->userdata);
375 break;
376 default:
377 break;
378 }
49699bac
SS
379}
380
ad1ad5c8
SS
381/* 10.5.5.2.1 mibDeleteObjects ()
382 * The mibDeleteObjects () procedure deletes all information in the LLDP remote
383 * systems MIB associated with the MSAP identifier if an LLDPDU is received with
384 * an rxTTL value of zero (see 10.3.2) or the timing counter rxInfoTTL expires. */
385
386static void lldp_mib_delete_objects(sd_lldp *lldp) {
387 lldp_neighbour_port *p;
388 usec_t t = 0;
389
390 /* Remove all entries that are past their TTL */
391 for (;;) {
392
393 if (prioq_size(lldp->by_expiry) <= 0)
394 break;
395
396 p = prioq_peek(lldp->by_expiry);
397 if (!p)
398 break;
399
400 if (t <= 0)
27ec691b 401 t = now(clock_boottime_or_monotonic());
ad1ad5c8
SS
402
403 if (p->until > t)
404 break;
405
406 lldp_neighbour_port_remove_and_free(p);
407
6b3fd9a1 408 lldp->statistics.stats_ageouts_total ++;
ad1ad5c8
SS
409 }
410}
411
412static void lldp_mib_objects_flush(sd_lldp *lldp) {
413 lldp_neighbour_port *p, *q;
414 lldp_chassis *c;
415
416 assert(lldp);
417 assert(lldp->neighbour_mib);
418 assert(lldp->by_expiry);
419
420 /* Drop all packets */
421 while ((c = hashmap_steal_first(lldp->neighbour_mib))) {
422
423 LIST_FOREACH_SAFE(port, p, q, c->ports) {
424 lldp_neighbour_port_remove_and_free(p);
425 }
426 }
427
428 assert(hashmap_size(lldp->neighbour_mib) == 0);
429 assert(prioq_size(lldp->by_expiry) == 0);
430}
431
49699bac 432int sd_lldp_save(sd_lldp *lldp, const char *lldp_file) {
49699bac
SS
433 _cleanup_free_ char *temp_path = NULL;
434 _cleanup_fclose_ FILE *f = NULL;
435 uint8_t *mac, *port_id, type;
436 lldp_neighbour_port *p;
437 uint16_t data = 0, length = 0;
438 char buf[LINE_MAX];
439 lldp_chassis *c;
440 usec_t time;
441 Iterator i;
442 int r;
443
444 assert(lldp);
445 assert(lldp_file);
446
447 r = fopen_temporary(lldp_file, &f, &temp_path);
448 if (r < 0)
dacd6cee 449 goto fail;
49699bac
SS
450
451 fchmod(fileno(f), 0644);
452
453 HASHMAP_FOREACH(c, lldp->neighbour_mib, i) {
454 LIST_FOREACH(port, p, c->ports) {
ee14ebf2
DH
455 _cleanup_free_ char *s = NULL;
456 char *k, *t;
49699bac 457
176c355b 458 r = sd_lldp_packet_read_chassis_id(p->packet, &type, &mac, &length);
49699bac
SS
459 if (r < 0)
460 continue;
461
49699bac
SS
462 sprintf(buf, "'_Chassis=%02x:%02x:%02x:%02x:%02x:%02x' '_CType=%d' ",
463 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], type);
464
465 s = strdup(buf);
dacd6cee
LP
466 if (!s) {
467 r = -ENOMEM;
468 goto fail;
469 }
49699bac 470
176c355b 471 r = sd_lldp_packet_read_port_id(p->packet, &type, &port_id, &length);
ee14ebf2 472 if (r < 0)
49699bac 473 continue;
49699bac 474
49699bac 475 if (type != LLDP_PORT_SUBTYPE_MAC_ADDRESS) {
49699bac 476 k = strndup((char *) port_id, length -1);
dacd6cee
LP
477 if (!k) {
478 r = -ENOMEM;
479 goto fail;
480 }
49699bac
SS
481
482 sprintf(buf, "'_Port=%s' '_PType=%d' ", k , type);
49699bac
SS
483 free(k);
484 } else {
49699bac 485 mac = port_id;
49699bac
SS
486 sprintf(buf, "'_Port=%02x:%02x:%02x:%02x:%02x:%02x' '_PType=%d' ",
487 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], type);
49699bac
SS
488 }
489
ee14ebf2 490 k = strappend(s, buf);
dacd6cee
LP
491 if (!k) {
492 r = -ENOMEM;
493 goto fail;
494 }
49699bac
SS
495
496 free(s);
ee14ebf2 497 s = k;
49699bac 498
27ec691b 499 time = now(clock_boottime_or_monotonic());
49699bac
SS
500
501 /* Don't write expired packets */
ee14ebf2 502 if (time - p->until <= 0)
49699bac 503 continue;
49699bac 504
ef753253 505 sprintf(buf, "'_TTL="USEC_FMT"' ", p->until);
49699bac 506
ee14ebf2 507 k = strappend(s, buf);
dacd6cee
LP
508 if (!k) {
509 r = -ENOMEM;
510 goto fail;
511 }
49699bac
SS
512
513 free(s);
ee14ebf2 514 s = k;
49699bac 515
176c355b 516 r = sd_lldp_packet_read_system_name(p->packet, &k, &length);
49699bac
SS
517 if (r < 0)
518 k = strappend(s, "'_NAME=N/A' ");
519 else {
520 t = strndup(k, length);
dacd6cee
LP
521 if (!t) {
522 r = -ENOMEM;
523 goto fail;
524 }
49699bac
SS
525
526 k = strjoin(s, "'_NAME=", t, "' ", NULL);
ee14ebf2 527 free(t);
49699bac
SS
528 }
529
dacd6cee
LP
530 if (!k) {
531 r = -ENOMEM;
532 goto fail;
533 }
49699bac
SS
534
535 free(s);
536 s = k;
537
176c355b 538 (void) sd_lldp_packet_read_system_capability(p->packet, &data);
49699bac
SS
539
540 sprintf(buf, "'_CAP=%x'", data);
541
ee14ebf2 542 k = strappend(s, buf);
dacd6cee
LP
543 if (!k) {
544 r = -ENOMEM;
545 goto fail;
546 }
49699bac 547
49699bac 548 free(s);
ee14ebf2
DH
549 s = k;
550
551 fprintf(f, "%s\n", s);
49699bac
SS
552 }
553 }
49699bac 554
dacd6cee
LP
555 r = fflush_and_check(f);
556 if (r < 0)
557 goto fail;
49699bac 558
dacd6cee 559 if (rename(temp_path, lldp_file) < 0) {
49699bac 560 r = -errno;
dacd6cee 561 goto fail;
49699bac
SS
562 }
563
dacd6cee
LP
564 return 0;
565
566 fail:
567 if (temp_path)
568 (void) unlink(temp_path);
49699bac 569
dacd6cee 570 return log_error_errno(r, "Failed to save lldp data %s: %m", lldp_file);
49699bac
SS
571}
572
ad1ad5c8
SS
573int sd_lldp_start(sd_lldp *lldp) {
574 int r;
575
576 assert_return(lldp, -EINVAL);
577 assert_return(lldp->port, -EINVAL);
578
579 lldp->port->status = LLDP_PORT_STATUS_ENABLED;
580
49699bac
SS
581 lldp_set_state(lldp, LLDP_AGENT_RX_LLDP_INITIALIZE);
582
ad1ad5c8
SS
583 r = lldp_port_start(lldp->port);
584 if (r < 0) {
585 log_lldp("Failed to start Port : %s , %s",
586 lldp->port->ifname,
587 strerror(-r));
49699bac
SS
588
589 lldp_set_state(lldp, LLDP_AGENT_RX_WAIT_PORT_OPERATIONAL);
590
ad1ad5c8
SS
591 return r;
592 }
593
49699bac
SS
594 lldp_set_state(lldp, LLDP_AGENT_RX_WAIT_FOR_FRAME);
595
ad1ad5c8
SS
596 return 0;
597}
598
599int sd_lldp_stop(sd_lldp *lldp) {
600 int r;
601
602 assert_return(lldp, -EINVAL);
603 assert_return(lldp->port, -EINVAL);
604
605 lldp->port->status = LLDP_PORT_STATUS_DISABLED;
606
607 r = lldp_port_stop(lldp->port);
608 if (r < 0)
609 return r;
610
611 lldp_mib_objects_flush(lldp);
612
613 return 0;
614}
615
616int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int priority) {
617 int r;
618
619 assert_return(lldp, -EINVAL);
620 assert_return(!lldp->port->event, -EBUSY);
621
622 if (event)
623 lldp->port->event = sd_event_ref(event);
624 else {
625 r = sd_event_default(&lldp->port->event);
626 if (r < 0)
627 return r;
628 }
629
630 lldp->port->event_priority = priority;
631
632 return 0;
633}
634
635int sd_lldp_detach_event(sd_lldp *lldp) {
636
637 assert_return(lldp, -EINVAL);
638
639 lldp->port->event = sd_event_unref(lldp->port->event);
640
641 return 0;
642}
643
49699bac
SS
644int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_cb_t cb, void *userdata) {
645 assert_return(lldp, -EINVAL);
646
647 lldp->cb = cb;
648 lldp->userdata = userdata;
649
650 return 0;
651}
652
ad1ad5c8
SS
653void sd_lldp_free(sd_lldp *lldp) {
654
655 if (!lldp)
656 return;
657
658 /* Drop all packets */
659 lldp_mib_objects_flush(lldp);
660
661 lldp_port_free(lldp->port);
662
663 hashmap_free(lldp->neighbour_mib);
664 prioq_free(lldp->by_expiry);
665
666 free(lldp);
667}
668
669int sd_lldp_new(int ifindex,
7a6f1457
TG
670 const char *ifname,
671 const struct ether_addr *mac,
ad1ad5c8 672 sd_lldp **ret) {
7a6f1457 673 _cleanup_lldp_free_ sd_lldp *lldp = NULL;
ad1ad5c8
SS
674 int r;
675
676 assert_return(ret, -EINVAL);
677 assert_return(ifindex > 0, -EINVAL);
678 assert_return(ifname, -EINVAL);
679 assert_return(mac, -EINVAL);
680
681 lldp = new0(sd_lldp, 1);
682 if (!lldp)
683 return -ENOMEM;
684
685 r = lldp_port_new(ifindex, ifname, mac, lldp, &lldp->port);
686 if (r < 0)
687 return r;
688
689 lldp->neighbour_mib = hashmap_new(&chassis_id_hash_ops);
690 if (!lldp->neighbour_mib)
691 return -ENOMEM;
692
693 r = prioq_ensure_allocated(&lldp->by_expiry,
694 ttl_expiry_item_prioq_compare_func);
695 if (r < 0)
696 return r;
697
49699bac
SS
698 lldp->rx_state = LLDP_AGENT_RX_WAIT_PORT_OPERATIONAL;
699
ad1ad5c8
SS
700 *ret = lldp;
701 lldp = NULL;
702
703 return 0;
704}