1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
3 #include "alloc-util.h"
5 #include "ether-addr-util.h"
8 #include "in-addr-util.h"
10 #include "lldp-neighbor.h"
11 #include "lldp-rx-internal.h"
12 #include "memory-util.h"
14 #include "siphash24.h"
15 #include "unaligned.h"
17 static void lldp_neighbor_id_hash_func(const LLDPNeighborID
*id
, struct siphash
*state
) {
21 siphash24_compress_safe(id
->chassis_id
, id
->chassis_id_size
, state
);
22 siphash24_compress_typesafe(id
->chassis_id_size
, state
);
23 siphash24_compress_safe(id
->port_id
, id
->port_id_size
, state
);
24 siphash24_compress_typesafe(id
->port_id_size
, state
);
27 int lldp_neighbor_id_compare_func(const LLDPNeighborID
*x
, const LLDPNeighborID
*y
) {
31 return memcmp_nn(x
->chassis_id
, x
->chassis_id_size
, y
->chassis_id
, y
->chassis_id_size
)
32 ?: memcmp_nn(x
->port_id
, x
->port_id_size
, y
->port_id
, y
->port_id_size
);
35 DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
36 lldp_neighbor_hash_ops
,
38 lldp_neighbor_id_hash_func
,
39 lldp_neighbor_id_compare_func
,
41 lldp_neighbor_unlink
);
43 int lldp_neighbor_prioq_compare_func(const sd_lldp_neighbor
*x
, const sd_lldp_neighbor
*y
) {
47 return CMP(x
->until
, y
->until
);
50 sd_lldp_neighbor
*sd_lldp_neighbor_ref(sd_lldp_neighbor
*n
) {
54 assert(n
->n_ref
> 0 || n
->lldp_rx
);
60 static sd_lldp_neighbor
*lldp_neighbor_free(sd_lldp_neighbor
*n
) {
65 free(n
->id
.chassis_id
);
66 free(n
->port_description
);
68 free(n
->system_description
);
70 free(n
->chassis_id_as_string
);
71 free(n
->port_id_as_string
);
75 sd_lldp_neighbor
*sd_lldp_neighbor_unref(sd_lldp_neighbor
*n
) {
77 /* Drops one reference from the neighbor. Note that the object is not freed unless it is already unlinked from
78 * the sd_lldp object. */
86 if (n
->n_ref
<= 0 && !n
->lldp_rx
)
87 lldp_neighbor_free(n
);
92 sd_lldp_neighbor
*lldp_neighbor_unlink(sd_lldp_neighbor
*n
) {
94 /* Removes the neighbor object from the LLDP object, and frees it if it also has no other reference. */
102 /* Only remove the neighbor object from the hash table if it's in there, don't complain if it isn't. This is
103 * because we are used as destructor call for hashmap_clear() and thus sometimes are called to de-register
104 * ourselves from the hashtable and sometimes are called after we already are de-registered. */
106 (void) hashmap_remove_value(n
->lldp_rx
->neighbor_by_id
, &n
->id
, n
);
108 assert_se(prioq_remove(n
->lldp_rx
->neighbor_by_expiry
, n
, &n
->prioq_idx
) >= 0);
113 lldp_neighbor_free(n
);
118 sd_lldp_neighbor
*lldp_neighbor_new(size_t raw_size
) {
121 if (raw_size
> SIZE_MAX
- ALIGN(sizeof(sd_lldp_neighbor
)))
124 n
= malloc0(ALIGN(sizeof(sd_lldp_neighbor
)) + raw_size
);
128 n
->raw_size
= raw_size
;
134 static int parse_string(sd_lldp_rx
*lldp_rx
, char **s
, const void *q
, size_t n
) {
142 log_lldp_rx(lldp_rx
, "Found duplicate string, ignoring field.");
146 /* Strip trailing NULs, just to be nice */
147 while (n
> 0 && p
[n
-1] == 0)
150 if (n
<= 0) /* Ignore empty strings */
153 /* Look for inner NULs */
154 if (memchr(p
, 0, n
)) {
155 log_lldp_rx(lldp_rx
, "Found inner NUL in string, ignoring field.");
159 /* Let's escape weird chars, for security reasons */
160 k
= cescape_length(p
, n
);
162 return log_oom_debug();
164 free_and_replace(*s
, k
);
169 int lldp_neighbor_parse(sd_lldp_neighbor
*n
) {
170 struct ether_header h
;
177 if (n
->raw_size
< sizeof(struct ether_header
))
178 return log_lldp_rx_errno(n
->lldp_rx
, SYNTHETIC_ERRNO(EBADMSG
),
179 "Received truncated packet, ignoring.");
181 memcpy(&h
, LLDP_NEIGHBOR_RAW(n
), sizeof(h
));
183 if (h
.ether_type
!= htobe16(ETH_P_LLDP
))
184 return log_lldp_rx_errno(n
->lldp_rx
, SYNTHETIC_ERRNO(EBADMSG
),
185 "Received packet with wrong type, ignoring.");
187 if (h
.ether_dhost
[0] != 0x01 ||
188 h
.ether_dhost
[1] != 0x80 ||
189 h
.ether_dhost
[2] != 0xc2 ||
190 h
.ether_dhost
[3] != 0x00 ||
191 h
.ether_dhost
[4] != 0x00 ||
192 !IN_SET(h
.ether_dhost
[5], 0x00, 0x03, 0x0e))
193 return log_lldp_rx_errno(n
->lldp_rx
, SYNTHETIC_ERRNO(EBADMSG
),
194 "Received packet with wrong destination address, ignoring.");
196 memcpy(&n
->source_address
, h
.ether_shost
, sizeof(struct ether_addr
));
197 memcpy(&n
->destination_address
, h
.ether_dhost
, sizeof(struct ether_addr
));
199 p
= (const uint8_t*) LLDP_NEIGHBOR_RAW(n
) + sizeof(struct ether_header
);
200 left
= n
->raw_size
- sizeof(struct ether_header
);
207 return log_lldp_rx_errno(n
->lldp_rx
, SYNTHETIC_ERRNO(EBADMSG
),
208 "TLV lacks header, ignoring.");
211 length
= p
[1] + (((uint16_t) (p
[0] & 1)) << 8);
215 return log_lldp_rx_errno(n
->lldp_rx
, SYNTHETIC_ERRNO(EBADMSG
),
216 "TLV truncated, ignoring datagram.");
220 case SD_LLDP_TYPE_END
:
222 return log_lldp_rx_errno(n
->lldp_rx
, SYNTHETIC_ERRNO(EBADMSG
),
223 "End marker TLV not zero-sized, ignoring datagram.");
225 /* Note that after processing the SD_LLDP_TYPE_END left could still be > 0
226 * as the message may contain padding (see IEEE 802.1AB-2016, sec. 8.5.12) */
230 case SD_LLDP_TYPE_CHASSIS_ID
:
231 if (length
< 2 || length
> 256)
232 /* includes the chassis subtype, hence one extra byte */
233 return log_lldp_rx_errno(n
->lldp_rx
, SYNTHETIC_ERRNO(EBADMSG
),
234 "Chassis ID field size out of range, ignoring datagram.");
236 if (n
->id
.chassis_id
)
237 return log_lldp_rx_errno(n
->lldp_rx
, SYNTHETIC_ERRNO(EBADMSG
),
238 "Duplicate chassis ID field, ignoring datagram.");
240 n
->id
.chassis_id
= memdup(p
, length
);
241 if (!n
->id
.chassis_id
)
242 return log_oom_debug();
244 n
->id
.chassis_id_size
= length
;
247 case SD_LLDP_TYPE_PORT_ID
:
248 if (length
< 2 || length
> 256)
249 /* includes the port subtype, hence one extra byte */
250 return log_lldp_rx_errno(n
->lldp_rx
, SYNTHETIC_ERRNO(EBADMSG
),
251 "Port ID field size out of range, ignoring datagram.");
254 return log_lldp_rx_errno(n
->lldp_rx
, SYNTHETIC_ERRNO(EBADMSG
),
255 "Duplicate port ID field, ignoring datagram.");
257 n
->id
.port_id
= memdup(p
, length
);
259 return log_oom_debug();
261 n
->id
.port_id_size
= length
;
264 case SD_LLDP_TYPE_TTL
:
266 return log_lldp_rx_errno(n
->lldp_rx
, SYNTHETIC_ERRNO(EBADMSG
),
267 "TTL field has wrong size, ignoring datagram.");
270 return log_lldp_rx_errno(n
->lldp_rx
, SYNTHETIC_ERRNO(EBADMSG
),
271 "Duplicate TTL field, ignoring datagram.");
273 n
->ttl
= unaligned_read_be16(p
);
277 case SD_LLDP_TYPE_PORT_DESCRIPTION
:
278 r
= parse_string(n
->lldp_rx
, &n
->port_description
, p
, length
);
283 case SD_LLDP_TYPE_SYSTEM_NAME
:
284 r
= parse_string(n
->lldp_rx
, &n
->system_name
, p
, length
);
289 case SD_LLDP_TYPE_SYSTEM_DESCRIPTION
:
290 r
= parse_string(n
->lldp_rx
, &n
->system_description
, p
, length
);
295 case SD_LLDP_TYPE_SYSTEM_CAPABILITIES
:
297 return log_lldp_rx_errno(n
->lldp_rx
, SYNTHETIC_ERRNO(EBADMSG
),
298 "System capabilities field has wrong size.");
300 n
->system_capabilities
= unaligned_read_be16(p
);
301 n
->enabled_capabilities
= unaligned_read_be16(p
+ 2);
302 n
->has_capabilities
= true;
305 case SD_LLDP_TYPE_PRIVATE
:
307 return log_lldp_rx_errno(n
->lldp_rx
, SYNTHETIC_ERRNO(EBADMSG
),
308 "Found private TLV that is too short, ignoring.");
310 /* RFC 8520: MUD URL */
311 if (memcmp(p
, SD_LLDP_OUI_IANA_MUD
, sizeof(SD_LLDP_OUI_IANA_MUD
)) == 0) {
312 r
= parse_string(n
->lldp_rx
, &n
->mud_url
, p
+ sizeof(SD_LLDP_OUI_IANA_MUD
),
313 length
- sizeof(SD_LLDP_OUI_IANA_MUD
));
318 /* IEEE 802.1: VLAN ID */
319 if (memcmp(p
, SD_LLDP_OUI_802_1_VLAN_ID
, sizeof(SD_LLDP_OUI_802_1_VLAN_ID
)) == 0) {
320 if (length
!= (sizeof(SD_LLDP_OUI_802_1_VLAN_ID
) + sizeof(uint16_t)))
321 return log_lldp_rx_errno(n
->lldp_rx
, SYNTHETIC_ERRNO(EBADMSG
),
322 "Found 802.1 VLAN ID TLV with wrong length, ignoring.");
324 n
->has_port_vlan_id
= true;
325 n
->port_vlan_id
= unaligned_read_be16(p
+ sizeof(SD_LLDP_OUI_802_1_VLAN_ID
));
330 p
+= length
, left
-= length
;
334 if (!n
->id
.chassis_id
|| !n
->id
.port_id
|| !n
->has_ttl
)
335 return log_lldp_rx_errno(n
->lldp_rx
, SYNTHETIC_ERRNO(EBADMSG
),
336 "One or more mandatory TLV missing in datagram. Ignoring.");
338 n
->rindex
= sizeof(struct ether_header
);
343 void lldp_neighbor_start_ttl(sd_lldp_neighbor
*n
) {
349 /* Use the packet's timestamp if there is one known */
350 base
= triple_timestamp_by_clock(&n
->timestamp
, CLOCK_BOOTTIME
);
351 if (!timestamp_is_set(base
))
352 base
= now(CLOCK_BOOTTIME
); /* Otherwise, take the current time */
354 n
->until
= usec_add(base
, n
->ttl
* USEC_PER_SEC
);
359 prioq_reshuffle(n
->lldp_rx
->neighbor_by_expiry
, n
, &n
->prioq_idx
);
362 bool lldp_neighbor_equal(const sd_lldp_neighbor
*a
, const sd_lldp_neighbor
*b
) {
369 if (a
->raw_size
!= b
->raw_size
)
372 return memcmp(LLDP_NEIGHBOR_RAW(a
), LLDP_NEIGHBOR_RAW(b
), a
->raw_size
) == 0;
375 int sd_lldp_neighbor_get_source_address(sd_lldp_neighbor
*n
, struct ether_addr
* address
) {
376 assert_return(n
, -EINVAL
);
377 assert_return(address
, -EINVAL
);
379 *address
= n
->source_address
;
383 int sd_lldp_neighbor_get_destination_address(sd_lldp_neighbor
*n
, struct ether_addr
* address
) {
384 assert_return(n
, -EINVAL
);
385 assert_return(address
, -EINVAL
);
387 *address
= n
->destination_address
;
391 int sd_lldp_neighbor_get_chassis_id(sd_lldp_neighbor
*n
, uint8_t *type
, const void **ret
, size_t *size
) {
392 assert_return(n
, -EINVAL
);
393 assert_return(type
, -EINVAL
);
394 assert_return(ret
, -EINVAL
);
395 assert_return(size
, -EINVAL
);
397 assert(n
->id
.chassis_id_size
> 0);
399 *type
= *(uint8_t*) n
->id
.chassis_id
;
400 *ret
= (uint8_t*) n
->id
.chassis_id
+ 1;
401 *size
= n
->id
.chassis_id_size
- 1;
406 static int format_mac_address(const void *data
, size_t sz
, char **ret
) {
410 assert(data
|| sz
<= 0);
415 memcpy(&a
, (uint8_t*) data
+ 1, sizeof(a
));
417 k
= new(char, ETHER_ADDR_TO_STRING_MAX
);
421 *ret
= ether_addr_to_string(&a
, k
);
425 static int format_network_address(const void *data
, size_t sz
, char **ret
) {
426 union in_addr_union a
;
429 if (sz
== 6 && ((uint8_t*) data
)[1] == 1) {
430 memcpy(&a
.in
, (uint8_t*) data
+ 2, sizeof(a
.in
));
432 } else if (sz
== 18 && ((uint8_t*) data
)[1] == 2) {
433 memcpy(&a
.in6
, (uint8_t*) data
+ 2, sizeof(a
.in6
));
438 r
= in_addr_to_string(family
, &a
, ret
);
444 int sd_lldp_neighbor_get_chassis_id_as_string(sd_lldp_neighbor
*n
, const char **ret
) {
448 assert_return(n
, -EINVAL
);
449 assert_return(ret
, -EINVAL
);
451 if (n
->chassis_id_as_string
) {
452 *ret
= n
->chassis_id_as_string
;
456 assert(n
->id
.chassis_id_size
> 0);
458 switch (*(uint8_t*) n
->id
.chassis_id
) {
460 case SD_LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT
:
461 case SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_ALIAS
:
462 case SD_LLDP_CHASSIS_SUBTYPE_PORT_COMPONENT
:
463 case SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_NAME
:
464 case SD_LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED
:
465 k
= cescape_length((char*) n
->id
.chassis_id
+ 1, n
->id
.chassis_id_size
- 1);
471 case SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS
:
472 r
= format_mac_address(n
->id
.chassis_id
, n
->id
.chassis_id_size
, &k
);
480 case SD_LLDP_CHASSIS_SUBTYPE_NETWORK_ADDRESS
:
481 r
= format_network_address(n
->id
.chassis_id
, n
->id
.chassis_id_size
, &k
);
490 /* Generic fallback */
491 k
= hexmem(n
->id
.chassis_id
, n
->id
.chassis_id_size
);
496 *ret
= n
->chassis_id_as_string
= k
;
500 int sd_lldp_neighbor_get_port_id(sd_lldp_neighbor
*n
, uint8_t *type
, const void **ret
, size_t *size
) {
501 assert_return(n
, -EINVAL
);
502 assert_return(type
, -EINVAL
);
503 assert_return(ret
, -EINVAL
);
504 assert_return(size
, -EINVAL
);
506 assert(n
->id
.port_id_size
> 0);
508 *type
= *(uint8_t*) n
->id
.port_id
;
509 *ret
= (uint8_t*) n
->id
.port_id
+ 1;
510 *size
= n
->id
.port_id_size
- 1;
515 int sd_lldp_neighbor_get_port_id_as_string(sd_lldp_neighbor
*n
, const char **ret
) {
519 assert_return(n
, -EINVAL
);
520 assert_return(ret
, -EINVAL
);
522 if (n
->port_id_as_string
) {
523 *ret
= n
->port_id_as_string
;
527 assert(n
->id
.port_id_size
> 0);
529 switch (*(uint8_t*) n
->id
.port_id
) {
531 case SD_LLDP_PORT_SUBTYPE_INTERFACE_ALIAS
:
532 case SD_LLDP_PORT_SUBTYPE_PORT_COMPONENT
:
533 case SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME
:
534 case SD_LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED
:
535 k
= cescape_length((char*) n
->id
.port_id
+ 1, n
->id
.port_id_size
- 1);
541 case SD_LLDP_PORT_SUBTYPE_MAC_ADDRESS
:
542 r
= format_mac_address(n
->id
.port_id
, n
->id
.port_id_size
, &k
);
550 case SD_LLDP_PORT_SUBTYPE_NETWORK_ADDRESS
:
551 r
= format_network_address(n
->id
.port_id
, n
->id
.port_id_size
, &k
);
560 /* Generic fallback */
561 k
= hexmem(n
->id
.port_id
, n
->id
.port_id_size
);
566 *ret
= n
->port_id_as_string
= k
;
570 int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor
*n
, uint16_t *ret_sec
) {
571 assert_return(n
, -EINVAL
);
572 assert_return(ret_sec
, -EINVAL
);
578 int sd_lldp_neighbor_get_system_name(sd_lldp_neighbor
*n
, const char **ret
) {
579 assert_return(n
, -EINVAL
);
580 assert_return(ret
, -EINVAL
);
585 *ret
= n
->system_name
;
589 int sd_lldp_neighbor_get_system_description(sd_lldp_neighbor
*n
, const char **ret
) {
590 assert_return(n
, -EINVAL
);
591 assert_return(ret
, -EINVAL
);
593 if (!n
->system_description
)
596 *ret
= n
->system_description
;
600 int sd_lldp_neighbor_get_port_description(sd_lldp_neighbor
*n
, const char **ret
) {
601 assert_return(n
, -EINVAL
);
602 assert_return(ret
, -EINVAL
);
604 if (!n
->port_description
)
607 *ret
= n
->port_description
;
611 int sd_lldp_neighbor_get_mud_url(sd_lldp_neighbor
*n
, const char **ret
) {
612 assert_return(n
, -EINVAL
);
613 assert_return(ret
, -EINVAL
);
622 int sd_lldp_neighbor_get_system_capabilities(sd_lldp_neighbor
*n
, uint16_t *ret
) {
623 assert_return(n
, -EINVAL
);
624 assert_return(ret
, -EINVAL
);
626 if (!n
->has_capabilities
)
629 *ret
= n
->system_capabilities
;
633 int sd_lldp_neighbor_get_enabled_capabilities(sd_lldp_neighbor
*n
, uint16_t *ret
) {
634 assert_return(n
, -EINVAL
);
635 assert_return(ret
, -EINVAL
);
637 if (!n
->has_capabilities
)
640 *ret
= n
->enabled_capabilities
;
644 int sd_lldp_neighbor_get_port_vlan_id(sd_lldp_neighbor
*n
, uint16_t *ret
) {
645 assert_return(n
, -EINVAL
);
646 assert_return(ret
, -EINVAL
);
648 if (!n
->has_port_vlan_id
)
651 *ret
= n
->port_vlan_id
;
655 int sd_lldp_neighbor_tlv_rewind(sd_lldp_neighbor
*n
) {
656 assert_return(n
, -EINVAL
);
658 assert(n
->raw_size
>= sizeof(struct ether_header
));
659 n
->rindex
= sizeof(struct ether_header
);
661 return n
->rindex
< n
->raw_size
;
664 int sd_lldp_neighbor_tlv_next(sd_lldp_neighbor
*n
) {
667 assert_return(n
, -EINVAL
);
669 if (n
->rindex
== n
->raw_size
) /* EOF */
672 if (n
->rindex
+ 2 > n
->raw_size
) /* Truncated message */
675 length
= LLDP_NEIGHBOR_TLV_LENGTH(n
);
676 if (n
->rindex
+ 2 + length
> n
->raw_size
)
679 n
->rindex
+= 2 + length
;
680 return n
->rindex
< n
->raw_size
;
683 int sd_lldp_neighbor_tlv_get_type(sd_lldp_neighbor
*n
, uint8_t *type
) {
684 assert_return(n
, -EINVAL
);
685 assert_return(type
, -EINVAL
);
687 if (n
->rindex
== n
->raw_size
) /* EOF */
690 if (n
->rindex
+ 2 > n
->raw_size
)
693 *type
= LLDP_NEIGHBOR_TLV_TYPE(n
);
697 int sd_lldp_neighbor_tlv_is_type(sd_lldp_neighbor
*n
, uint8_t type
) {
701 assert_return(n
, -EINVAL
);
703 r
= sd_lldp_neighbor_tlv_get_type(n
, &k
);
710 int sd_lldp_neighbor_tlv_get_oui(sd_lldp_neighbor
*n
, uint8_t oui
[static 3], uint8_t *subtype
) {
715 assert_return(n
, -EINVAL
);
716 assert_return(oui
, -EINVAL
);
717 assert_return(subtype
, -EINVAL
);
719 r
= sd_lldp_neighbor_tlv_is_type(n
, SD_LLDP_TYPE_PRIVATE
);
725 length
= LLDP_NEIGHBOR_TLV_LENGTH(n
);
729 if (n
->rindex
+ 2 + length
> n
->raw_size
)
732 d
= LLDP_NEIGHBOR_TLV_DATA(n
);
739 int sd_lldp_neighbor_tlv_is_oui(sd_lldp_neighbor
*n
, const uint8_t oui
[static 3], uint8_t subtype
) {
743 r
= sd_lldp_neighbor_tlv_get_oui(n
, k
, &st
);
749 return memcmp(k
, oui
, 3) == 0 && st
== subtype
;
752 int sd_lldp_neighbor_tlv_get_raw(sd_lldp_neighbor
*n
, const void **ret
, size_t *size
) {
755 assert_return(n
, -EINVAL
);
756 assert_return(ret
, -EINVAL
);
757 assert_return(size
, -EINVAL
);
759 /* Note that this returns the full TLV, including the TLV header */
761 if (n
->rindex
+ 2 > n
->raw_size
)
764 length
= LLDP_NEIGHBOR_TLV_LENGTH(n
);
765 if (n
->rindex
+ 2 + length
> n
->raw_size
)
768 *ret
= (uint8_t*) LLDP_NEIGHBOR_RAW(n
) + n
->rindex
;
774 int sd_lldp_neighbor_get_timestamp(sd_lldp_neighbor
*n
, clockid_t clock
, uint64_t *ret
) {
775 assert_return(n
, -EINVAL
);
776 assert_return(TRIPLE_TIMESTAMP_HAS_CLOCK(clock
), -EOPNOTSUPP
);
777 assert_return(clock_supported(clock
), -EOPNOTSUPP
);
778 assert_return(ret
, -EINVAL
);
780 if (!triple_timestamp_is_set(&n
->timestamp
))
783 *ret
= triple_timestamp_by_clock(&n
->timestamp
, clock
);
787 int lldp_neighbor_build_json(sd_lldp_neighbor
*n
, sd_json_variant
**ret
) {
788 const char *chassis_id
= NULL
, *port_id
= NULL
, *port_description
= NULL
,
789 *system_name
= NULL
, *system_description
= NULL
;
798 (void) sd_lldp_neighbor_get_chassis_id_as_string(n
, &chassis_id
);
799 (void) sd_lldp_neighbor_get_port_id_as_string(n
, &port_id
);
800 (void) sd_lldp_neighbor_get_port_description(n
, &port_description
);
801 (void) sd_lldp_neighbor_get_system_name(n
, &system_name
);
802 (void) sd_lldp_neighbor_get_system_description(n
, &system_description
);
804 valid_cc
= sd_lldp_neighbor_get_enabled_capabilities(n
, &cc
) >= 0;
805 valid_vlanid
= sd_lldp_neighbor_get_port_vlan_id(n
, &vlanid
) >= 0;
807 return sd_json_buildo(
809 JSON_BUILD_PAIR_STRING_NON_EMPTY("ChassisID", chassis_id
),
810 SD_JSON_BUILD_PAIR_BYTE_ARRAY("RawChassisID", n
->id
.chassis_id
, n
->id
.chassis_id_size
),
811 JSON_BUILD_PAIR_STRING_NON_EMPTY("PortID", port_id
),
812 SD_JSON_BUILD_PAIR_BYTE_ARRAY("RawPortID", n
->id
.port_id
, n
->id
.port_id_size
),
813 JSON_BUILD_PAIR_STRING_NON_EMPTY("PortDescription", port_description
),
814 JSON_BUILD_PAIR_STRING_NON_EMPTY("SystemName", system_name
),
815 JSON_BUILD_PAIR_STRING_NON_EMPTY("SystemDescription", system_description
),
816 SD_JSON_BUILD_PAIR_CONDITION(valid_cc
, "EnabledCapabilities", SD_JSON_BUILD_UNSIGNED(cc
)),
817 JSON_BUILD_PAIR_STRING_NON_EMPTY("MUDURL", n
->mud_url
),
818 SD_JSON_BUILD_PAIR_CONDITION(valid_vlanid
, "VlanID", SD_JSON_BUILD_UNSIGNED(vlanid
)));