2 This file is part of systemd.
4 Copyright (C) 2014 Tom Gundersen
5 Copyright (C) 2014 Susant Sahani
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.
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.
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/>.
21 #include <arpa/inet.h>
25 #include "alloc-util.h"
29 #include "lldp-internal.h"
30 #include "lldp-network.h"
33 #include "siphash24.h"
34 #include "string-util.h"
41 int64_t event_priority
;
42 sd_event_source
*event_source
;
45 Hashmap
*neighbour_mib
;
47 sd_lldp_callback_t callback
;
51 static void chassis_id_hash_func(const void *p
, struct siphash
*state
) {
52 const lldp_chassis_id
*id
= p
;
57 siphash24_compress(&id
->length
, sizeof(id
->length
), state
);
58 siphash24_compress(id
->data
, id
->length
, state
);
61 static int chassis_id_compare_func(const void *_a
, const void *_b
) {
62 const lldp_chassis_id
*a
, *b
;
67 assert(!a
->length
|| a
->data
);
68 assert(!b
->length
|| b
->data
);
70 if (a
->type
!= b
->type
)
73 if (a
->length
!= b
->length
)
74 return a
->length
< b
->length
? -1 : 1;
76 return memcmp(a
->data
, b
->data
, a
->length
);
79 static const struct hash_ops chassis_id_hash_ops
= {
80 .hash
= chassis_id_hash_func
,
81 .compare
= chassis_id_compare_func
84 static void lldp_mib_delete_objects(sd_lldp
*lldp
);
86 static int lldp_receive_frame(sd_lldp
*lldp
, tlv_packet
*tlv
) {
92 /* Remove expired packets */
93 if (prioq_size(lldp
->by_expiry
) > 0)
94 lldp_mib_delete_objects(lldp
);
96 r
= lldp_mib_add_objects(lldp
->by_expiry
, lldp
->neighbour_mib
, tlv
);
101 lldp
->callback(lldp
, SD_LLDP_EVENT_UPDATE_INFO
, lldp
->userdata
);
103 log_lldp("Packet added. MIB size: %d , PQ size: %d",
104 hashmap_size(lldp
->neighbour_mib
),
105 prioq_size(lldp
->by_expiry
));
109 log_lldp("Receive frame failed: %s", strerror(-r
));
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
;
126 lldp
= tlv
->userdata
;
129 p
+= sizeof(struct ether_header
);
131 for (i
= 1, l
= 0; l
<= length
; i
++) {
133 memcpy(&t
, p
, sizeof(uint16_t));
135 type
= ntohs(t
) >> 9;
136 len
= ntohs(t
) & 0x01ff;
138 if (type
== LLDP_TYPE_END
) {
140 log_lldp("TLV type end must be length 0 (not %d). Dropping.", len
);
148 } else if (type
>=_LLDP_TYPE_MAX
) {
149 log_lldp("TLV type: %d not recognized. Dropping.", type
);
154 /* skip type and length encoding */
163 log_lldp("TLV missing or out of order. Dropping.");
170 case LLDP_TYPE_CHASSIS_ID
:
173 log_lldp("Received malformed Chassis ID TLV length: %d. Dropping.", len
);
179 log_lldp("Duplicate Chassis ID TLV found. Dropping.");
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
);
195 case LLDP_TYPE_PORT_ID
:
198 log_lldp("Received malformed Port ID TLV length: %d. Dropping.", len
);
204 log_lldp("Duplicate Port ID TLV found. Dropping.");
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
);
223 log_lldp("Received invalid TTL TLV lenth: %d. Dropping.", len
);
229 log_lldp("Duplicate TTL TLV found. Dropping.");
237 case LLDP_TYPE_SYSTEM_NAME
:
239 /* According to RFC 1035 the length of a FQDN is limited to 255 characters */
241 log_lldp("Received invalid system name length: %d. Dropping.", len
);
246 log_lldp("Duplicate system name found. Dropping.");
253 case LLDP_TYPE_SYSTEM_DESCRIPTION
:
255 /* 0 <= n <= 255 octets */
257 log_lldp("Received invalid system description length: %d. Dropping.", len
);
261 if (system_description
) {
262 log_lldp("Duplicate system description found. Dropping.");
266 system_description
= true;
271 log_lldp("TLV type: %d length 0 received. Dropping.", type
);
279 if(!chassis_id
|| !port_id
|| !ttl
|| !end
) {
280 log_lldp("One or more mandatory TLV missing. Dropping.");
286 r
= tlv_packet_parse_pdu(tlv
, length
);
288 log_lldp("Failed to parse the TLV. Dropping.");
293 return lldp_receive_frame(lldp
, tlv
);
297 sd_lldp_packet_unref(tlv
);
302 static int ttl_expiry_item_prioq_compare_func(const void *a
, const void *b
) {
303 const lldp_neighbour_port
*p
= a
, *q
= b
;
305 if (p
->until
< q
->until
)
308 if (p
->until
> q
->until
)
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. */
319 static void lldp_mib_delete_objects(sd_lldp
*lldp
) {
320 lldp_neighbour_port
*p
;
323 /* Remove all entries that are past their TTL */
326 if (prioq_size(lldp
->by_expiry
) <= 0)
329 p
= prioq_peek(lldp
->by_expiry
);
334 t
= now(clock_boottime_or_monotonic());
339 lldp_neighbour_port_remove_and_free(p
);
343 static void lldp_mib_objects_flush(sd_lldp
*lldp
) {
344 lldp_neighbour_port
*p
, *q
;
348 assert(lldp
->neighbour_mib
);
349 assert(lldp
->by_expiry
);
351 /* Drop all packets */
352 while ((c
= hashmap_steal_first(lldp
->neighbour_mib
))) {
354 LIST_FOREACH_SAFE(port
, p
, q
, c
->ports
) {
355 lldp_neighbour_port_remove_and_free(p
);
359 assert(hashmap_size(lldp
->neighbour_mib
) == 0);
360 assert(prioq_size(lldp
->by_expiry
) == 0);
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;
378 r
= fopen_temporary(lldp_file
, &f
, &temp_path
);
382 fchmod(fileno(f
), 0644);
384 HASHMAP_FOREACH(c
, lldp
->neighbour_mib
, i
) {
385 LIST_FOREACH(port
, p
, c
->ports
) {
386 _cleanup_free_
char *s
= NULL
;
389 r
= sd_lldp_packet_read_chassis_id(p
->packet
, &type
, &mac
, &length
);
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
);
402 r
= sd_lldp_packet_read_port_id(p
->packet
, &type
, &port_id
, &length
);
406 if (type
!= LLDP_PORT_SUBTYPE_MAC_ADDRESS
) {
407 k
= strndup((char *) port_id
, length
-1);
413 sprintf(buf
, "'_Port=%s' '_PType=%d' ", k
, type
);
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
);
421 k
= strappend(s
, buf
);
430 time
= now(clock_boottime_or_monotonic());
432 /* Don't write expired packets */
433 if (time
- p
->until
<= 0)
436 sprintf(buf
, "'_TTL="USEC_FMT
"' ", p
->until
);
438 k
= strappend(s
, buf
);
447 r
= sd_lldp_packet_read_system_name(p
->packet
, &k
, &length
);
449 k
= strappend(s
, "'_NAME=N/A' ");
451 t
= strndup(k
, length
);
457 k
= strjoin(s
, "'_NAME=", t
, "' ", NULL
);
469 (void) sd_lldp_packet_read_system_capability(p
->packet
, &data
);
471 sprintf(buf
, "'_CAP=%x'", data
);
473 k
= strappend(s
, buf
);
482 fprintf(f
, "%s\n", s
);
486 r
= fflush_and_check(f
);
490 if (rename(temp_path
, lldp_file
) < 0) {
499 (void) unlink(temp_path
);
501 return log_error_errno(r
, "Failed to save lldp data %s: %m", lldp_file
);
504 int sd_lldp_start(sd_lldp
*lldp
) {
507 assert_return(lldp
, -EINVAL
);
512 assert(!lldp
->event_source
);
514 lldp
->fd
= lldp_network_bind_raw_socket(lldp
->ifindex
);
519 r
= sd_event_add_io(lldp
->event
, &lldp
->event_source
, lldp
->fd
, EPOLLIN
, lldp_receive_packet
, lldp
);
523 r
= sd_event_source_set_priority(lldp
->event_source
, lldp
->event_priority
);
527 (void) sd_event_source_set_description(lldp
->event_source
, "lldp");
533 lldp
->event_source
= sd_event_source_unref(lldp
->event_source
);
534 lldp
->fd
= safe_close(lldp
->fd
);
539 int sd_lldp_stop(sd_lldp
*lldp
) {
540 assert_return(lldp
, -EINVAL
);
545 lldp
->event_source
= sd_event_source_unref(lldp
->event_source
);
546 lldp
->fd
= safe_close(lldp
->fd
);
548 lldp_mib_objects_flush(lldp
);
553 int sd_lldp_attach_event(sd_lldp
*lldp
, sd_event
*event
, int64_t priority
) {
556 assert_return(lldp
, -EINVAL
);
557 assert_return(lldp
->fd
< 0, -EBUSY
);
558 assert_return(!lldp
->event
, -EBUSY
);
561 lldp
->event
= sd_event_ref(event
);
563 r
= sd_event_default(&lldp
->event
);
568 lldp
->event_priority
= priority
;
573 int sd_lldp_detach_event(sd_lldp
*lldp
) {
575 assert_return(lldp
, -EINVAL
);
576 assert_return(lldp
->fd
< 0, -EBUSY
);
578 lldp
->event
= sd_event_unref(lldp
->event
);
583 int sd_lldp_set_callback(sd_lldp
*lldp
, sd_lldp_callback_t cb
, void *userdata
) {
584 assert_return(lldp
, -EINVAL
);
587 lldp
->userdata
= userdata
;
592 sd_lldp
* sd_lldp_unref(sd_lldp
*lldp
) {
597 /* Drop all packets */
598 lldp_mib_objects_flush(lldp
);
600 hashmap_free(lldp
->neighbour_mib
);
601 prioq_free(lldp
->by_expiry
);
603 sd_event_source_unref(lldp
->event_source
);
604 sd_event_unref(lldp
->event
);
605 safe_close(lldp
->fd
);
611 int sd_lldp_new(int ifindex
, sd_lldp
**ret
) {
612 _cleanup_(sd_lldp_unrefp
) sd_lldp
*lldp
= NULL
;
615 assert_return(ret
, -EINVAL
);
616 assert_return(ifindex
> 0, -EINVAL
);
618 lldp
= new0(sd_lldp
, 1);
623 lldp
->ifindex
= ifindex
;
625 lldp
->neighbour_mib
= hashmap_new(&chassis_id_hash_ops
);
626 if (!lldp
->neighbour_mib
)
629 r
= prioq_ensure_allocated(&lldp
->by_expiry
, ttl_expiry_item_prioq_compare_func
);
639 int sd_lldp_get_packets(sd_lldp
*lldp
, sd_lldp_packet
***tlvs
) {
640 lldp_neighbour_port
*p
;
643 unsigned count
= 0, i
;
645 assert_return(lldp
, -EINVAL
);
646 assert_return(tlvs
, -EINVAL
);
648 HASHMAP_FOREACH(c
, lldp
->neighbour_mib
, iter
) {
649 LIST_FOREACH(port
, p
, c
->ports
)
658 *tlvs
= new(sd_lldp_packet
*, count
);
663 HASHMAP_FOREACH(c
, lldp
->neighbour_mib
, iter
) {
664 LIST_FOREACH(port
, p
, c
->ports
)
665 (*tlvs
)[i
++] = sd_lldp_packet_ref(p
->packet
);