]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd-network/sd-lldp.c
45881e5f64a1a98ca889caa564f0ae0c7292051a
[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 #include "event-util.h"
28
29 #include "lldp-tlv.h"
30 #include "lldp-port.h"
31 #include "sd-lldp.h"
32 #include "prioq.h"
33 #include "strv.h"
34 #include "lldp-internal.h"
35 #include "ether-addr-util.h"
36
37 typedef enum LLDPAgentRXState {
38 LLDP_AGENT_RX_WAIT_PORT_OPERATIONAL = 4,
39 LLDP_AGENT_RX_DELETE_AGED_INFO,
40 LLDP_AGENT_RX_LLDP_INITIALIZE,
41 LLDP_AGENT_RX_WAIT_FOR_FRAME,
42 LLDP_AGENT_RX_RX_FRAME,
43 LLDP_AGENT_RX_DELETE_INFO,
44 LLDP_AGENT_RX_UPDATE_INFO,
45 _LLDP_AGENT_RX_STATE_MAX,
46 _LLDP_AGENT_RX_INVALID = -1,
47 } LLDPAgentRXState;
48
49 /* Section 10.5.2.2 Reception counters */
50 struct lldp_agent_statitics {
51 uint64_t stats_ageouts_total;
52 uint64_t stats_frames_discarded_total;
53 uint64_t stats_frames_in_errors_total;
54 uint64_t stats_frames_in_total;
55 uint64_t stats_tlvs_discarded_total;
56 uint64_t stats_tlvs_unrecognized_total;
57 };
58
59 struct sd_lldp {
60 lldp_port *port;
61
62 Prioq *by_expiry;
63 Hashmap *neighbour_mib;
64
65 sd_lldp_cb_t cb;
66
67 void *userdata;
68
69 LLDPAgentRXState rx_state;
70 lldp_agent_statitics statitics;
71 };
72
73 static unsigned long chassis_id_hash_func(const void *p,
74 const uint8_t hash_key[HASH_KEY_SIZE]) {
75 uint64_t u;
76 const lldp_chassis_id *id = p;
77
78 assert(id);
79
80 siphash24((uint8_t *) &u, id->data, id->length, hash_key);
81
82 return (unsigned long) u;
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->statitics.stats_frames_in_total ++;
137
138 return 0;
139
140 out:
141 if (r < 0)
142 log_lldp("Receive frame failed: %s", strerror(-r));
143
144 lldp_set_state(lldp, LLDP_AGENT_RX_WAIT_FOR_FRAME);
145
146 return 0;
147 }
148
149 /* 10.3.2 LLDPDU validation: rxProcessFrame() */
150 int lldp_handle_packet(tlv_packet *tlv, uint16_t length) {
151 uint16_t type, len, i, l, t;
152 bool chassis_id = false;
153 bool malformed = false;
154 bool port_id = false;
155 bool ttl = false;
156 bool end = false;
157 lldp_port *port;
158 uint8_t *p, *q;
159 sd_lldp *lldp;
160 int r;
161
162 assert(tlv);
163 assert(length > 0);
164
165 port = (lldp_port *) tlv->userdata;
166 lldp = (sd_lldp *) port->userdata;
167
168 if (lldp->port->status == LLDP_PORT_STATUS_DISABLED) {
169 log_lldp("Port is disabled : %s . Dropping ...",
170 lldp->port->ifname);
171 goto out;
172 }
173
174 lldp_set_state(lldp, LLDP_AGENT_RX_RX_FRAME);
175
176 p = tlv->pdu;
177 p += sizeof(struct ether_header);
178
179 for (i = 1, l = 0; l <= length; i++) {
180
181 memcpy(&t, p, sizeof(uint16_t));
182
183 type = ntohs(t) >> 9;
184 len = ntohs(t) & 0x01ff;
185
186 if (type == LLDP_TYPE_END) {
187 if (len != 0) {
188 log_lldp("TLV type end is not length 0. Length:%d received . Dropping ...",
189 len);
190
191 malformed = true;
192 goto out;
193 }
194
195 end = true;
196
197 break;
198 } else if (type >=_LLDP_TYPE_MAX) {
199 log_lldp("TLV type not recognized %d . Dropping ...",
200 type);
201
202 malformed = true;
203 goto out;
204 }
205
206 /* skip type and lengh encoding */
207 p += 2;
208 q = p;
209
210 p += len;
211 l += (len + 2);
212
213 if (i <= 3) {
214 if (i != type) {
215 log_lldp("TLV missing or out of order. Dropping ...");
216
217 malformed = true;
218 goto out;
219 }
220 }
221
222 switch(type) {
223 case LLDP_TYPE_CHASSIS_ID:
224
225 if (len < 2) {
226 log_lldp("Received malformed Chassis ID TLV len = %d. Dropping",
227 len);
228
229 malformed = true;
230 goto out;
231 }
232
233 if (chassis_id) {
234 log_lldp("Duplicate Chassis ID TLV found. Dropping ...");
235
236 malformed = true;
237 goto out;
238 }
239
240 /* Look what subtype it has */
241 if (*q == LLDP_CHASSIS_SUBTYPE_RESERVED ||
242 *q > LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED) {
243 log_lldp("Unknown subtype: %d found in Chassis ID TLV . Dropping ...",
244 *q);
245
246 malformed = true;
247 goto out;
248
249 }
250
251 chassis_id = true;
252
253 break;
254 case LLDP_TYPE_PORT_ID:
255
256 if (len < 2) {
257 log_lldp("Received malformed Port ID TLV len = %d. Dropping",
258 len);
259
260 malformed = true;
261 goto out;
262 }
263
264 if (port_id) {
265 log_lldp("Duplicate Port ID TLV found. Dropping ...");
266
267 malformed = true;
268 goto out;
269 }
270
271 /* Look what subtype it has */
272 if (*q == LLDP_PORT_SUBTYPE_RESERVED ||
273 *q > LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED) {
274 log_lldp("Unknown subtype: %d found in Port ID TLV . Dropping ...",
275 *q);
276
277 malformed = true;
278 goto out;
279
280 }
281
282 port_id = true;
283
284 break;
285 case LLDP_TYPE_TTL:
286
287 if(len != 2) {
288 log_lldp(
289 "Received invalid lenth: %d TTL TLV. Dropping ...",
290 len);
291
292 malformed = true;
293 goto out;
294 }
295
296 if (ttl) {
297 log_lldp("Duplicate TTL TLV found. Dropping ...");
298
299 malformed = true;
300 goto out;
301 }
302
303 ttl = true;
304
305 break;
306 default:
307
308 if (len == 0) {
309 log_lldp("TLV type = %d's, length 0 received . Dropping ...",
310 type);
311
312 malformed = true;
313 goto out;
314 }
315 break;
316 }
317 }
318
319 if(!chassis_id || !port_id || !ttl || !end) {
320 log_lldp( "One or more mandotory TLV missing . Dropping ...");
321
322 malformed = true;
323 goto out;
324
325 }
326
327 r = tlv_packet_parse_pdu(tlv, length);
328 if (r < 0) {
329 log_lldp( "Failed to parse the TLV. Dropping ...");
330
331 malformed = true;
332 goto out;
333 }
334
335 return lldp_receive_frame(lldp, tlv);
336
337 out:
338 lldp_set_state(lldp, LLDP_AGENT_RX_WAIT_FOR_FRAME);
339
340 if (malformed) {
341 lldp->statitics.stats_frames_discarded_total ++;
342 lldp->statitics.stats_frames_in_errors_total ++;
343 }
344
345 tlv_packet_free(tlv);
346
347 return 0;
348 }
349
350 static int ttl_expiry_item_prioq_compare_func(const void *a, const void *b) {
351 const lldp_neighbour_port *p = a, *q = b;
352
353 if (p->until < q->until)
354 return -1;
355
356 if (p->until > q->until)
357 return 1;
358
359 return 0;
360 }
361
362 static void lldp_set_state(sd_lldp *lldp, LLDPAgentRXState state) {
363
364 assert(lldp);
365 assert(state < _LLDP_AGENT_RX_STATE_MAX);
366
367 lldp->rx_state = state;
368
369 lldp_run_state_machine(lldp);
370 }
371
372 static void lldp_run_state_machine(sd_lldp *lldp) {
373
374 if (lldp->rx_state == LLDP_AGENT_RX_UPDATE_INFO)
375 if (lldp->cb)
376 lldp->cb(lldp, LLDP_AGENT_RX_UPDATE_INFO, lldp->userdata);
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);
400
401 if (p->until > t)
402 break;
403
404 lldp_neighbour_port_remove_and_free(p);
405
406 lldp->statitics.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 *s = NULL, *t = NULL, *k = NULL;
432 _cleanup_free_ char *temp_path = NULL;
433 _cleanup_fclose_ FILE *f = NULL;
434 uint8_t *mac, *port_id, type;
435 lldp_neighbour_port *p;
436 uint16_t data = 0, length = 0;
437 char buf[LINE_MAX];
438 lldp_chassis *c;
439 usec_t time;
440 Iterator i;
441 int r;
442
443 assert(lldp);
444 assert(lldp_file);
445
446 r = fopen_temporary(lldp_file, &f, &temp_path);
447 if (r < 0)
448 goto finish;
449
450 fchmod(fileno(f), 0644);
451
452 HASHMAP_FOREACH(c, lldp->neighbour_mib, i) {
453 LIST_FOREACH(port, p, c->ports) {
454
455 r = lldp_read_chassis_id(p->packet, &type, &length, &mac);
456 if (r < 0)
457 continue;
458
459 memzero(buf, LINE_MAX);
460
461 sprintf(buf, "'_Chassis=%02x:%02x:%02x:%02x:%02x:%02x' '_CType=%d' ",
462 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], type);
463
464 s = strdup(buf);
465 if (!s)
466 return -ENOMEM;
467
468 r = lldp_read_port_id(p->packet, &type, &length, &port_id);
469 if (r < 0) {
470 free(s);
471 continue;
472 }
473
474 memzero(buf, LINE_MAX);
475 if (type != LLDP_PORT_SUBTYPE_MAC_ADDRESS) {
476
477 k = strndup((char *) port_id, length -1);
478 if (!k)
479 return -ENOMEM;
480
481 sprintf(buf, "'_Port=%s' '_PType=%d' ", k , type);
482
483 t = strappend(s, buf);
484
485 free(k);
486 } else {
487
488 mac = port_id;
489
490 sprintf(buf, "'_Port=%02x:%02x:%02x:%02x:%02x:%02x' '_PType=%d' ",
491 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], type);
492
493 t = strappend(s, buf);
494 }
495
496 if (!t)
497 return -ENOMEM;
498
499 free(s);
500 s = t;
501
502 time = now(CLOCK_BOOTTIME);
503
504 /* Don't write expired packets */
505 if(time - p->until <= 0) {
506 free(s);
507 continue;
508 }
509
510 memzero(buf, LINE_MAX);
511 sprintf(buf, "'_TTL=%lu' ", p->until);
512
513 t = strappend(s, buf);
514 if (!t)
515 return -ENOMEM;
516
517 free(s);
518 s = t;
519
520 r = lldp_read_system_name(p->packet, &length, &k);
521 if (r < 0)
522 k = strappend(s, "'_NAME=N/A' ");
523 else {
524 t = strndup(k, length);
525 if (!t)
526 return -ENOMEM;
527
528 k = strjoin(s, "'_NAME=", t, "' ", NULL);
529 }
530
531 if (!k)
532 return -ENOMEM;
533
534 free(s);
535 s = k;
536
537 memzero(buf, LINE_MAX);
538
539 (void)lldp_read_system_capability(p->packet, &data);
540
541 sprintf(buf, "'_CAP=%x'", data);
542
543 t = strappend(s, buf);
544 if (!t)
545 return -ENOMEM;
546
547 fprintf(f, "%s\n", t);
548
549 free(s);
550 free(t);
551 }
552 }
553 r = 0;
554
555 s = NULL;
556 t = NULL;
557 k = NULL;
558
559 fflush(f);
560
561 if (ferror(f) || rename(temp_path, lldp_file) < 0) {
562 r = -errno;
563 unlink(lldp_file);
564 unlink(temp_path);
565 }
566
567 finish:
568 if (r < 0)
569 log_error("Failed to save lldp data %s: %s", lldp_file, strerror(-r));
570
571 return r;
572 }
573
574 int sd_lldp_start(sd_lldp *lldp) {
575 int r;
576
577 assert_return(lldp, -EINVAL);
578 assert_return(lldp->port, -EINVAL);
579
580 lldp->port->status = LLDP_PORT_STATUS_ENABLED;
581
582 lldp_set_state(lldp, LLDP_AGENT_RX_LLDP_INITIALIZE);
583
584 r = lldp_port_start(lldp->port);
585 if (r < 0) {
586 log_lldp("Failed to start Port : %s , %s",
587 lldp->port->ifname,
588 strerror(-r));
589
590 lldp_set_state(lldp, LLDP_AGENT_RX_WAIT_PORT_OPERATIONAL);
591
592 return r;
593 }
594
595 lldp_set_state(lldp, LLDP_AGENT_RX_WAIT_FOR_FRAME);
596
597 return 0;
598 }
599
600 int sd_lldp_stop(sd_lldp *lldp) {
601 int r;
602
603 assert_return(lldp, -EINVAL);
604 assert_return(lldp->port, -EINVAL);
605
606 lldp->port->status = LLDP_PORT_STATUS_DISABLED;
607
608 r = lldp_port_stop(lldp->port);
609 if (r < 0)
610 return r;
611
612 lldp_mib_objects_flush(lldp);
613
614 return 0;
615 }
616
617 int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int priority) {
618 int r;
619
620 assert_return(lldp, -EINVAL);
621 assert_return(!lldp->port->event, -EBUSY);
622
623 if (event)
624 lldp->port->event = sd_event_ref(event);
625 else {
626 r = sd_event_default(&lldp->port->event);
627 if (r < 0)
628 return r;
629 }
630
631 lldp->port->event_priority = priority;
632
633 return 0;
634 }
635
636 int sd_lldp_detach_event(sd_lldp *lldp) {
637
638 assert_return(lldp, -EINVAL);
639
640 lldp->port->event = sd_event_unref(lldp->port->event);
641
642 return 0;
643 }
644
645 int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_cb_t cb, void *userdata) {
646 assert_return(lldp, -EINVAL);
647
648 lldp->cb = cb;
649 lldp->userdata = userdata;
650
651 return 0;
652 }
653
654 void sd_lldp_free(sd_lldp *lldp) {
655
656 if (!lldp)
657 return;
658
659 /* Drop all packets */
660 lldp_mib_objects_flush(lldp);
661
662 lldp_port_free(lldp->port);
663
664 hashmap_free(lldp->neighbour_mib);
665 prioq_free(lldp->by_expiry);
666
667 free(lldp);
668 }
669
670 int sd_lldp_new(int ifindex,
671 char *ifname,
672 struct ether_addr *mac,
673 sd_lldp **ret) {
674 _cleanup_sd_lldp_free_ sd_lldp *lldp = NULL;
675 int r;
676
677 assert_return(ret, -EINVAL);
678 assert_return(ifindex > 0, -EINVAL);
679 assert_return(ifname, -EINVAL);
680 assert_return(mac, -EINVAL);
681
682 lldp = new0(sd_lldp, 1);
683 if (!lldp)
684 return -ENOMEM;
685
686 r = lldp_port_new(ifindex, ifname, mac, lldp, &lldp->port);
687 if (r < 0)
688 return r;
689
690 lldp->neighbour_mib = hashmap_new(&chassis_id_hash_ops);
691 if (!lldp->neighbour_mib)
692 return -ENOMEM;
693
694 r = prioq_ensure_allocated(&lldp->by_expiry,
695 ttl_expiry_item_prioq_compare_func);
696 if (r < 0)
697 return r;
698
699 lldp->rx_state = LLDP_AGENT_RX_WAIT_PORT_OPERATIONAL;
700
701 *ret = lldp;
702 lldp = NULL;
703
704 return 0;
705 }