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