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-port.h"
33 #include "siphash24.h"
34 #include "string-util.h"
36 /* Section 10.5.2.2 Reception counters */
37 struct lldp_agent_statistics
{
38 uint64_t stats_ageouts_total
;
39 uint64_t stats_frames_discarded_total
;
40 uint64_t stats_frames_in_errors_total
;
41 uint64_t stats_frames_in_total
;
42 uint64_t stats_tlvs_discarded_total
;
43 uint64_t stats_tlvs_unrecognized_total
;
50 Hashmap
*neighbour_mib
;
56 lldp_agent_statistics statistics
;
59 static void chassis_id_hash_func(const void *p
, struct siphash
*state
) {
60 const lldp_chassis_id
*id
= p
;
65 siphash24_compress(&id
->length
, sizeof(id
->length
), state
);
66 siphash24_compress(id
->data
, id
->length
, state
);
69 static int chassis_id_compare_func(const void *_a
, const void *_b
) {
70 const lldp_chassis_id
*a
, *b
;
75 assert(!a
->length
|| a
->data
);
76 assert(!b
->length
|| b
->data
);
78 if (a
->type
!= b
->type
)
81 if (a
->length
!= b
->length
)
82 return a
->length
< b
->length
? -1 : 1;
84 return memcmp(a
->data
, b
->data
, a
->length
);
87 static const struct hash_ops chassis_id_hash_ops
= {
88 .hash
= chassis_id_hash_func
,
89 .compare
= chassis_id_compare_func
92 static void lldp_mib_delete_objects(sd_lldp
*lldp
);
94 static int lldp_receive_frame(sd_lldp
*lldp
, tlv_packet
*tlv
) {
100 /* Remove expired packets */
101 if (prioq_size(lldp
->by_expiry
) > 0)
102 lldp_mib_delete_objects(lldp
);
104 r
= lldp_mib_add_objects(lldp
->by_expiry
, lldp
->neighbour_mib
, tlv
);
109 lldp
->cb(lldp
, SD_LLDP_EVENT_UPDATE_INFO
, lldp
->userdata
);
111 log_lldp("Packet added. MIB size: %d , PQ size: %d",
112 hashmap_size(lldp
->neighbour_mib
),
113 prioq_size(lldp
->by_expiry
));
115 lldp
->statistics
.stats_frames_in_total
++;
119 log_lldp("Receive frame failed: %s", strerror(-r
));
124 /* 10.3.2 LLDPDU validation: rxProcessFrame() */
125 int lldp_handle_packet(tlv_packet
*tlv
, uint16_t length
) {
126 bool system_description
= false, system_name
= false, chassis_id
= false;
127 bool malformed
= false, port_id
= false, ttl
= false, end
= false;
128 uint16_t type
, len
, i
, l
, t
;
137 port
= (lldp_port
*) tlv
->userdata
;
138 lldp
= (sd_lldp
*) port
->userdata
;
140 if (lldp
->port
->status
== LLDP_PORT_STATUS_DISABLED
) {
141 log_lldp("Port: %s is disabled. Dropping.", lldp
->port
->ifname
);
146 p
+= sizeof(struct ether_header
);
148 for (i
= 1, l
= 0; l
<= length
; i
++) {
150 memcpy(&t
, p
, sizeof(uint16_t));
152 type
= ntohs(t
) >> 9;
153 len
= ntohs(t
) & 0x01ff;
155 if (type
== LLDP_TYPE_END
) {
157 log_lldp("TLV type end must be length 0 (not %d). Dropping.", len
);
166 } else if (type
>=_LLDP_TYPE_MAX
) {
167 log_lldp("TLV type: %d not recognized. Dropping.", type
);
173 /* skip type and length encoding */
182 log_lldp("TLV missing or out of order. Dropping.");
190 case LLDP_TYPE_CHASSIS_ID
:
193 log_lldp("Received malformed Chassis ID TLV length: %d. Dropping.", len
);
200 log_lldp("Duplicate Chassis ID TLV found. Dropping.");
206 /* Look what subtype it has */
207 if (*q
== LLDP_CHASSIS_SUBTYPE_RESERVED
|| *q
> LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED
) {
208 log_lldp("Unknown subtype: %d found in Chassis ID TLV. Dropping.", *q
);
218 case LLDP_TYPE_PORT_ID
:
221 log_lldp("Received malformed Port ID TLV length: %d. Dropping.", len
);
228 log_lldp("Duplicate Port ID TLV found. Dropping.");
234 /* Look what subtype it has */
235 if (*q
== LLDP_PORT_SUBTYPE_RESERVED
|| *q
> LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED
) {
236 log_lldp("Unknown subtype: %d found in Port ID TLV. Dropping.", *q
);
249 log_lldp("Received invalid TTL TLV lenth: %d. Dropping.", len
);
256 log_lldp("Duplicate TTL TLV found. Dropping.");
265 case LLDP_TYPE_SYSTEM_NAME
:
267 /* According to RFC 1035 the length of a FQDN is limited to 255 characters */
269 log_lldp("Received invalid system name length: %d. Dropping.", len
);
275 log_lldp("Duplicate system name found. Dropping.");
283 case LLDP_TYPE_SYSTEM_DESCRIPTION
:
285 /* 0 <= n <= 255 octets */
287 log_lldp("Received invalid system description length: %d. Dropping.", len
);
292 if (system_description
) {
293 log_lldp("Duplicate system description found. Dropping.");
298 system_description
= true;
303 log_lldp("TLV type: %d length 0 received. Dropping.", type
);
312 if(!chassis_id
|| !port_id
|| !ttl
|| !end
) {
313 log_lldp("One or more mandatory TLV missing. Dropping.");
320 r
= tlv_packet_parse_pdu(tlv
, length
);
322 log_lldp("Failed to parse the TLV. Dropping.");
328 return lldp_receive_frame(lldp
, tlv
);
332 lldp
->statistics
.stats_frames_discarded_total
++;
333 lldp
->statistics
.stats_frames_in_errors_total
++;
336 sd_lldp_packet_unref(tlv
);
341 static int ttl_expiry_item_prioq_compare_func(const void *a
, const void *b
) {
342 const lldp_neighbour_port
*p
= a
, *q
= b
;
344 if (p
->until
< q
->until
)
347 if (p
->until
> q
->until
)
353 /* 10.5.5.2.1 mibDeleteObjects ()
354 * The mibDeleteObjects () procedure deletes all information in the LLDP remote
355 * systems MIB associated with the MSAP identifier if an LLDPDU is received with
356 * an rxTTL value of zero (see 10.3.2) or the timing counter rxInfoTTL expires. */
358 static void lldp_mib_delete_objects(sd_lldp
*lldp
) {
359 lldp_neighbour_port
*p
;
362 /* Remove all entries that are past their TTL */
365 if (prioq_size(lldp
->by_expiry
) <= 0)
368 p
= prioq_peek(lldp
->by_expiry
);
373 t
= now(clock_boottime_or_monotonic());
378 lldp_neighbour_port_remove_and_free(p
);
380 lldp
->statistics
.stats_ageouts_total
++;
384 static void lldp_mib_objects_flush(sd_lldp
*lldp
) {
385 lldp_neighbour_port
*p
, *q
;
389 assert(lldp
->neighbour_mib
);
390 assert(lldp
->by_expiry
);
392 /* Drop all packets */
393 while ((c
= hashmap_steal_first(lldp
->neighbour_mib
))) {
395 LIST_FOREACH_SAFE(port
, p
, q
, c
->ports
) {
396 lldp_neighbour_port_remove_and_free(p
);
400 assert(hashmap_size(lldp
->neighbour_mib
) == 0);
401 assert(prioq_size(lldp
->by_expiry
) == 0);
404 int sd_lldp_save(sd_lldp
*lldp
, const char *lldp_file
) {
405 _cleanup_free_
char *temp_path
= NULL
;
406 _cleanup_fclose_
FILE *f
= NULL
;
407 uint8_t *mac
, *port_id
, type
;
408 lldp_neighbour_port
*p
;
409 uint16_t data
= 0, length
= 0;
419 r
= fopen_temporary(lldp_file
, &f
, &temp_path
);
423 fchmod(fileno(f
), 0644);
425 HASHMAP_FOREACH(c
, lldp
->neighbour_mib
, i
) {
426 LIST_FOREACH(port
, p
, c
->ports
) {
427 _cleanup_free_
char *s
= NULL
;
430 r
= sd_lldp_packet_read_chassis_id(p
->packet
, &type
, &mac
, &length
);
434 sprintf(buf
, "'_Chassis=%02x:%02x:%02x:%02x:%02x:%02x' '_CType=%d' ",
435 mac
[0], mac
[1], mac
[2], mac
[3], mac
[4], mac
[5], type
);
443 r
= sd_lldp_packet_read_port_id(p
->packet
, &type
, &port_id
, &length
);
447 if (type
!= LLDP_PORT_SUBTYPE_MAC_ADDRESS
) {
448 k
= strndup((char *) port_id
, length
-1);
454 sprintf(buf
, "'_Port=%s' '_PType=%d' ", k
, type
);
458 sprintf(buf
, "'_Port=%02x:%02x:%02x:%02x:%02x:%02x' '_PType=%d' ",
459 mac
[0], mac
[1], mac
[2], mac
[3], mac
[4], mac
[5], type
);
462 k
= strappend(s
, buf
);
471 time
= now(clock_boottime_or_monotonic());
473 /* Don't write expired packets */
474 if (time
- p
->until
<= 0)
477 sprintf(buf
, "'_TTL="USEC_FMT
"' ", p
->until
);
479 k
= strappend(s
, buf
);
488 r
= sd_lldp_packet_read_system_name(p
->packet
, &k
, &length
);
490 k
= strappend(s
, "'_NAME=N/A' ");
492 t
= strndup(k
, length
);
498 k
= strjoin(s
, "'_NAME=", t
, "' ", NULL
);
510 (void) sd_lldp_packet_read_system_capability(p
->packet
, &data
);
512 sprintf(buf
, "'_CAP=%x'", data
);
514 k
= strappend(s
, buf
);
523 fprintf(f
, "%s\n", s
);
527 r
= fflush_and_check(f
);
531 if (rename(temp_path
, lldp_file
) < 0) {
540 (void) unlink(temp_path
);
542 return log_error_errno(r
, "Failed to save lldp data %s: %m", lldp_file
);
545 int sd_lldp_start(sd_lldp
*lldp
) {
548 assert_return(lldp
, -EINVAL
);
549 assert_return(lldp
->port
, -EINVAL
);
551 lldp
->port
->status
= LLDP_PORT_STATUS_ENABLED
;
553 r
= lldp_port_start(lldp
->port
);
555 log_lldp("Failed to start Port : %s , %s",
565 int sd_lldp_stop(sd_lldp
*lldp
) {
568 assert_return(lldp
, -EINVAL
);
569 assert_return(lldp
->port
, -EINVAL
);
571 lldp
->port
->status
= LLDP_PORT_STATUS_DISABLED
;
573 r
= lldp_port_stop(lldp
->port
);
577 lldp_mib_objects_flush(lldp
);
582 int sd_lldp_attach_event(sd_lldp
*lldp
, sd_event
*event
, int priority
) {
585 assert_return(lldp
, -EINVAL
);
586 assert_return(!lldp
->port
->event
, -EBUSY
);
589 lldp
->port
->event
= sd_event_ref(event
);
591 r
= sd_event_default(&lldp
->port
->event
);
596 lldp
->port
->event_priority
= priority
;
601 int sd_lldp_detach_event(sd_lldp
*lldp
) {
603 assert_return(lldp
, -EINVAL
);
605 lldp
->port
->event
= sd_event_unref(lldp
->port
->event
);
610 int sd_lldp_set_callback(sd_lldp
*lldp
, sd_lldp_cb_t cb
, void *userdata
) {
611 assert_return(lldp
, -EINVAL
);
614 lldp
->userdata
= userdata
;
619 sd_lldp
* sd_lldp_unref(sd_lldp
*lldp
) {
624 /* Drop all packets */
625 lldp_mib_objects_flush(lldp
);
627 lldp_port_free(lldp
->port
);
629 hashmap_free(lldp
->neighbour_mib
);
630 prioq_free(lldp
->by_expiry
);
636 int sd_lldp_new(int ifindex
,
638 const struct ether_addr
*mac
,
640 _cleanup_(sd_lldp_unrefp
) sd_lldp
*lldp
= NULL
;
643 assert_return(ret
, -EINVAL
);
644 assert_return(ifindex
> 0, -EINVAL
);
645 assert_return(ifname
, -EINVAL
);
646 assert_return(mac
, -EINVAL
);
648 lldp
= new0(sd_lldp
, 1);
652 r
= lldp_port_new(ifindex
, ifname
, mac
, lldp
, &lldp
->port
);
656 lldp
->neighbour_mib
= hashmap_new(&chassis_id_hash_ops
);
657 if (!lldp
->neighbour_mib
)
660 r
= prioq_ensure_allocated(&lldp
->by_expiry
,
661 ttl_expiry_item_prioq_compare_func
);
671 int sd_lldp_get_packets(sd_lldp
*lldp
, sd_lldp_packet
***tlvs
) {
672 lldp_neighbour_port
*p
;
675 unsigned count
= 0, i
;
677 assert_return(lldp
, -EINVAL
);
678 assert_return(tlvs
, -EINVAL
);
680 HASHMAP_FOREACH(c
, lldp
->neighbour_mib
, iter
) {
681 LIST_FOREACH(port
, p
, c
->ports
)
690 *tlvs
= new(sd_lldp_packet
*, count
);
695 HASHMAP_FOREACH(c
, lldp
->neighbour_mib
, iter
) {
696 LIST_FOREACH(port
, p
, c
->ports
)
697 (*tlvs
)[i
++] = sd_lldp_packet_ref(p
->packet
);