mirror of https://github.com/ipxe/ipxe.git
277 lines
7.5 KiB
C
277 lines
7.5 KiB
C
/*
|
|
* Copyright (C) 2013 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 <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <ipxe/dhcppkt.h>
|
|
#include <ipxe/init.h>
|
|
#include <ipxe/netdevice.h>
|
|
#include <ipxe/cachedhcp.h>
|
|
|
|
/** @file
|
|
*
|
|
* Cached DHCP packet
|
|
*
|
|
*/
|
|
|
|
/** A cached DHCP packet */
|
|
struct cached_dhcp_packet {
|
|
/** Settings block name */
|
|
const char *name;
|
|
/** DHCP packet (if any) */
|
|
struct dhcp_packet *dhcppkt;
|
|
};
|
|
|
|
/** Cached DHCPACK */
|
|
struct cached_dhcp_packet cached_dhcpack = {
|
|
.name = DHCP_SETTINGS_NAME,
|
|
};
|
|
|
|
/** Cached ProxyDHCPOFFER */
|
|
struct cached_dhcp_packet cached_proxydhcp = {
|
|
.name = PROXYDHCP_SETTINGS_NAME,
|
|
};
|
|
|
|
/** Cached PXEBSACK */
|
|
struct cached_dhcp_packet cached_pxebs = {
|
|
.name = PXEBS_SETTINGS_NAME,
|
|
};
|
|
|
|
/** List of cached DHCP packets */
|
|
static struct cached_dhcp_packet *cached_packets[] = {
|
|
&cached_dhcpack,
|
|
&cached_proxydhcp,
|
|
&cached_pxebs,
|
|
};
|
|
|
|
/** Colour for debug messages */
|
|
#define colour &cached_dhcpack
|
|
|
|
/**
|
|
* Free cached DHCP packet
|
|
*
|
|
* @v cache Cached DHCP packet
|
|
*/
|
|
static void cachedhcp_free ( struct cached_dhcp_packet *cache ) {
|
|
|
|
dhcppkt_put ( cache->dhcppkt );
|
|
cache->dhcppkt = NULL;
|
|
}
|
|
|
|
/**
|
|
* Apply cached DHCP packet settings
|
|
*
|
|
* @v cache Cached DHCP packet
|
|
* @v netdev Network device, or NULL
|
|
* @ret rc Return status code
|
|
*/
|
|
static int cachedhcp_apply ( struct cached_dhcp_packet *cache,
|
|
struct net_device *netdev ) {
|
|
struct settings *settings = NULL;
|
|
struct ll_protocol *ll_protocol;
|
|
const uint8_t *chaddr;
|
|
uint8_t *hw_addr;
|
|
uint8_t *ll_addr;
|
|
size_t ll_addr_len;
|
|
int rc;
|
|
|
|
/* Do nothing if cache is empty */
|
|
if ( ! cache->dhcppkt )
|
|
return 0;
|
|
chaddr = cache->dhcppkt->dhcphdr->chaddr;
|
|
|
|
/* Handle association with network device, if specified */
|
|
if ( netdev ) {
|
|
hw_addr = netdev->hw_addr;
|
|
ll_addr = netdev->ll_addr;
|
|
ll_protocol = netdev->ll_protocol;
|
|
ll_addr_len = ll_protocol->ll_addr_len;
|
|
|
|
/* If cached packet's MAC address matches the network
|
|
* device's permanent MAC address, then assume that
|
|
* the permanent MAC address ought to be the network
|
|
* device's current link-layer address.
|
|
*
|
|
* This situation can arise when the PXE ROM does not
|
|
* understand the system-specific mechanism for
|
|
* overriding the MAC address, and so uses the
|
|
* permanent MAC address instead. We choose to match
|
|
* this behaviour in order to minimise surprise.
|
|
*/
|
|
if ( memcmp ( hw_addr, chaddr, ll_addr_len ) == 0 ) {
|
|
if ( memcmp ( hw_addr, ll_addr, ll_addr_len ) != 0 ) {
|
|
DBGC ( colour, "CACHEDHCP %s resetting %s MAC "
|
|
"%s ", cache->name, netdev->name,
|
|
ll_protocol->ntoa ( ll_addr ) );
|
|
DBGC ( colour, "-> %s\n",
|
|
ll_protocol->ntoa ( hw_addr ) );
|
|
}
|
|
memcpy ( ll_addr, hw_addr, ll_addr_len );
|
|
}
|
|
|
|
/* Do nothing unless cached packet's MAC address
|
|
* matches this network device.
|
|
*/
|
|
if ( memcmp ( ll_addr, chaddr, ll_addr_len ) != 0 ) {
|
|
DBGC ( colour, "CACHEDHCP %s does not match %s\n",
|
|
cache->name, netdev->name );
|
|
return 0;
|
|
}
|
|
DBGC ( colour, "CACHEDHCP %s is for %s\n",
|
|
cache->name, netdev->name );
|
|
|
|
/* Use network device's settings block */
|
|
settings = netdev_settings ( netdev );
|
|
}
|
|
|
|
/* Register settings */
|
|
if ( ( rc = register_settings ( &cache->dhcppkt->settings, settings,
|
|
cache->name ) ) != 0 ) {
|
|
DBGC ( colour, "CACHEDHCP %s could not register settings: %s\n",
|
|
cache->name, strerror ( rc ) );
|
|
return rc;
|
|
}
|
|
|
|
/* Free cached DHCP packet */
|
|
cachedhcp_free ( cache );
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Record cached DHCP packet
|
|
*
|
|
* @v cache Cached DHCP packet
|
|
* @v data DHCPACK packet buffer
|
|
* @v max_len Maximum possible length
|
|
* @ret rc Return status code
|
|
*/
|
|
int cachedhcp_record ( struct cached_dhcp_packet *cache, userptr_t data,
|
|
size_t max_len ) {
|
|
struct dhcp_packet *dhcppkt;
|
|
struct dhcp_packet *tmp;
|
|
struct dhcphdr *dhcphdr;
|
|
unsigned int i;
|
|
size_t len;
|
|
|
|
/* Free any existing cached packet */
|
|
cachedhcp_free ( cache );
|
|
|
|
/* Allocate and populate DHCP packet */
|
|
dhcppkt = zalloc ( sizeof ( *dhcppkt ) + max_len );
|
|
if ( ! dhcppkt ) {
|
|
DBGC ( colour, "CACHEDHCP %s could not allocate copy\n",
|
|
cache->name );
|
|
return -ENOMEM;
|
|
}
|
|
dhcphdr = ( ( ( void * ) dhcppkt ) + sizeof ( *dhcppkt ) );
|
|
copy_from_user ( dhcphdr, data, 0, max_len );
|
|
dhcppkt_init ( dhcppkt, dhcphdr, max_len );
|
|
|
|
/* Shrink packet to required length. If reallocation fails,
|
|
* just continue to use the original packet and waste the
|
|
* unused space.
|
|
*/
|
|
len = dhcppkt_len ( dhcppkt );
|
|
assert ( len <= max_len );
|
|
tmp = realloc ( dhcppkt, ( sizeof ( *dhcppkt ) + len ) );
|
|
if ( tmp )
|
|
dhcppkt = tmp;
|
|
|
|
/* Reinitialise packet at new address */
|
|
dhcphdr = ( ( ( void * ) dhcppkt ) + sizeof ( *dhcppkt ) );
|
|
dhcppkt_init ( dhcppkt, dhcphdr, len );
|
|
|
|
/* Discard duplicate packets, since some PXE stacks (including
|
|
* iPXE itself) will report the DHCPACK packet as the PXEBSACK
|
|
* if no separate PXEBSACK exists.
|
|
*/
|
|
for ( i = 0 ; i < ( sizeof ( cached_packets ) /
|
|
sizeof ( cached_packets[0] ) ) ; i++ ) {
|
|
tmp = cached_packets[i]->dhcppkt;
|
|
if ( tmp && ( dhcppkt_len ( tmp ) == len ) &&
|
|
( memcmp ( tmp->dhcphdr, dhcppkt->dhcphdr, len ) == 0 ) ) {
|
|
DBGC ( colour, "CACHEDHCP %s duplicates %s\n",
|
|
cache->name, cached_packets[i]->name );
|
|
dhcppkt_put ( dhcppkt );
|
|
return -EEXIST;
|
|
}
|
|
}
|
|
|
|
/* Store as cached packet */
|
|
DBGC ( colour, "CACHEDHCP %s at %#08lx+%#zx/%#zx\n", cache->name,
|
|
user_to_phys ( data, 0 ), len, max_len );
|
|
cache->dhcppkt = dhcppkt;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Cached DHCPACK startup function
|
|
*
|
|
*/
|
|
static void cachedhcp_startup ( void ) {
|
|
|
|
/* Apply cached ProxyDHCPOFFER, if any */
|
|
cachedhcp_apply ( &cached_proxydhcp, NULL );
|
|
|
|
/* Apply cached PXEBSACK, if any */
|
|
cachedhcp_apply ( &cached_pxebs, NULL );
|
|
|
|
/* Free any remaining cached packets */
|
|
if ( cached_dhcpack.dhcppkt ) {
|
|
DBGC ( colour, "CACHEDHCP %s unclaimed\n",
|
|
cached_dhcpack.name );
|
|
}
|
|
cachedhcp_free ( &cached_dhcpack );
|
|
cachedhcp_free ( &cached_proxydhcp );
|
|
cachedhcp_free ( &cached_pxebs );
|
|
}
|
|
|
|
/** Cached DHCPACK startup function */
|
|
struct startup_fn cachedhcp_startup_fn __startup_fn ( STARTUP_LATE ) = {
|
|
.name = "cachedhcp",
|
|
.startup = cachedhcp_startup,
|
|
};
|
|
|
|
/**
|
|
* Apply cached DHCPACK to network device, if applicable
|
|
*
|
|
* @v netdev Network device
|
|
* @ret rc Return status code
|
|
*/
|
|
static int cachedhcp_probe ( struct net_device *netdev ) {
|
|
|
|
/* Apply cached DHCPACK to network device, if applicable */
|
|
return cachedhcp_apply ( &cached_dhcpack, netdev );
|
|
}
|
|
|
|
/** Cached DHCP packet network device driver */
|
|
struct net_driver cachedhcp_driver __net_driver = {
|
|
.name = "cachedhcp",
|
|
.probe = cachedhcp_probe,
|
|
};
|