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