1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright (C) 2014 Tom Gundersen
7 Copyright (C) 2014 Susant Sahani
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.
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.
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/>.
23 #include <arpa/inet.h>
27 #include "alloc-util.h"
31 #include "lldp-internal.h"
32 #include "lldp-port.h"
35 #include "siphash24.h"
36 #include "string-util.h"
38 typedef enum LLDPAgentRXState
{
39 LLDP_AGENT_RX_WAIT_PORT_OPERATIONAL
= 4,
40 LLDP_AGENT_RX_DELETE_AGED_INFO
,
41 LLDP_AGENT_RX_LLDP_INITIALIZE
,
42 LLDP_AGENT_RX_WAIT_FOR_FRAME
,
43 LLDP_AGENT_RX_RX_FRAME
,
44 LLDP_AGENT_RX_DELETE_INFO
,
45 LLDP_AGENT_RX_UPDATE_INFO
,
46 _LLDP_AGENT_RX_STATE_MAX
,
47 _LLDP_AGENT_RX_INVALID
= -1,
50 /* Section 10.5.2.2 Reception counters */
51 struct lldp_agent_statistics
{
52 uint64_t stats_ageouts_total
;
53 uint64_t stats_frames_discarded_total
;
54 uint64_t stats_frames_in_errors_total
;
55 uint64_t stats_frames_in_total
;
56 uint64_t stats_tlvs_discarded_total
;
57 uint64_t stats_tlvs_unrecognized_total
;
64 Hashmap
*neighbour_mib
;
70 LLDPAgentRXState rx_state
;
71 lldp_agent_statistics statistics
;
74 static void chassis_id_hash_func(const void *p
, struct siphash
*state
) {
75 const lldp_chassis_id
*id
= p
;
80 siphash24_compress(&id
->length
, sizeof(id
->length
), state
);
81 siphash24_compress(id
->data
, id
->length
, state
);
84 static int chassis_id_compare_func(const void *_a
, const void *_b
) {
85 const lldp_chassis_id
*a
, *b
;
90 assert(!a
->length
|| a
->data
);
91 assert(!b
->length
|| b
->data
);
93 if (a
->type
!= b
->type
)
96 if (a
->length
!= b
->length
)
97 return a
->length
< b
->length
? -1 : 1;
99 return memcmp(a
->data
, b
->data
, a
->length
);
102 static const struct hash_ops chassis_id_hash_ops
= {
103 .hash
= chassis_id_hash_func
,
104 .compare
= chassis_id_compare_func
107 static void lldp_mib_delete_objects(sd_lldp
*lldp
);
108 static void lldp_set_state(sd_lldp
*lldp
, LLDPAgentRXState state
);
109 static void lldp_run_state_machine(sd_lldp
*ll
);
111 static int lldp_receive_frame(sd_lldp
*lldp
, tlv_packet
*tlv
) {
117 /* Remove expired packets */
118 if (prioq_size(lldp
->by_expiry
) > 0) {
120 lldp_set_state(lldp
, LLDP_AGENT_RX_DELETE_INFO
);
122 lldp_mib_delete_objects(lldp
);
125 r
= lldp_mib_add_objects(lldp
->by_expiry
, lldp
->neighbour_mib
, tlv
);
129 lldp_set_state(lldp
, LLDP_AGENT_RX_UPDATE_INFO
);
131 log_lldp("Packet added. MIB size: %d , PQ size: %d",
132 hashmap_size(lldp
->neighbour_mib
),
133 prioq_size(lldp
->by_expiry
));
135 lldp
->statistics
.stats_frames_in_total
++;
139 log_lldp("Receive frame failed: %s", strerror(-r
));
141 lldp_set_state(lldp
, LLDP_AGENT_RX_WAIT_FOR_FRAME
);
146 /* 10.3.2 LLDPDU validation: rxProcessFrame() */
147 int lldp_handle_packet(tlv_packet
*tlv
, uint16_t length
) {
148 bool system_description
= false, system_name
= false, chassis_id
= false;
149 bool malformed
= false, port_id
= false, ttl
= false, end
= false;
150 uint16_t type
, len
, i
, l
, t
;
159 port
= (lldp_port
*) tlv
->userdata
;
160 lldp
= (sd_lldp
*) port
->userdata
;
162 if (lldp
->port
->status
== LLDP_PORT_STATUS_DISABLED
) {
163 log_lldp("Port: %s is disabled. Dropping.", lldp
->port
->ifname
);
167 lldp_set_state(lldp
, LLDP_AGENT_RX_RX_FRAME
);
170 p
+= sizeof(struct ether_header
);
172 for (i
= 1, l
= 0; l
<= length
; i
++) {
174 memcpy(&t
, p
, sizeof(uint16_t));
176 type
= ntohs(t
) >> 9;
177 len
= ntohs(t
) & 0x01ff;
179 if (type
== LLDP_TYPE_END
) {
181 log_lldp("TLV type end must be length 0 (not %d). Dropping.", len
);
190 } else if (type
>=_LLDP_TYPE_MAX
) {
191 log_lldp("TLV type: %d not recognized. Dropping.", type
);
197 /* skip type and length encoding */
206 log_lldp("TLV missing or out of order. Dropping.");
214 case LLDP_TYPE_CHASSIS_ID
:
217 log_lldp("Received malformed Chassis ID TLV length: %d. Dropping.", len
);
224 log_lldp("Duplicate Chassis ID TLV found. Dropping.");
230 /* Look what subtype it has */
231 if (*q
== LLDP_CHASSIS_SUBTYPE_RESERVED
|| *q
> LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED
) {
232 log_lldp("Unknown subtype: %d found in Chassis ID TLV. Dropping.", *q
);
242 case LLDP_TYPE_PORT_ID
:
245 log_lldp("Received malformed Port ID TLV length: %d. Dropping.", len
);
252 log_lldp("Duplicate Port ID TLV found. Dropping.");
258 /* Look what subtype it has */
259 if (*q
== LLDP_PORT_SUBTYPE_RESERVED
|| *q
> LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED
) {
260 log_lldp("Unknown subtype: %d found in Port ID TLV. Dropping.", *q
);
273 log_lldp("Received invalid TTL TLV lenth: %d. Dropping.", len
);
280 log_lldp("Duplicate TTL TLV found. Dropping.");
289 case LLDP_TYPE_SYSTEM_NAME
:
291 /* According to RFC 1035 the length of a FQDN is limited to 255 characters */
293 log_lldp("Received invalid system name length: %d. Dropping.", len
);
299 log_lldp("Duplicate system name found. Dropping.");
307 case LLDP_TYPE_SYSTEM_DESCRIPTION
:
309 /* 0 <= n <= 255 octets */
311 log_lldp("Received invalid system description length: %d. Dropping.", len
);
316 if (system_description
) {
317 log_lldp("Duplicate system description found. Dropping.");
322 system_description
= true;
327 log_lldp("TLV type: %d length 0 received. Dropping.", type
);
336 if(!chassis_id
|| !port_id
|| !ttl
|| !end
) {
337 log_lldp("One or more mandatory TLV missing. Dropping.");
344 r
= tlv_packet_parse_pdu(tlv
, length
);
346 log_lldp("Failed to parse the TLV. Dropping.");
352 return lldp_receive_frame(lldp
, tlv
);
355 lldp_set_state(lldp
, LLDP_AGENT_RX_WAIT_FOR_FRAME
);
358 lldp
->statistics
.stats_frames_discarded_total
++;
359 lldp
->statistics
.stats_frames_in_errors_total
++;
362 sd_lldp_packet_unref(tlv
);
367 static int ttl_expiry_item_prioq_compare_func(const void *a
, const void *b
) {
368 const lldp_neighbour_port
*p
= a
, *q
= b
;
370 if (p
->until
< q
->until
)
373 if (p
->until
> q
->until
)
379 static void lldp_set_state(sd_lldp
*lldp
, LLDPAgentRXState state
) {
382 assert(state
< _LLDP_AGENT_RX_STATE_MAX
);
384 lldp
->rx_state
= state
;
386 lldp_run_state_machine(lldp
);
389 static void lldp_run_state_machine(sd_lldp
*lldp
) {
393 switch (lldp
->rx_state
) {
394 case LLDP_AGENT_RX_UPDATE_INFO
:
395 lldp
->cb(lldp
, SD_LLDP_EVENT_UPDATE_INFO
, lldp
->userdata
);
402 /* 10.5.5.2.1 mibDeleteObjects ()
403 * The mibDeleteObjects () procedure deletes all information in the LLDP remote
404 * systems MIB associated with the MSAP identifier if an LLDPDU is received with
405 * an rxTTL value of zero (see 10.3.2) or the timing counter rxInfoTTL expires. */
407 static void lldp_mib_delete_objects(sd_lldp
*lldp
) {
408 lldp_neighbour_port
*p
;
411 /* Remove all entries that are past their TTL */
414 if (prioq_size(lldp
->by_expiry
) <= 0)
417 p
= prioq_peek(lldp
->by_expiry
);
422 t
= now(clock_boottime_or_monotonic());
427 lldp_neighbour_port_remove_and_free(p
);
429 lldp
->statistics
.stats_ageouts_total
++;
433 static void lldp_mib_objects_flush(sd_lldp
*lldp
) {
434 lldp_neighbour_port
*p
, *q
;
438 assert(lldp
->neighbour_mib
);
439 assert(lldp
->by_expiry
);
441 /* Drop all packets */
442 while ((c
= hashmap_steal_first(lldp
->neighbour_mib
))) {
444 LIST_FOREACH_SAFE(port
, p
, q
, c
->ports
) {
445 lldp_neighbour_port_remove_and_free(p
);
449 assert(hashmap_size(lldp
->neighbour_mib
) == 0);
450 assert(prioq_size(lldp
->by_expiry
) == 0);
453 int sd_lldp_save(sd_lldp
*lldp
, const char *lldp_file
) {
454 _cleanup_free_
char *temp_path
= NULL
;
455 _cleanup_fclose_
FILE *f
= NULL
;
456 uint8_t *mac
, *port_id
, type
;
457 lldp_neighbour_port
*p
;
458 uint16_t data
= 0, length
= 0;
468 r
= fopen_temporary(lldp_file
, &f
, &temp_path
);
472 fchmod(fileno(f
), 0644);
474 HASHMAP_FOREACH(c
, lldp
->neighbour_mib
, i
) {
475 LIST_FOREACH(port
, p
, c
->ports
) {
476 _cleanup_free_
char *s
= NULL
;
479 r
= sd_lldp_packet_read_chassis_id(p
->packet
, &type
, &mac
, &length
);
483 sprintf(buf
, "'_Chassis=%02x:%02x:%02x:%02x:%02x:%02x' '_CType=%d' ",
484 mac
[0], mac
[1], mac
[2], mac
[3], mac
[4], mac
[5], type
);
492 r
= sd_lldp_packet_read_port_id(p
->packet
, &type
, &port_id
, &length
);
496 if (type
!= LLDP_PORT_SUBTYPE_MAC_ADDRESS
) {
497 k
= strndup((char *) port_id
, length
-1);
503 sprintf(buf
, "'_Port=%s' '_PType=%d' ", k
, type
);
507 sprintf(buf
, "'_Port=%02x:%02x:%02x:%02x:%02x:%02x' '_PType=%d' ",
508 mac
[0], mac
[1], mac
[2], mac
[3], mac
[4], mac
[5], type
);
511 k
= strappend(s
, buf
);
520 time
= now(clock_boottime_or_monotonic());
522 /* Don't write expired packets */
523 if (time
- p
->until
<= 0)
526 sprintf(buf
, "'_TTL="USEC_FMT
"' ", p
->until
);
528 k
= strappend(s
, buf
);
537 r
= sd_lldp_packet_read_system_name(p
->packet
, &k
, &length
);
539 k
= strappend(s
, "'_NAME=N/A' ");
541 t
= strndup(k
, length
);
547 k
= strjoin(s
, "'_NAME=", t
, "' ", NULL
);
559 (void) sd_lldp_packet_read_system_capability(p
->packet
, &data
);
561 sprintf(buf
, "'_CAP=%x'", data
);
563 k
= strappend(s
, buf
);
572 fprintf(f
, "%s\n", s
);
576 r
= fflush_and_check(f
);
580 if (rename(temp_path
, lldp_file
) < 0) {
589 (void) unlink(temp_path
);
591 return log_error_errno(r
, "Failed to save lldp data %s: %m", lldp_file
);
594 int sd_lldp_start(sd_lldp
*lldp
) {
597 assert_return(lldp
, -EINVAL
);
598 assert_return(lldp
->port
, -EINVAL
);
600 lldp
->port
->status
= LLDP_PORT_STATUS_ENABLED
;
602 lldp_set_state(lldp
, LLDP_AGENT_RX_LLDP_INITIALIZE
);
604 r
= lldp_port_start(lldp
->port
);
606 log_lldp("Failed to start Port : %s , %s",
610 lldp_set_state(lldp
, LLDP_AGENT_RX_WAIT_PORT_OPERATIONAL
);
615 lldp_set_state(lldp
, LLDP_AGENT_RX_WAIT_FOR_FRAME
);
620 int sd_lldp_stop(sd_lldp
*lldp
) {
623 assert_return(lldp
, -EINVAL
);
624 assert_return(lldp
->port
, -EINVAL
);
626 lldp
->port
->status
= LLDP_PORT_STATUS_DISABLED
;
628 r
= lldp_port_stop(lldp
->port
);
632 lldp_mib_objects_flush(lldp
);
637 int sd_lldp_attach_event(sd_lldp
*lldp
, sd_event
*event
, int priority
) {
640 assert_return(lldp
, -EINVAL
);
641 assert_return(!lldp
->port
->event
, -EBUSY
);
644 lldp
->port
->event
= sd_event_ref(event
);
646 r
= sd_event_default(&lldp
->port
->event
);
651 lldp
->port
->event_priority
= priority
;
656 int sd_lldp_detach_event(sd_lldp
*lldp
) {
658 assert_return(lldp
, -EINVAL
);
660 lldp
->port
->event
= sd_event_unref(lldp
->port
->event
);
665 int sd_lldp_set_callback(sd_lldp
*lldp
, sd_lldp_cb_t cb
, void *userdata
) {
666 assert_return(lldp
, -EINVAL
);
669 lldp
->userdata
= userdata
;
674 sd_lldp
* sd_lldp_unref(sd_lldp
*lldp
) {
679 /* Drop all packets */
680 lldp_mib_objects_flush(lldp
);
682 lldp_port_free(lldp
->port
);
684 hashmap_free(lldp
->neighbour_mib
);
685 prioq_free(lldp
->by_expiry
);
691 int sd_lldp_new(int ifindex
,
693 const struct ether_addr
*mac
,
695 _cleanup_(sd_lldp_unrefp
) sd_lldp
*lldp
= NULL
;
698 assert_return(ret
, -EINVAL
);
699 assert_return(ifindex
> 0, -EINVAL
);
700 assert_return(ifname
, -EINVAL
);
701 assert_return(mac
, -EINVAL
);
703 lldp
= new0(sd_lldp
, 1);
707 r
= lldp_port_new(ifindex
, ifname
, mac
, lldp
, &lldp
->port
);
711 lldp
->neighbour_mib
= hashmap_new(&chassis_id_hash_ops
);
712 if (!lldp
->neighbour_mib
)
715 r
= prioq_ensure_allocated(&lldp
->by_expiry
,
716 ttl_expiry_item_prioq_compare_func
);
720 lldp
->rx_state
= LLDP_AGENT_RX_WAIT_PORT_OPERATIONAL
;
728 int sd_lldp_get_packets(sd_lldp
*lldp
, sd_lldp_packet
***tlvs
) {
729 lldp_neighbour_port
*p
;
732 unsigned count
= 0, i
;
734 assert_return(lldp
, -EINVAL
);
735 assert_return(tlvs
, -EINVAL
);
737 HASHMAP_FOREACH(c
, lldp
->neighbour_mib
, iter
) {
738 LIST_FOREACH(port
, p
, c
->ports
)
747 *tlvs
= new(sd_lldp_packet
*, count
);
752 HASHMAP_FOREACH(c
, lldp
->neighbour_mib
, iter
) {
753 LIST_FOREACH(port
, p
, c
->ports
)
754 (*tlvs
)[i
++] = sd_lldp_packet_ref(p
->packet
);