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