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