david/ipxe
Archived
1
0
This repository has been archived on 2020-12-06. You can view files and clone it, but cannot push or open issues or pull requests.
ipxe/src/net/netdev_settings.c
Michael Brown 70fc25ad6e [netdevice] Limit MTU by hardware maximum frame length
Separate out the concept of "hardware maximum supported frame length"
and "configured link MTU", and limit the latter according to the
former.

In networks where the DHCP-supplied link MTU is inconsistent with the
hardware or driver capabilities (e.g. a network using jumbo frames),
this will result in iPXE advertising a TCP MSS consistent with a size
that can actually be received.

Note that the term "MTU" is typically used to refer to the maximum
length excluding the link-layer headers; we adopt this usage.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
2017-01-25 14:55:09 +00:00

449 lines
12 KiB
C

/*
* Copyright (C) 2008 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 );
#include <string.h>
#include <errno.h>
#include <byteswap.h>
#include <ipxe/dhcp.h>
#include <ipxe/dhcpopts.h>
#include <ipxe/settings.h>
#include <ipxe/device.h>
#include <ipxe/netdevice.h>
#include <ipxe/init.h>
/** @file
*
* Network device configuration settings
*
*/
/** Network device predefined settings */
const struct setting mac_setting __setting ( SETTING_NETDEV, mac ) = {
.name = "mac",
.description = "MAC address",
.type = &setting_type_hex,
};
const struct setting bustype_setting __setting ( SETTING_NETDEV, bustype ) = {
.name = "bustype",
.description = "Bus type",
.type = &setting_type_string,
};
const struct setting busloc_setting __setting ( SETTING_NETDEV, busloc ) = {
.name = "busloc",
.description = "Bus location",
.type = &setting_type_uint32,
};
const struct setting busid_setting __setting ( SETTING_NETDEV, busid ) = {
.name = "busid",
.description = "Bus ID",
.type = &setting_type_hex,
};
const struct setting chip_setting __setting ( SETTING_NETDEV, chip ) = {
.name = "chip",
.description = "Chip",
.type = &setting_type_string,
};
const struct setting ifname_setting __setting ( SETTING_NETDEV, ifname ) = {
.name = "ifname",
.description = "Interface name",
.type = &setting_type_string,
};
const struct setting mtu_setting __setting ( SETTING_NETDEV, mtu ) = {
.name = "mtu",
.description = "MTU",
.type = &setting_type_int16,
.tag = DHCP_MTU,
};
/**
* Store MAC address setting
*
* @v netdev Network device
* @v data Setting data, or NULL to clear setting
* @v len Length of setting data
* @ret rc Return status code
*/
static int netdev_store_mac ( struct net_device *netdev,
const void *data, size_t len ) {
struct ll_protocol *ll_protocol = netdev->ll_protocol;
/* Record new MAC address */
if ( data ) {
if ( len != netdev->ll_protocol->ll_addr_len )
return -EINVAL;
memcpy ( netdev->ll_addr, data, len );
} else {
/* Reset MAC address if clearing setting */
ll_protocol->init_addr ( netdev->hw_addr, netdev->ll_addr );
}
return 0;
}
/**
* Fetch MAC address setting
*
* @v netdev Network device
* @v data Buffer to fill with setting data
* @v len Length of buffer
* @ret len Length of setting data, or negative error
*/
static int netdev_fetch_mac ( struct net_device *netdev, void *data,
size_t len ) {
if ( len > netdev->ll_protocol->ll_addr_len )
len = netdev->ll_protocol->ll_addr_len;
memcpy ( data, netdev->ll_addr, len );
return netdev->ll_protocol->ll_addr_len;
}
/**
* Fetch bus type setting
*
* @v netdev Network device
* @v data Buffer to fill with setting data
* @v len Length of buffer
* @ret len Length of setting data, or negative error
*/
static int netdev_fetch_bustype ( struct net_device *netdev, void *data,
size_t len ) {
static const char *bustypes[] = {
[BUS_TYPE_PCI] = "PCI",
[BUS_TYPE_ISAPNP] = "ISAPNP",
[BUS_TYPE_EISA] = "EISA",
[BUS_TYPE_MCA] = "MCA",
[BUS_TYPE_ISA] = "ISA",
[BUS_TYPE_TAP] = "TAP",
[BUS_TYPE_EFI] = "EFI",
[BUS_TYPE_XEN] = "XEN",
[BUS_TYPE_HV] = "HV",
[BUS_TYPE_USB] = "USB",
};
struct device_description *desc = &netdev->dev->desc;
const char *bustype;
assert ( desc->bus_type < ( sizeof ( bustypes ) /
sizeof ( bustypes[0] ) ) );
bustype = bustypes[desc->bus_type];
if ( ! bustype )
return -ENOENT;
strncpy ( data, bustype, len );
return strlen ( bustype );
}
/**
* Fetch bus location setting
*
* @v netdev Network device
* @v data Buffer to fill with setting data
* @v len Length of buffer
* @ret len Length of setting data, or negative error
*/
static int netdev_fetch_busloc ( struct net_device *netdev, void *data,
size_t len ) {
struct device_description *desc = &netdev->dev->desc;
uint32_t busloc;
busloc = cpu_to_be32 ( desc->location );
if ( len > sizeof ( busloc ) )
len = sizeof ( busloc );
memcpy ( data, &busloc, len );
return sizeof ( busloc );
}
/**
* Fetch bus ID setting
*
* @v netdev Network device
* @v data Buffer to fill with setting data
* @v len Length of buffer
* @ret len Length of setting data, or negative error
*/
static int netdev_fetch_busid ( struct net_device *netdev, void *data,
size_t len ) {
struct device_description *desc = &netdev->dev->desc;
struct dhcp_netdev_desc dhcp_desc;
dhcp_desc.type = desc->bus_type;
dhcp_desc.vendor = htons ( desc->vendor );
dhcp_desc.device = htons ( desc->device );
if ( len > sizeof ( dhcp_desc ) )
len = sizeof ( dhcp_desc );
memcpy ( data, &dhcp_desc, len );
return sizeof ( dhcp_desc );
}
/**
* Fetch chip setting
*
* @v netdev Network device
* @v data Buffer to fill with setting data
* @v len Length of buffer
* @ret len Length of setting data, or negative error
*/
static int netdev_fetch_chip ( struct net_device *netdev, void *data,
size_t len ) {
const char *chip = netdev->dev->driver_name;
strncpy ( data, chip, len );
return strlen ( chip );
}
/**
* Fetch ifname setting
*
* @v netdev Network device
* @v data Buffer to fill with setting data
* @v len Length of buffer
* @ret len Length of setting data, or negative error
*/
static int netdev_fetch_ifname ( struct net_device *netdev, void *data,
size_t len ) {
const char *ifname = netdev->name;
strncpy ( data, ifname, len );
return strlen ( ifname );
}
/** A network device setting operation */
struct netdev_setting_operation {
/** Setting */
const struct setting *setting;
/** Store setting (or NULL if not supported)
*
* @v netdev Network device
* @v data Setting data, or NULL to clear setting
* @v len Length of setting data
* @ret rc Return status code
*/
int ( * store ) ( struct net_device *netdev, const void *data,
size_t len );
/** Fetch setting
*
* @v netdev Network device
* @v data Buffer to fill with setting data
* @v len Length of buffer
* @ret len Length of setting data, or negative error
*/
int ( * fetch ) ( struct net_device *netdev, void *data, size_t len );
};
/** Network device settings */
static struct netdev_setting_operation netdev_setting_operations[] = {
{ &mac_setting, netdev_store_mac, netdev_fetch_mac },
{ &bustype_setting, NULL, netdev_fetch_bustype },
{ &busloc_setting, NULL, netdev_fetch_busloc },
{ &busid_setting, NULL, netdev_fetch_busid },
{ &chip_setting, NULL, netdev_fetch_chip },
{ &ifname_setting, NULL, netdev_fetch_ifname },
};
/**
* Store value of network device setting
*
* @v settings Settings block
* @v setting Setting to store
* @v data Setting data, or NULL to clear setting
* @v len Length of setting data
* @ret rc Return status code
*/
static int netdev_store ( struct settings *settings,
const struct setting *setting,
const void *data, size_t len ) {
struct net_device *netdev = container_of ( settings, struct net_device,
settings.settings );
struct netdev_setting_operation *op;
unsigned int i;
/* Handle network device-specific settings */
for ( i = 0 ; i < ( sizeof ( netdev_setting_operations ) /
sizeof ( netdev_setting_operations[0] ) ) ; i++ ) {
op = &netdev_setting_operations[i];
if ( setting_cmp ( setting, op->setting ) == 0 ) {
if ( op->store ) {
return op->store ( netdev, data, len );
} else {
return -ENOTSUP;
}
}
}
return generic_settings_store ( settings, setting, data, len );
}
/**
* Fetch value of network device setting
*
* @v settings Settings block
* @v setting Setting to fetch
* @v data Buffer to fill with setting data
* @v len Length of buffer
* @ret len Length of setting data, or negative error
*/
static int netdev_fetch ( struct settings *settings, struct setting *setting,
void *data, size_t len ) {
struct net_device *netdev = container_of ( settings, struct net_device,
settings.settings );
struct netdev_setting_operation *op;
unsigned int i;
/* Handle network device-specific settings */
for ( i = 0 ; i < ( sizeof ( netdev_setting_operations ) /
sizeof ( netdev_setting_operations[0] ) ) ; i++ ) {
op = &netdev_setting_operations[i];
if ( setting_cmp ( setting, op->setting ) == 0 )
return op->fetch ( netdev, data, len );
}
return generic_settings_fetch ( settings, setting, data, len );
}
/**
* Clear network device settings
*
* @v settings Settings block
*/
static void netdev_clear ( struct settings *settings ) {
generic_settings_clear ( settings );
}
/** Network device configuration settings operations */
struct settings_operations netdev_settings_operations = {
.store = netdev_store,
.fetch = netdev_fetch,
.clear = netdev_clear,
};
/**
* Redirect "netX" settings block
*
* @v settings Settings block
* @ret settings Underlying settings block
*/
static struct settings * netdev_redirect ( struct settings *settings ) {
struct net_device *netdev;
/* Redirect to most recently opened network device */
netdev = last_opened_netdev();
if ( netdev ) {
return netdev_settings ( netdev );
} else {
return settings;
}
}
/** "netX" settings operations */
static struct settings_operations netdev_redirect_settings_operations = {
.redirect = netdev_redirect,
};
/** "netX" settings */
static struct settings netdev_redirect_settings = {
.refcnt = NULL,
.siblings = LIST_HEAD_INIT ( netdev_redirect_settings.siblings ),
.children = LIST_HEAD_INIT ( netdev_redirect_settings.children ),
.op = &netdev_redirect_settings_operations,
};
/** Initialise "netX" settings */
static void netdev_redirect_settings_init ( void ) {
int rc;
if ( ( rc = register_settings ( &netdev_redirect_settings, NULL,
"netX" ) ) != 0 ) {
DBG ( "Could not register netX settings: %s\n",
strerror ( rc ) );
return;
}
}
/** "netX" settings initialiser */
struct init_fn netdev_redirect_settings_init_fn __init_fn ( INIT_LATE ) = {
.initialise = netdev_redirect_settings_init,
};
/**
* Apply network device settings
*
* @ret rc Return status code
*/
static int apply_netdev_settings ( void ) {
struct net_device *netdev;
struct settings *settings;
struct ll_protocol *ll_protocol;
size_t max_mtu;
size_t old_mtu;
size_t mtu;
int rc;
/* Process settings for each network device */
for_each_netdev ( netdev ) {
/* Get network device settings */
settings = netdev_settings ( netdev );
/* Get MTU */
mtu = fetch_uintz_setting ( settings, &mtu_setting );
/* Do nothing unless MTU is specified */
if ( ! mtu )
continue;
/* Limit MTU to maximum supported by hardware */
ll_protocol = netdev->ll_protocol;
max_mtu = ( netdev->max_pkt_len - ll_protocol->ll_header_len );
if ( mtu > max_mtu ) {
DBGC ( netdev, "NETDEV %s cannot support MTU %zd (max "
"%zd)\n", netdev->name, mtu, max_mtu );
mtu = max_mtu;
}
/* Update maximum packet length */
old_mtu = netdev->mtu;
netdev->mtu = mtu;
if ( mtu != old_mtu ) {
DBGC ( netdev, "NETDEV %s MTU is %zd\n",
netdev->name, mtu );
}
/* Close and reopen network device if MTU has increased */
if ( netdev_is_open ( netdev ) && ( mtu > old_mtu ) ) {
netdev_close ( netdev );
if ( ( rc = netdev_open ( netdev ) ) != 0 ) {
DBGC ( netdev, "NETDEV %s could not reopen: "
"%s\n", netdev->name, strerror ( rc ) );
return rc;
}
}
}
return 0;
}
/** Network device settings applicator */
struct settings_applicator netdev_applicator __settings_applicator = {
.apply = apply_netdev_settings,
};