mirror of https://github.com/ipxe/ipxe.git
341 lines
8.4 KiB
C
341 lines
8.4 KiB
C
/*
|
|
* 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,
|
|
};
|