]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/libsystemd-network/sd-lldp.c
util-lib: move more file I/O related calls into fileio.[ch]
[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 "fd-util.h"
28 #include "fileio.h"
29 #include "hashmap.h"
30 #include "lldp-internal.h"
31 #include "lldp-port.h"
32 #include "lldp-tlv.h"
33 #include "lldp-util.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 uint16_t type, len, i, l, t;
149 bool chassis_id = false;
150 bool malformed = false;
151 bool port_id = false;
152 bool ttl = false;
153 bool end = false;
154 lldp_port *port;
155 uint8_t *p, *q;
156 sd_lldp *lldp;
157 int r;
158
159 assert(tlv);
160 assert(length > 0);
161
162 port = (lldp_port *) tlv->userdata;
163 lldp = (sd_lldp *) port->userdata;
164
165 if (lldp->port->status == LLDP_PORT_STATUS_DISABLED) {
166 log_lldp("Port is disabled : %s . Dropping ...",
167 lldp->port->ifname);
168 goto out;
169 }
170
171 lldp_set_state(lldp, LLDP_AGENT_RX_RX_FRAME);
172
173 p = tlv->pdu;
174 p += sizeof(struct ether_header);
175
176 for (i = 1, l = 0; l <= length; i++) {
177
178 memcpy(&t, p, sizeof(uint16_t));
179
180 type = ntohs(t) >> 9;
181 len = ntohs(t) & 0x01ff;
182
183 if (type == LLDP_TYPE_END) {
184 if (len != 0) {
185 log_lldp("TLV type end is not length 0. Length:%d received . Dropping ...",
186 len);
187
188 malformed = true;
189 goto out;
190 }
191
192 end = true;
193
194 break;
195 } else if (type >=_LLDP_TYPE_MAX) {
196 log_lldp("TLV type not recognized %d . Dropping ...",
197 type);
198
199 malformed = true;
200 goto out;
201 }
202
203 /* skip type and length encoding */
204 p += 2;
205 q = p;
206
207 p += len;
208 l += (len + 2);
209
210 if (i <= 3) {
211 if (i != type) {
212 log_lldp("TLV missing or out of order. Dropping ...");
213
214 malformed = true;
215 goto out;
216 }
217 }
218
219 switch(type) {
220 case LLDP_TYPE_CHASSIS_ID:
221
222 if (len < 2) {
223 log_lldp("Received malformed Chassis ID TLV len = %d. Dropping",
224 len);
225
226 malformed = true;
227 goto out;
228 }
229
230 if (chassis_id) {
231 log_lldp("Duplicate Chassis ID TLV found. Dropping ...");
232
233 malformed = true;
234 goto out;
235 }
236
237 /* Look what subtype it has */
238 if (*q == LLDP_CHASSIS_SUBTYPE_RESERVED ||
239 *q > LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED) {
240 log_lldp("Unknown subtype: %d found in Chassis ID TLV . Dropping ...",
241 *q);
242
243 malformed = true;
244 goto out;
245
246 }
247
248 chassis_id = true;
249
250 break;
251 case LLDP_TYPE_PORT_ID:
252
253 if (len < 2) {
254 log_lldp("Received malformed Port ID TLV len = %d. Dropping",
255 len);
256
257 malformed = true;
258 goto out;
259 }
260
261 if (port_id) {
262 log_lldp("Duplicate Port ID TLV found. Dropping ...");
263
264 malformed = true;
265 goto out;
266 }
267
268 /* Look what subtype it has */
269 if (*q == LLDP_PORT_SUBTYPE_RESERVED ||
270 *q > LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED) {
271 log_lldp("Unknown subtype: %d found in Port ID TLV . Dropping ...",
272 *q);
273
274 malformed = true;
275 goto out;
276
277 }
278
279 port_id = true;
280
281 break;
282 case LLDP_TYPE_TTL:
283
284 if(len != 2) {
285 log_lldp(
286 "Received invalid lenth: %d TTL TLV. Dropping ...",
287 len);
288
289 malformed = true;
290 goto out;
291 }
292
293 if (ttl) {
294 log_lldp("Duplicate TTL TLV found. Dropping ...");
295
296 malformed = true;
297 goto out;
298 }
299
300 ttl = true;
301
302 break;
303 default:
304
305 if (len == 0) {
306 log_lldp("TLV type = %d's, length 0 received . Dropping ...",
307 type);
308
309 malformed = true;
310 goto out;
311 }
312 break;
313 }
314 }
315
316 if(!chassis_id || !port_id || !ttl || !end) {
317 log_lldp( "One or more mandotory TLV missing . Dropping ...");
318
319 malformed = true;
320 goto out;
321
322 }
323
324 r = tlv_packet_parse_pdu(tlv, length);
325 if (r < 0) {
326 log_lldp( "Failed to parse the TLV. Dropping ...");
327
328 malformed = true;
329 goto out;
330 }
331
332 return lldp_receive_frame(lldp, tlv);
333
334 out:
335 lldp_set_state(lldp, LLDP_AGENT_RX_WAIT_FOR_FRAME);
336
337 if (malformed) {
338 lldp->statistics.stats_frames_discarded_total ++;
339 lldp->statistics.stats_frames_in_errors_total ++;
340 }
341
342 sd_lldp_packet_unref(tlv);
343
344 return 0;
345 }
346
347 static int ttl_expiry_item_prioq_compare_func(const void *a, const void *b) {
348 const lldp_neighbour_port *p = a, *q = b;
349
350 if (p->until < q->until)
351 return -1;
352
353 if (p->until > q->until)
354 return 1;
355
356 return 0;
357 }
358
359 static void lldp_set_state(sd_lldp *lldp, LLDPAgentRXState state) {
360
361 assert(lldp);
362 assert(state < _LLDP_AGENT_RX_STATE_MAX);
363
364 lldp->rx_state = state;
365
366 lldp_run_state_machine(lldp);
367 }
368
369 static void lldp_run_state_machine(sd_lldp *lldp) {
370 if (!lldp->cb)
371 return;
372
373 switch (lldp->rx_state) {
374 case LLDP_AGENT_RX_UPDATE_INFO:
375 lldp->cb(lldp, SD_LLDP_EVENT_UPDATE_INFO, lldp->userdata);
376 break;
377 default:
378 break;
379 }
380 }
381
382 /* 10.5.5.2.1 mibDeleteObjects ()
383 * The mibDeleteObjects () procedure deletes all information in the LLDP remote
384 * systems MIB associated with the MSAP identifier if an LLDPDU is received with
385 * an rxTTL value of zero (see 10.3.2) or the timing counter rxInfoTTL expires. */
386
387 static void lldp_mib_delete_objects(sd_lldp *lldp) {
388 lldp_neighbour_port *p;
389 usec_t t = 0;
390
391 /* Remove all entries that are past their TTL */
392 for (;;) {
393
394 if (prioq_size(lldp->by_expiry) <= 0)
395 break;
396
397 p = prioq_peek(lldp->by_expiry);
398 if (!p)
399 break;
400
401 if (t <= 0)
402 t = now(clock_boottime_or_monotonic());
403
404 if (p->until > t)
405 break;
406
407 lldp_neighbour_port_remove_and_free(p);
408
409 lldp->statistics.stats_ageouts_total ++;
410 }
411 }
412
413 static void lldp_mib_objects_flush(sd_lldp *lldp) {
414 lldp_neighbour_port *p, *q;
415 lldp_chassis *c;
416
417 assert(lldp);
418 assert(lldp->neighbour_mib);
419 assert(lldp->by_expiry);
420
421 /* Drop all packets */
422 while ((c = hashmap_steal_first(lldp->neighbour_mib))) {
423
424 LIST_FOREACH_SAFE(port, p, q, c->ports) {
425 lldp_neighbour_port_remove_and_free(p);
426 }
427 }
428
429 assert(hashmap_size(lldp->neighbour_mib) == 0);
430 assert(prioq_size(lldp->by_expiry) == 0);
431 }
432
433 int sd_lldp_save(sd_lldp *lldp, const char *lldp_file) {
434 _cleanup_free_ char *temp_path = NULL;
435 _cleanup_fclose_ FILE *f = NULL;
436 uint8_t *mac, *port_id, type;
437 lldp_neighbour_port *p;
438 uint16_t data = 0, length = 0;
439 char buf[LINE_MAX];
440 lldp_chassis *c;
441 usec_t time;
442 Iterator i;
443 int r;
444
445 assert(lldp);
446 assert(lldp_file);
447
448 r = fopen_temporary(lldp_file, &f, &temp_path);
449 if (r < 0)
450 goto fail;
451
452 fchmod(fileno(f), 0644);
453
454 HASHMAP_FOREACH(c, lldp->neighbour_mib, i) {
455 LIST_FOREACH(port, p, c->ports) {
456 _cleanup_free_ char *s = NULL;
457 char *k, *t;
458
459 r = sd_lldp_packet_read_chassis_id(p->packet, &type, &mac, &length);
460 if (r < 0)
461 continue;
462
463 sprintf(buf, "'_Chassis=%02x:%02x:%02x:%02x:%02x:%02x' '_CType=%d' ",
464 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], type);
465
466 s = strdup(buf);
467 if (!s) {
468 r = -ENOMEM;
469 goto fail;
470 }
471
472 r = sd_lldp_packet_read_port_id(p->packet, &type, &port_id, &length);
473 if (r < 0)
474 continue;
475
476 if (type != LLDP_PORT_SUBTYPE_MAC_ADDRESS) {
477 k = strndup((char *) port_id, length -1);
478 if (!k) {
479 r = -ENOMEM;
480 goto fail;
481 }
482
483 sprintf(buf, "'_Port=%s' '_PType=%d' ", k , type);
484 free(k);
485 } else {
486 mac = port_id;
487 sprintf(buf, "'_Port=%02x:%02x:%02x:%02x:%02x:%02x' '_PType=%d' ",
488 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], type);
489 }
490
491 k = strappend(s, buf);
492 if (!k) {
493 r = -ENOMEM;
494 goto fail;
495 }
496
497 free(s);
498 s = k;
499
500 time = now(clock_boottime_or_monotonic());
501
502 /* Don't write expired packets */
503 if (time - p->until <= 0)
504 continue;
505
506 sprintf(buf, "'_TTL="USEC_FMT"' ", p->until);
507
508 k = strappend(s, buf);
509 if (!k) {
510 r = -ENOMEM;
511 goto fail;
512 }
513
514 free(s);
515 s = k;
516
517 r = sd_lldp_packet_read_system_name(p->packet, &k, &length);
518 if (r < 0)
519 k = strappend(s, "'_NAME=N/A' ");
520 else {
521 t = strndup(k, length);
522 if (!t) {
523 r = -ENOMEM;
524 goto fail;
525 }
526
527 k = strjoin(s, "'_NAME=", t, "' ", NULL);
528 free(t);
529 }
530
531 if (!k) {
532 r = -ENOMEM;
533 goto fail;
534 }
535
536 free(s);
537 s = k;
538
539 (void) sd_lldp_packet_read_system_capability(p->packet, &data);
540
541 sprintf(buf, "'_CAP=%x'", data);
542
543 k = strappend(s, buf);
544 if (!k) {
545 r = -ENOMEM;
546 goto fail;
547 }
548
549 free(s);
550 s = k;
551
552 fprintf(f, "%s\n", s);
553 }
554 }
555
556 r = fflush_and_check(f);
557 if (r < 0)
558 goto fail;
559
560 if (rename(temp_path, lldp_file) < 0) {
561 r = -errno;
562 goto fail;
563 }
564
565 return 0;
566
567 fail:
568 if (temp_path)
569 (void) unlink(temp_path);
570
571 return log_error_errno(r, "Failed to save lldp data %s: %m", lldp_file);
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 const char *ifname,
672 const struct ether_addr *mac,
673 sd_lldp **ret) {
674 _cleanup_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 }
706
707 int sd_lldp_get_packets(sd_lldp *lldp, sd_lldp_packet ***tlvs) {
708 lldp_neighbour_port *p;
709 lldp_chassis *c;
710 Iterator iter;
711 unsigned count = 0, i;
712
713 assert_return(lldp, -EINVAL);
714 assert_return(tlvs, -EINVAL);
715
716 HASHMAP_FOREACH(c, lldp->neighbour_mib, iter) {
717 LIST_FOREACH(port, p, c->ports)
718 count++;
719 }
720
721 if (!count) {
722 *tlvs = NULL;
723 return 0;
724 }
725
726 *tlvs = new(sd_lldp_packet *, count);
727 if (!*tlvs)
728 return -ENOMEM;
729
730 i = 0;
731 HASHMAP_FOREACH(c, lldp->neighbour_mib, iter) {
732 LIST_FOREACH(port, p, c->ports)
733 (*tlvs)[i++] = sd_lldp_packet_ref(p->packet);
734 }
735
736 return count;
737 }