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