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/dhcppkt.c
Michael Brown 22001cb206 [settings] Explicitly separate the concept of a completed fetched setting
The fetch_setting() family of functions may currently modify the
definition of the specified setting (e.g. to add missing type
information).  Clean up this interface by requiring callers to provide
an explicit buffer to contain the completed definition of the fetched
setting, if required.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
2013-12-05 00:37:02 +00:00

306 lines
8.3 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.
*/
FILE_LICENCE ( GPL2_OR_LATER );
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <ipxe/netdevice.h>
#include <ipxe/dhcp.h>
#include <ipxe/dhcpopts.h>
#include <ipxe/dhcppkt.h>
/** @file
*
* DHCP packets
*
*/
/****************************************************************************
*
* DHCP packet raw interface
*
*/
/**
* Calculate used length of an IPv4 field within a DHCP packet
*
* @v data Field data
* @v len Length of field
* @ret used Used length of field
*/
static size_t used_len_ipv4 ( const void *data, size_t len __unused ) {
const struct in_addr *in = data;
return ( in->s_addr ? sizeof ( *in ) : 0 );
}
/**
* Calculate used length of a string field within a DHCP packet
*
* @v data Field data
* @v len Length of field
* @ret used Used length of field
*/
static size_t used_len_string ( const void *data, size_t len ) {
return strnlen ( data, len );
}
/** A dedicated field within a DHCP packet */
struct dhcp_packet_field {
/** Settings tag number */
unsigned int tag;
/** Offset within DHCP packet */
uint16_t offset;
/** Length of field */
uint16_t len;
/** Calculate used length of field
*
* @v data Field data
* @v len Length of field
* @ret used Used length of field
*/
size_t ( * used_len ) ( const void *data, size_t len );
};
/** Declare a dedicated field within a DHCP packet
*
* @v _tag Settings tag number
* @v _field Field name
* @v _used_len Function to calculate used length of field
*/
#define DHCP_PACKET_FIELD( _tag, _field, _used_len ) { \
.tag = (_tag), \
.offset = offsetof ( struct dhcphdr, _field ), \
.len = sizeof ( ( ( struct dhcphdr * ) 0 )->_field ), \
.used_len = _used_len, \
}
/** Dedicated fields within a DHCP packet */
static struct dhcp_packet_field dhcp_packet_fields[] = {
DHCP_PACKET_FIELD ( DHCP_EB_YIADDR, yiaddr, used_len_ipv4 ),
DHCP_PACKET_FIELD ( DHCP_EB_SIADDR, siaddr, used_len_ipv4 ),
DHCP_PACKET_FIELD ( DHCP_TFTP_SERVER_NAME, sname, used_len_string ),
DHCP_PACKET_FIELD ( DHCP_BOOTFILE_NAME, file, used_len_string ),
};
/**
* Get address of a DHCP packet field
*
* @v dhcphdr DHCP packet header
* @v field DHCP packet field
* @ret data Packet field data
*/
static inline void * dhcp_packet_field ( struct dhcphdr *dhcphdr,
struct dhcp_packet_field *field ) {
return ( ( ( void * ) dhcphdr ) + field->offset );
}
/**
* Find DHCP packet field corresponding to settings tag number
*
* @v tag Settings tag number
* @ret field DHCP packet field, or NULL
*/
static struct dhcp_packet_field *
find_dhcp_packet_field ( unsigned int tag ) {
struct dhcp_packet_field *field;
unsigned int i;
for ( i = 0 ; i < ( sizeof ( dhcp_packet_fields ) /
sizeof ( dhcp_packet_fields[0] ) ) ; i++ ) {
field = &dhcp_packet_fields[i];
if ( field->tag == tag )
return field;
}
return NULL;
}
/**
* Check applicability of DHCP setting
*
* @v dhcppkt DHCP packet
* @v tag Setting tag number
* @ret applies Setting applies within this settings block
*/
static int dhcppkt_applies ( struct dhcp_packet *dhcppkt __unused,
unsigned int tag ) {
return dhcpopt_applies ( tag );
}
/**
* Store value of DHCP packet setting
*
* @v dhcppkt DHCP packet
* @v tag Setting tag number
* @v data Setting data, or NULL to clear setting
* @v len Length of setting data
* @ret rc Return status code
*/
int dhcppkt_store ( struct dhcp_packet *dhcppkt, unsigned int tag,
const void *data, size_t len ) {
struct dhcp_packet_field *field;
void *field_data;
/* If this is a special field, fill it in */
if ( ( field = find_dhcp_packet_field ( tag ) ) != NULL ) {
if ( len > field->len )
return -ENOSPC;
field_data = dhcp_packet_field ( dhcppkt->dhcphdr, field );
memset ( field_data, 0, field->len );
memcpy ( dhcp_packet_field ( dhcppkt->dhcphdr, field ),
data, len );
/* Erase any equivalent option from the options block */
dhcpopt_store ( &dhcppkt->options, tag, NULL, 0 );
return 0;
}
/* Otherwise, use the generic options block */
return dhcpopt_store ( &dhcppkt->options, tag, data, len );
}
/**
* Fetch value of DHCP packet setting
*
* @v dhcppkt DHCP packet
* @v tag Setting tag number
* @v data Buffer to fill with setting data
* @v len Length of buffer
* @ret len Length of setting data, or negative error
*/
int dhcppkt_fetch ( struct dhcp_packet *dhcppkt, unsigned int tag,
void *data, size_t len ) {
struct dhcp_packet_field *field;
void *field_data;
size_t field_len = 0;
/* Identify special field, if any */
if ( ( field = find_dhcp_packet_field ( tag ) ) != NULL ) {
field_data = dhcp_packet_field ( dhcppkt->dhcphdr, field );
field_len = field->used_len ( field_data, field->len );
}
/* Return special field, if it exists and is populated */
if ( field_len ) {
if ( len > field_len )
len = field_len;
memcpy ( data, field_data, len );
return field_len;
}
/* Otherwise, use the generic options block */
return dhcpopt_fetch ( &dhcppkt->options, tag, data, len );
}
/****************************************************************************
*
* DHCP packet settings interface
*
*/
/**
* Check applicability of DHCP setting
*
* @v settings Settings block
* @v setting Setting
* @ret applies Setting applies within this settings block
*/
static int dhcppkt_settings_applies ( struct settings *settings,
const struct setting *setting ) {
struct dhcp_packet *dhcppkt =
container_of ( settings, struct dhcp_packet, settings );
return ( ( setting->scope == NULL ) &&
dhcppkt_applies ( dhcppkt, setting->tag ) );
}
/**
* Store value of DHCP 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 dhcppkt_settings_store ( struct settings *settings,
const struct setting *setting,
const void *data, size_t len ) {
struct dhcp_packet *dhcppkt =
container_of ( settings, struct dhcp_packet, settings );
return dhcppkt_store ( dhcppkt, setting->tag, data, len );
}
/**
* Fetch value of DHCP setting
*
* @v settings Settings block, or NULL to search all blocks
* @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 dhcppkt_settings_fetch ( struct settings *settings,
struct setting *setting,
void *data, size_t len ) {
struct dhcp_packet *dhcppkt =
container_of ( settings, struct dhcp_packet, settings );
return dhcppkt_fetch ( dhcppkt, setting->tag, data, len );
}
/** DHCP settings operations */
static struct settings_operations dhcppkt_settings_operations = {
.applies = dhcppkt_settings_applies,
.store = dhcppkt_settings_store,
.fetch = dhcppkt_settings_fetch,
};
/****************************************************************************
*
* Constructor
*
*/
/**
* Initialise DHCP packet
*
* @v dhcppkt DHCP packet structure to fill in
* @v data DHCP packet raw data
* @v max_len Length of raw data buffer
*
* Initialise a DHCP packet structure from a data buffer containing a
* DHCP packet.
*/
void dhcppkt_init ( struct dhcp_packet *dhcppkt, struct dhcphdr *data,
size_t len ) {
ref_init ( &dhcppkt->refcnt, NULL );
dhcppkt->dhcphdr = data;
dhcpopt_init ( &dhcppkt->options, &dhcppkt->dhcphdr->options,
( len - offsetof ( struct dhcphdr, options ) ),
dhcpopt_no_realloc );
settings_init ( &dhcppkt->settings, &dhcppkt_settings_operations,
&dhcppkt->refcnt, NULL );
}