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 typedef enum LLDPAgentRXState
{
37 LLDP_AGENT_RX_WAIT_PORT_OPERATIONAL
= 4,
38 LLDP_AGENT_RX_DELETE_AGED_INFO
,
39 LLDP_AGENT_RX_LLDP_INITIALIZE
,
40 LLDP_AGENT_RX_WAIT_FOR_FRAME
,
41 LLDP_AGENT_RX_RX_FRAME
,
42 LLDP_AGENT_RX_DELETE_INFO
,
43 LLDP_AGENT_RX_UPDATE_INFO
,
44 _LLDP_AGENT_RX_STATE_MAX
,
45 _LLDP_AGENT_RX_INVALID
= -1,
48 /* Section 10.5.2.2 Reception counters */
49 struct lldp_agent_statistics
{
50 uint64_t stats_ageouts_total
;
51 uint64_t stats_frames_discarded_total
;
52 uint64_t stats_frames_in_errors_total
;
53 uint64_t stats_frames_in_total
;
54 uint64_t stats_tlvs_discarded_total
;
55 uint64_t stats_tlvs_unrecognized_total
;
62 Hashmap
*neighbour_mib
;
68 LLDPAgentRXState rx_state
;
69 lldp_agent_statistics statistics
;
72 static void chassis_id_hash_func(const void *p
, struct siphash
*state
) {
73 const lldp_chassis_id
*id
= p
;
78 siphash24_compress(&id
->length
, sizeof(id
->length
), state
);
79 siphash24_compress(id
->data
, id
->length
, state
);
82 static int chassis_id_compare_func(const void *_a
, const void *_b
) {
83 const lldp_chassis_id
*a
, *b
;
88 assert(!a
->length
|| a
->data
);
89 assert(!b
->length
|| b
->data
);
91 if (a
->type
!= b
->type
)
94 if (a
->length
!= b
->length
)
95 return a
->length
< b
->length
? -1 : 1;
97 return memcmp(a
->data
, b
->data
, a
->length
);
100 static const struct hash_ops chassis_id_hash_ops
= {
101 .hash
= chassis_id_hash_func
,
102 .compare
= chassis_id_compare_func
105 static void lldp_mib_delete_objects(sd_lldp
*lldp
);
106 static void lldp_set_state(sd_lldp
*lldp
, LLDPAgentRXState state
);
107 static void lldp_run_state_machine(sd_lldp
*ll
);
109 static int lldp_receive_frame(sd_lldp
*lldp
, tlv_packet
*tlv
) {
115 /* Remove expired packets */
116 if (prioq_size(lldp
->by_expiry
) > 0) {
118 lldp_set_state(lldp
, LLDP_AGENT_RX_DELETE_INFO
);
120 lldp_mib_delete_objects(lldp
);
123 r
= lldp_mib_add_objects(lldp
->by_expiry
, lldp
->neighbour_mib
, tlv
);
127 lldp_set_state(lldp
, LLDP_AGENT_RX_UPDATE_INFO
);
129 log_lldp("Packet added. MIB size: %d , PQ size: %d",
130 hashmap_size(lldp
->neighbour_mib
),
131 prioq_size(lldp
->by_expiry
));
133 lldp
->statistics
.stats_frames_in_total
++;
137 log_lldp("Receive frame failed: %s", strerror(-r
));
139 lldp_set_state(lldp
, LLDP_AGENT_RX_WAIT_FOR_FRAME
);
144 /* 10.3.2 LLDPDU validation: rxProcessFrame() */
145 int lldp_handle_packet(tlv_packet
*tlv
, uint16_t length
) {
146 bool system_description
= false, system_name
= false, chassis_id
= false;
147 bool malformed
= false, port_id
= false, ttl
= false, end
= false;
148 uint16_t type
, len
, i
, l
, t
;
157 port
= (lldp_port
*) tlv
->userdata
;
158 lldp
= (sd_lldp
*) port
->userdata
;
160 if (lldp
->port
->status
== LLDP_PORT_STATUS_DISABLED
) {
161 log_lldp("Port: %s is disabled. Dropping.", lldp
->port
->ifname
);
165 lldp_set_state(lldp
, LLDP_AGENT_RX_RX_FRAME
);
168 p
+= sizeof(struct ether_header
);
170 for (i
= 1, l
= 0; l
<= length
; i
++) {
172 memcpy(&t
, p
, sizeof(uint16_t));
174 type
= ntohs(t
) >> 9;
175 len
= ntohs(t
) & 0x01ff;
177 if (type
== LLDP_TYPE_END
) {
179 log_lldp("TLV type end must be length 0 (not %d). Dropping.", len
);
188 } else if (type
>=_LLDP_TYPE_MAX
) {
189 log_lldp("TLV type: %d not recognized. Dropping.", type
);
195 /* skip type and length encoding */
204 log_lldp("TLV missing or out of order. Dropping.");
212 case LLDP_TYPE_CHASSIS_ID
:
215 log_lldp("Received malformed Chassis ID TLV length: %d. Dropping.", len
);
222 log_lldp("Duplicate Chassis ID TLV found. Dropping.");
228 /* Look what subtype it has */
229 if (*q
== LLDP_CHASSIS_SUBTYPE_RESERVED
|| *q
> LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED
) {
230 log_lldp("Unknown subtype: %d found in Chassis ID TLV. Dropping.", *q
);
240 case LLDP_TYPE_PORT_ID
:
243 log_lldp("Received malformed Port ID TLV length: %d. Dropping.", len
);
250 log_lldp("Duplicate Port ID TLV found. Dropping.");
256 /* Look what subtype it has */
257 if (*q
== LLDP_PORT_SUBTYPE_RESERVED
|| *q
> LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED
) {
258 log_lldp("Unknown subtype: %d found in Port ID TLV. Dropping.", *q
);
271 log_lldp("Received invalid TTL TLV lenth: %d. Dropping.", len
);
278 log_lldp("Duplicate TTL TLV found. Dropping.");
287 case LLDP_TYPE_SYSTEM_NAME
:
289 /* According to RFC 1035 the length of a FQDN is limited to 255 characters */
291 log_lldp("Received invalid system name length: %d. Dropping.", len
);
297 log_lldp("Duplicate system name found. Dropping.");
305 case LLDP_TYPE_SYSTEM_DESCRIPTION
:
307 /* 0 <= n <= 255 octets */
309 log_lldp("Received invalid system description length: %d. Dropping.", len
);
314 if (system_description
) {
315 log_lldp("Duplicate system description found. Dropping.");
320 system_description
= true;
325 log_lldp("TLV type: %d length 0 received. Dropping.", type
);
334 if(!chassis_id
|| !port_id
|| !ttl
|| !end
) {
335 log_lldp("One or more mandatory TLV missing. Dropping.");
342 r
= tlv_packet_parse_pdu(tlv
, length
);
344 log_lldp("Failed to parse the TLV. Dropping.");
350 return lldp_receive_frame(lldp
, tlv
);
353 lldp_set_state(lldp
, LLDP_AGENT_RX_WAIT_FOR_FRAME
);
356 lldp
->statistics
.stats_frames_discarded_total
++;
357 lldp
->statistics
.stats_frames_in_errors_total
++;
360 sd_lldp_packet_unref(tlv
);
365 static int ttl_expiry_item_prioq_compare_func(const void *a
, const void *b
) {
366 const lldp_neighbour_port
*p
= a
, *q
= b
;
368 if (p
->until
< q
->until
)
371 if (p
->until
> q
->until
)
377 static void lldp_set_state(sd_lldp
*lldp
, LLDPAgentRXState state
) {
380 assert(state
< _LLDP_AGENT_RX_STATE_MAX
);
382 lldp
->rx_state
= state
;
384 lldp_run_state_machine(lldp
);
387 static void lldp_run_state_machine(sd_lldp
*lldp
) {
391 switch (lldp
->rx_state
) {
392 case LLDP_AGENT_RX_UPDATE_INFO
:
393 lldp
->cb(lldp
, SD_LLDP_EVENT_UPDATE_INFO
, lldp
->userdata
);
400 /* 10.5.5.2.1 mibDeleteObjects ()
401 * The mibDeleteObjects () procedure deletes all information in the LLDP remote
402 * systems MIB associated with the MSAP identifier if an LLDPDU is received with
403 * an rxTTL value of zero (see 10.3.2) or the timing counter rxInfoTTL expires. */
405 static void lldp_mib_delete_objects(sd_lldp
*lldp
) {
406 lldp_neighbour_port
*p
;
409 /* Remove all entries that are past their TTL */
412 if (prioq_size(lldp
->by_expiry
) <= 0)
415 p
= prioq_peek(lldp
->by_expiry
);
420 t
= now(clock_boottime_or_monotonic());
425 lldp_neighbour_port_remove_and_free(p
);
427 lldp
->statistics
.stats_ageouts_total
++;
431 static void lldp_mib_objects_flush(sd_lldp
*lldp
) {
432 lldp_neighbour_port
*p
, *q
;
436 assert(lldp
->neighbour_mib
);
437 assert(lldp
->by_expiry
);
439 /* Drop all packets */
440 while ((c
= hashmap_steal_first(lldp
->neighbour_mib
))) {
442 LIST_FOREACH_SAFE(port
, p
, q
, c
->ports
) {
443 lldp_neighbour_port_remove_and_free(p
);
447 assert(hashmap_size(lldp
->neighbour_mib
) == 0);
448 assert(prioq_size(lldp
->by_expiry
) == 0);
451 int sd_lldp_save(sd_lldp
*lldp
, const char *lldp_file
) {
452 _cleanup_free_
char *temp_path
= NULL
;
453 _cleanup_fclose_
FILE *f
= NULL
;
454 uint8_t *mac
, *port_id
, type
;
455 lldp_neighbour_port
*p
;
456 uint16_t data
= 0, length
= 0;
466 r
= fopen_temporary(lldp_file
, &f
, &temp_path
);
470 fchmod(fileno(f
), 0644);
472 HASHMAP_FOREACH(c
, lldp
->neighbour_mib
, i
) {
473 LIST_FOREACH(port
, p
, c
->ports
) {
474 _cleanup_free_
char *s
= NULL
;
477 r
= sd_lldp_packet_read_chassis_id(p
->packet
, &type
, &mac
, &length
);
481 sprintf(buf
, "'_Chassis=%02x:%02x:%02x:%02x:%02x:%02x' '_CType=%d' ",
482 mac
[0], mac
[1], mac
[2], mac
[3], mac
[4], mac
[5], type
);
490 r
= sd_lldp_packet_read_port_id(p
->packet
, &type
, &port_id
, &length
);
494 if (type
!= LLDP_PORT_SUBTYPE_MAC_ADDRESS
) {
495 k
= strndup((char *) port_id
, length
-1);
501 sprintf(buf
, "'_Port=%s' '_PType=%d' ", k
, type
);
505 sprintf(buf
, "'_Port=%02x:%02x:%02x:%02x:%02x:%02x' '_PType=%d' ",
506 mac
[0], mac
[1], mac
[2], mac
[3], mac
[4], mac
[5], type
);
509 k
= strappend(s
, buf
);
518 time
= now(clock_boottime_or_monotonic());
520 /* Don't write expired packets */
521 if (time
- p
->until
<= 0)
524 sprintf(buf
, "'_TTL="USEC_FMT
"' ", p
->until
);
526 k
= strappend(s
, buf
);
535 r
= sd_lldp_packet_read_system_name(p
->packet
, &k
, &length
);
537 k
= strappend(s
, "'_NAME=N/A' ");
539 t
= strndup(k
, length
);
545 k
= strjoin(s
, "'_NAME=", t
, "' ", NULL
);
557 (void) sd_lldp_packet_read_system_capability(p
->packet
, &data
);
559 sprintf(buf
, "'_CAP=%x'", data
);
561 k
= strappend(s
, buf
);
570 fprintf(f
, "%s\n", s
);
574 r
= fflush_and_check(f
);
578 if (rename(temp_path
, lldp_file
) < 0) {
587 (void) unlink(temp_path
);
589 return log_error_errno(r
, "Failed to save lldp data %s: %m", lldp_file
);
592 int sd_lldp_start(sd_lldp
*lldp
) {
595 assert_return(lldp
, -EINVAL
);
596 assert_return(lldp
->port
, -EINVAL
);
598 lldp
->port
->status
= LLDP_PORT_STATUS_ENABLED
;
600 lldp_set_state(lldp
, LLDP_AGENT_RX_LLDP_INITIALIZE
);
602 r
= lldp_port_start(lldp
->port
);
604 log_lldp("Failed to start Port : %s , %s",
608 lldp_set_state(lldp
, LLDP_AGENT_RX_WAIT_PORT_OPERATIONAL
);
613 lldp_set_state(lldp
, LLDP_AGENT_RX_WAIT_FOR_FRAME
);
618 int sd_lldp_stop(sd_lldp
*lldp
) {
621 assert_return(lldp
, -EINVAL
);
622 assert_return(lldp
->port
, -EINVAL
);
624 lldp
->port
->status
= LLDP_PORT_STATUS_DISABLED
;
626 r
= lldp_port_stop(lldp
->port
);
630 lldp_mib_objects_flush(lldp
);
635 int sd_lldp_attach_event(sd_lldp
*lldp
, sd_event
*event
, int priority
) {
638 assert_return(lldp
, -EINVAL
);
639 assert_return(!lldp
->port
->event
, -EBUSY
);
642 lldp
->port
->event
= sd_event_ref(event
);
644 r
= sd_event_default(&lldp
->port
->event
);
649 lldp
->port
->event_priority
= priority
;
654 int sd_lldp_detach_event(sd_lldp
*lldp
) {
656 assert_return(lldp
, -EINVAL
);
658 lldp
->port
->event
= sd_event_unref(lldp
->port
->event
);
663 int sd_lldp_set_callback(sd_lldp
*lldp
, sd_lldp_cb_t cb
, void *userdata
) {
664 assert_return(lldp
, -EINVAL
);
667 lldp
->userdata
= userdata
;
672 sd_lldp
* sd_lldp_unref(sd_lldp
*lldp
) {
677 /* Drop all packets */
678 lldp_mib_objects_flush(lldp
);
680 lldp_port_free(lldp
->port
);
682 hashmap_free(lldp
->neighbour_mib
);
683 prioq_free(lldp
->by_expiry
);
689 int sd_lldp_new(int ifindex
,
691 const struct ether_addr
*mac
,
693 _cleanup_(sd_lldp_unrefp
) sd_lldp
*lldp
= NULL
;
696 assert_return(ret
, -EINVAL
);
697 assert_return(ifindex
> 0, -EINVAL
);
698 assert_return(ifname
, -EINVAL
);
699 assert_return(mac
, -EINVAL
);
701 lldp
= new0(sd_lldp
, 1);
705 r
= lldp_port_new(ifindex
, ifname
, mac
, lldp
, &lldp
->port
);
709 lldp
->neighbour_mib
= hashmap_new(&chassis_id_hash_ops
);
710 if (!lldp
->neighbour_mib
)
713 r
= prioq_ensure_allocated(&lldp
->by_expiry
,
714 ttl_expiry_item_prioq_compare_func
);
718 lldp
->rx_state
= LLDP_AGENT_RX_WAIT_PORT_OPERATIONAL
;
726 int sd_lldp_get_packets(sd_lldp
*lldp
, sd_lldp_packet
***tlvs
) {
727 lldp_neighbour_port
*p
;
730 unsigned count
= 0, i
;
732 assert_return(lldp
, -EINVAL
);
733 assert_return(tlvs
, -EINVAL
);
735 HASHMAP_FOREACH(c
, lldp
->neighbour_mib
, iter
) {
736 LIST_FOREACH(port
, p
, c
->ports
)
745 *tlvs
= new(sd_lldp_packet
*, count
);
750 HASHMAP_FOREACH(c
, lldp
->neighbour_mib
, iter
) {
751 LIST_FOREACH(port
, p
, c
->ports
)
752 (*tlvs
)[i
++] = sd_lldp_packet_ref(p
->packet
);