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