diff options
author | Michael Brown <mcb30@ipxe.org> | 2023-02-05 13:07:30 +0000 |
---|---|---|
committer | Michael Brown <mcb30@ipxe.org> | 2023-02-05 18:18:02 +0000 |
commit | dc16de3204d1956d4fd17808e6d34ac926bbe932 (patch) | |
tree | da7fdb7b513690b3c51876674d273874bd144104 /src | |
parent | 6c0335adf66cf58133ab2a1662d1b26f20000767 (diff) | |
download | ipxe-lldp.zip ipxe-lldp.tar.gz ipxe-lldp.tar.bz2 |
[lldp] Add support for the Link Layer Discovery Protocollldp
Add support for recording LLDP packets and exposing TLV values via the
settings mechanism. LLDP settings are encoded as
${netX.lldp/<prefix>.<type>.<index>.<offset>.<length>}
where
<type> is the TLV type
<offset> is the starting offset within the TLV value
<length> is the length (or zero to read the from <offset> to the end)
<prefix>, if it has a non-zero value, is the subtype byte string of
length <offset> to match at the start of the TLV value, up to a
maximum matched length of 4 bytes
<index> is the index of the entry matching <type> and <prefix> to be
accessed, with zero indicating the first matching entry
The <prefix> is designed to accommodate both matching of the OUI
within an organization-specific TLV (e.g. 0x0080c2 for IEEE 802.1
TLVs) and of a subtype byte as found within many TLVs.
This encoding allows most LLDP values to be extracted easily. For
example
System name: ${netX.lldp/5.0.0.0:string}
System description: ${netX.lldp/6.0.0.0:string}
Port description: ${netX.lldp/4.0.0.0:string}
Port interface name: ${netX.lldp/5.2.0.1.0:string}
Chassis MAC address: ${netX.lldp/4.1.0.1.0:hex}
Management IPv4 address: ${netX.lldp/5.1.8.0.2.4:ipv4}
Port VLAN ID: ${netX.lldp/0x0080c2.1.127.0.4.2:int16}
Port VLAN name: ${netX.lldp/0x0080c2.3.127.0.7.0:string}
Maximum frame size: ${netX.lldp/0x00120f.4.127.0.4.2:uint16}
Originally-implemented-by: Marin Hannache <git@mareo.fr>
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Diffstat (limited to 'src')
-rw-r--r-- | src/config/config_ethernet.c | 3 | ||||
-rw-r--r-- | src/config/general.h | 1 | ||||
-rw-r--r-- | src/include/ipxe/errfile.h | 1 | ||||
-rw-r--r-- | src/include/ipxe/if_ether.h | 1 | ||||
-rw-r--r-- | src/include/ipxe/lldp.h | 97 | ||||
-rw-r--r-- | src/net/lldp.c | 340 |
6 files changed, 443 insertions, 0 deletions
diff --git a/src/config/config_ethernet.c b/src/config/config_ethernet.c index 8a663c9..c1b35bf 100644 --- a/src/config/config_ethernet.c +++ b/src/config/config_ethernet.c @@ -49,3 +49,6 @@ REQUIRE_OBJECT ( eth_slow ); #ifdef NET_PROTO_EAPOL REQUIRE_OBJECT ( eapol ); #endif +#ifdef NET_PROTO_LLDP +REQUIRE_OBJECT ( lldp ); +#endif diff --git a/src/config/general.h b/src/config/general.h index 2d15f50..e9ceaff 100644 --- a/src/config/general.h +++ b/src/config/general.h @@ -40,6 +40,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define NET_PROTO_STP /* Spanning Tree protocol */ #define NET_PROTO_LACP /* Link Aggregation control protocol */ #define NET_PROTO_EAPOL /* EAP over LAN protocol */ +#undef NET_PROTO_LLDP /* Link Layer Discovery protocol */ /* * PXE support diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index 7c3b0c4..d7b6ea1 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -295,6 +295,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ERRFILE_ntp ( ERRFILE_NET | 0x00490000 ) #define ERRFILE_httpntlm ( ERRFILE_NET | 0x004a0000 ) #define ERRFILE_eap ( ERRFILE_NET | 0x004b0000 ) +#define ERRFILE_lldp ( ERRFILE_NET | 0x004c0000 ) #define ERRFILE_image ( ERRFILE_IMAGE | 0x00000000 ) #define ERRFILE_elf ( ERRFILE_IMAGE | 0x00010000 ) diff --git a/src/include/ipxe/if_ether.h b/src/include/ipxe/if_ether.h index 58d91b9..c1168b1 100644 --- a/src/include/ipxe/if_ether.h +++ b/src/include/ipxe/if_ether.h @@ -23,6 +23,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ETH_P_SLOW 0x8809 /* Ethernet slow protocols */ #define ETH_P_EAPOL 0x888E /* 802.1X EAP over LANs */ #define ETH_P_AOE 0x88A2 /* ATA over Ethernet */ +#define ETH_P_LLDP 0x88CC /* Link Layer Discovery Protocol */ #define ETH_P_FCOE 0x8906 /* Fibre Channel over Ethernet */ #define ETH_P_FIP 0x8914 /* FCoE Initialization Protocol */ diff --git a/src/include/ipxe/lldp.h b/src/include/ipxe/lldp.h new file mode 100644 index 0000000..9951d3b --- /dev/null +++ b/src/include/ipxe/lldp.h @@ -0,0 +1,97 @@ +#ifndef _IPXE_LLDP_H +#define _IPXE_LLDP_H + +/** @file + * + * Link Layer Discovery Protocol + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <stdint.h> + +/** An LLDP TLV header */ +struct lldp_tlv { + /** Type and length */ + uint16_t type_len; + /** Data */ + uint8_t data[0]; +} __attribute__ (( packed )); + +/** + * Extract LLDP TLV type + * + * @v type_len Type and length + * @ret type Type + */ +#define LLDP_TLV_TYPE( type_len ) ( (type_len) >> 9 ) + +/** + * Extract LLDP TLV length + * + * @v type_len Type and length + * @ret len Length + */ +#define LLDP_TLV_LEN( type_len ) ( (type_len) & 0x01ff ) + +/** End of LLDP data unit */ +#define LLDP_TYPE_END 0x00 + +/** LLDP settings block name */ +#define LLDP_SETTINGS_NAME "lldp" + +/** + * Construct LLDP setting tag + * + * LLDP settings are encoded as + * + * ${netX.lldp/<prefix>.<type>.<index>.<offset>.<length>} + * + * where + * + * <type> is the TLV type + * + * <offset> is the starting offset within the TLV value + * + * <length> is the length (or zero to read the from <offset> to the end) + * + * <prefix>, if it has a non-zero value, is the subtype byte string + * of length <offset> to match at the start of the TLV value, up to + * a maximum matched length of 4 bytes + * + * <index> is the index of the entry matching <type> and <prefix> to + * be accessed, with zero indicating the first matching entry + * + * The <prefix> is designed to accommodate both matching of the OUI + * within an organization-specific TLV (e.g. 0x0080c2 for IEEE 802.1 + * TLVs) and of a subtype byte as found within many TLVs. + * + * This encoding allows most LLDP values to be extracted easily. For + * example + * + * System name: ${netX.lldp/5.0.0.0:string} + * + * System description: ${netX.lldp/6.0.0.0:string} + * + * Port description: ${netX.lldp/4.0.0.0:string} + * + * Port interface name: ${netX.lldp/5.2.0.1.0:string} + * + * Chassis MAC address: ${netX.lldp/4.1.0.1.0:hex} + * + * Management IPv4 address: ${netX.lldp/5.1.8.0.2.4:ipv4} + * + * Port VLAN ID: ${netX.lldp/0x0080c2.1.127.0.4.2:int16} + * + * Port VLAN name: ${netX.lldp/0x0080c2.3.127.0.7.0:string} + * + * Maximum frame size: ${netX.lldp/0x00120f.4.127.0.4.2:uint16} + * + */ +#define LLDP_TAG( prefix, type, index, offset, length ) \ + ( ( ( ( uint64_t ) (prefix) ) << 32 ) | \ + ( (type) << 24 ) | ( (index) << 16 ) | \ + ( (offset) << 8 ) | ( (length) << 0 ) ) + +#endif /* _IPXE_LLDP_H */ diff --git a/src/net/lldp.c b/src/net/lldp.c new file mode 100644 index 0000000..72e3ecd --- /dev/null +++ b/src/net/lldp.c @@ -0,0 +1,340 @@ +/* + * Copyright (C) 2023 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** @file + * + * Link Layer Discovery Protocol + * + */ + +#include <stdlib.h> +#include <errno.h> +#include <byteswap.h> +#include <ipxe/iobuf.h> +#include <ipxe/netdevice.h> +#include <ipxe/if_ether.h> +#include <ipxe/settings.h> +#include <ipxe/lldp.h> + +/** An LLDP settings block */ +struct lldp_settings { + /** Reference counter */ + struct refcnt refcnt; + /** Settings interface */ + struct settings settings; + /** List of LLDP settings blocks */ + struct list_head list; + /** Name */ + const char *name; + /** LLDP data */ + void *data; + /** Length of LLDP data */ + size_t len; +}; + +/** LLDP settings scope */ +static const struct settings_scope lldp_settings_scope; + +/** List of LLDP settings blocks */ +static LIST_HEAD ( lldp_settings ); + +/** + * Free LLDP settings block + * + * @v refcnt Reference counter + */ +static void lldp_free ( struct refcnt *refcnt ) { + struct lldp_settings *lldpset = + container_of ( refcnt, struct lldp_settings, refcnt ); + + DBGC ( lldpset, "LLDP %s freed\n", lldpset->name ); + list_del ( &lldpset->list ); + free ( lldpset->data ); + free ( lldpset ); +} + +/** + * Find LLDP settings block + * + * @v netdev Network device + * @ret lldpset LLDP settings block + */ +static struct lldp_settings * lldp_find ( struct net_device *netdev ) { + struct lldp_settings *lldpset; + + /* Find matching LLDP settings block */ + list_for_each_entry ( lldpset, &lldp_settings, list ) { + if ( netdev_settings ( netdev ) == lldpset->settings.parent ) + return lldpset; + } + + return NULL; +} + +/** + * Check applicability of LLDP setting + * + * @v settings Settings block + * @v setting Setting to fetch + * @ret applies Setting applies within this settings block + */ +static int lldp_applies ( struct settings *settings __unused, + const struct setting *setting ) { + + return ( setting->scope == &lldp_settings_scope ); +} + +/** + * Fetch value of LLDP setting + * + * @v settings Settings block + * @v setting Setting to fetch + * @v buf Buffer to fill with setting data + * @v len Length of buffer + * @ret len Length of setting data, or negative error + */ +static int lldp_fetch ( struct settings *settings, + struct setting *setting, + void *buf, size_t len ) { + struct lldp_settings *lldpset = + container_of ( settings, struct lldp_settings, settings ); + union { + uint32_t high; + uint8_t raw[4]; + } tag_prefix; + uint32_t tag_low; + uint8_t tag_type; + uint8_t tag_index; + uint8_t tag_offset; + uint8_t tag_length; + const void *match; + const void *data; + size_t match_len; + size_t remaining; + const struct lldp_tlv *tlv; + unsigned int tlv_type_len; + unsigned int tlv_type; + unsigned int tlv_len; + + /* Parse setting tag */ + tag_prefix.high = htonl ( setting->tag >> 32 ); + tag_low = setting->tag; + tag_type = ( tag_low >> 24 ); + tag_index = ( tag_low >> 16 ); + tag_offset = ( tag_low >> 8 ); + tag_length = ( tag_low >> 0 ); + + /* Identify match prefix */ + match_len = tag_offset; + if ( match_len > sizeof ( tag_prefix ) ) + match_len = sizeof ( tag_prefix ); + if ( ! tag_prefix.high ) + match_len = 0; + match = &tag_prefix.raw[ sizeof ( tag_prefix ) - match_len ]; + + /* Locate matching TLV */ + for ( data = lldpset->data, remaining = lldpset->len ; remaining ; + data += tlv_len, remaining -= tlv_len ) { + + /* Parse TLV header */ + if ( remaining < sizeof ( *tlv ) ) { + DBGC ( lldpset, "LLDP %s underlength TLV header\n", + lldpset->name ); + DBGC_HDA ( lldpset, 0, data, remaining ); + break; + } + tlv = data; + data += sizeof ( *tlv ); + remaining -= sizeof ( *tlv ); + tlv_type_len = ntohs ( tlv->type_len ); + tlv_type = LLDP_TLV_TYPE ( tlv_type_len ); + if ( tlv_type == LLDP_TYPE_END ) + break; + tlv_len = LLDP_TLV_LEN ( tlv_type_len ); + if ( remaining < tlv_len ) { + DBGC ( lldpset, "LLDP %s underlength TLV value\n", + lldpset->name ); + DBGC_HDA ( lldpset, 0, data, remaining ); + break; + } + DBGC2 ( lldpset, "LLDP %s found type %d:\n", + lldpset->name, tlv_type ); + DBGC2_HDA ( lldpset, 0, data, tlv_len ); + + /* Check for matching tag type */ + if ( tlv_type != tag_type ) + continue; + + /* Check for matching prefix */ + if ( tlv_len < match_len ) + continue; + if ( memcmp ( data, match, match_len ) != 0 ) + continue; + + /* Check for matching index */ + if ( tag_index-- ) + continue; + + /* Skip offset */ + if ( tlv_len < tag_offset ) + return 0; + data += tag_offset; + tlv_len -= tag_offset; + + /* Set type if not already specified */ + if ( ! setting->type ) { + setting->type = ( tag_length ? &setting_type_hex : + &setting_type_string ); + } + + /* Extract value */ + if ( tag_length && ( tlv_len > tag_length ) ) + tlv_len = tag_length; + if ( len > tlv_len ) + len = tlv_len; + memcpy ( buf, data, len ); + return tlv_len; + } + + return -ENOENT; +} + +/** LLDP settings operations */ +static struct settings_operations lldp_settings_operations = { + .applies = lldp_applies, + .fetch = lldp_fetch, +}; + +/** + * Process LLDP packet + * + * @v iobuf I/O buffer + * @v netdev Network device + * @v ll_dest Link-layer destination address + * @v ll_source Link-layer source address + * @v flags Packet flags + * @ret rc Return status code + */ +static int lldp_rx ( struct io_buffer *iobuf, struct net_device *netdev, + const void *ll_dest, const void *ll_source, + unsigned int flags __unused ) { + struct lldp_settings *lldpset; + size_t len; + void *data; + int rc; + + /* Find matching LLDP settings block */ + lldpset = lldp_find ( netdev ); + if ( ! lldpset ) { + DBGC ( netdev, "LLDP %s has no \"%s\" settings block\n", + netdev->name, LLDP_SETTINGS_NAME ); + rc = -ENOENT; + goto err_find; + } + + /* Create trimmed copy of received LLDP data */ + len = iob_len ( iobuf ); + data = malloc ( len ); + if ( ! data ) { + rc = -ENOMEM; + goto err_alloc; + } + memcpy ( data, iobuf->data, len ); + + /* Free any existing LLDP data */ + free ( lldpset->data ); + + /* Transfer data to LLDP settings block */ + lldpset->data = data; + lldpset->len = len; + data = NULL; + DBGC2 ( lldpset, "LLDP %s src %s ", + lldpset->name, netdev->ll_protocol->ntoa ( ll_source ) ); + DBGC2 ( lldpset, "dst %s\n", netdev->ll_protocol->ntoa ( ll_dest ) ); + DBGC2_HDA ( lldpset, 0, lldpset->data, lldpset->len ); + + /* Success */ + rc = 0; + + free ( data ); + err_alloc: + err_find: + free_iob ( iobuf ); + return rc; +} + +/** LLDP protocol */ +struct net_protocol lldp_protocol __net_protocol = { + .name = "LLDP", + .net_proto = htons ( ETH_P_LLDP ), + .rx = lldp_rx, +}; + +/** + * Create LLDP settings block + * + * @v netdev Network device + * @ret rc Return status code + */ +static int lldp_probe ( struct net_device *netdev ) { + struct lldp_settings *lldpset; + int rc; + + /* Allocate LLDP settings block */ + lldpset = zalloc ( sizeof ( *lldpset ) ); + if ( ! lldpset ) { + rc = -ENOMEM; + goto err_alloc; + } + ref_init ( &lldpset->refcnt, lldp_free ); + settings_init ( &lldpset->settings, &lldp_settings_operations, + &lldpset->refcnt, &lldp_settings_scope ); + list_add_tail ( &lldpset->list, &lldp_settings ); + lldpset->name = netdev->name; + + /* Register settings */ + if ( ( rc = register_settings ( &lldpset->settings, netdev_settings ( netdev ), + LLDP_SETTINGS_NAME ) ) != 0 ) { + DBGC ( lldpset, "LLDP %s could not register settings: %s\n", + lldpset->name, strerror ( rc ) ); + goto err_register; + } + DBGC ( lldpset, "LLDP %s registered\n", lldpset->name ); + + ref_put ( &lldpset->refcnt ); + return 0; + + unregister_settings ( &lldpset->settings ); + err_register: + ref_put ( &lldpset->refcnt ); + err_alloc: + return rc; +} + +/** LLDP driver */ +struct net_driver lldp_driver __net_driver = { + .name = "LLDP", + .probe = lldp_probe, +}; |