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