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