Now successfully negotiates the whole DHCPDISCOVER/OFFER/REQUEST/ACK
cycle. :)
This commit is contained in:
parent
30d9bcdc6e
commit
7ca1bb0fbe
@ -12,6 +12,7 @@
|
|||||||
#include <gpxe/in.h>
|
#include <gpxe/in.h>
|
||||||
#include <gpxe/udp.h>
|
#include <gpxe/udp.h>
|
||||||
#include <gpxe/async.h>
|
#include <gpxe/async.h>
|
||||||
|
#include <gpxe/retry.h>
|
||||||
|
|
||||||
/** BOOTP/DHCP server port */
|
/** BOOTP/DHCP server port */
|
||||||
#define BOOTPS_PORT 67
|
#define BOOTPS_PORT 67
|
||||||
@ -407,6 +408,14 @@ struct dhcp_session {
|
|||||||
/** UDP connection for this session */
|
/** UDP connection for this session */
|
||||||
struct udp_connection udp;
|
struct udp_connection udp;
|
||||||
|
|
||||||
|
/** Network device being configured */
|
||||||
|
struct net_device *netdev;
|
||||||
|
|
||||||
|
/** Options obtained from server */
|
||||||
|
struct dhcp_option_block *options;
|
||||||
|
|
||||||
|
/** Transaction ID, in network-endian order */
|
||||||
|
uint32_t xid;
|
||||||
/** State of the session
|
/** State of the session
|
||||||
*
|
*
|
||||||
* This is a value for the @c DHCP_MESSAGE_TYPE option
|
* This is a value for the @c DHCP_MESSAGE_TYPE option
|
||||||
@ -415,11 +424,8 @@ struct dhcp_session {
|
|||||||
int state;
|
int state;
|
||||||
/** Asynchronous operation for this DHCP session */
|
/** Asynchronous operation for this DHCP session */
|
||||||
struct async_operation aop;
|
struct async_operation aop;
|
||||||
|
/** Retransmission timer */
|
||||||
/** Network device being configured */
|
struct retry_timer timer;
|
||||||
struct net_device *netdev;
|
|
||||||
/** Transaction ID, in network-endian order */
|
|
||||||
uint32_t xid;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
extern unsigned long dhcp_num_option ( struct dhcp_option *option );
|
extern unsigned long dhcp_num_option ( struct dhcp_option *option );
|
||||||
|
@ -109,7 +109,7 @@ static int set_dhcp_packet_option ( struct dhcp_packet *dhcppkt,
|
|||||||
unsigned int tag, const void *data,
|
unsigned int tag, const void *data,
|
||||||
size_t len ) {
|
size_t len ) {
|
||||||
struct dhcphdr *dhcphdr = dhcppkt->dhcphdr;
|
struct dhcphdr *dhcphdr = dhcppkt->dhcphdr;
|
||||||
struct dhcp_option_block *options;
|
struct dhcp_option_block *options = dhcppkt->options;
|
||||||
struct dhcp_option *option = NULL;
|
struct dhcp_option *option = NULL;
|
||||||
|
|
||||||
/* Special-case the magic options */
|
/* Special-case the magic options */
|
||||||
@ -123,14 +123,22 @@ static int set_dhcp_packet_option ( struct dhcp_packet *dhcppkt,
|
|||||||
case DHCP_EB_SIADDR:
|
case DHCP_EB_SIADDR:
|
||||||
memcpy ( &dhcphdr->siaddr, data, sizeof ( dhcphdr->siaddr ) );
|
memcpy ( &dhcphdr->siaddr, data, sizeof ( dhcphdr->siaddr ) );
|
||||||
return 0;
|
return 0;
|
||||||
|
case DHCP_MESSAGE_TYPE:
|
||||||
|
case DHCP_REQUESTED_ADDRESS:
|
||||||
|
/* These options have to be within the main options
|
||||||
|
* block. This doesn't seem to be required by the
|
||||||
|
* RFCs, but at least ISC dhcpd refuses to recognise
|
||||||
|
* them otherwise.
|
||||||
|
*/
|
||||||
|
options = &dhcppkt->options[OPTS_MAIN];
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
/* Continue processing as normal */
|
/* Continue processing as normal */
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Set option in first available options block */
|
/* Set option in first available options block */
|
||||||
for ( options = dhcppkt->options ;
|
for ( ; options < &dhcppkt->options[NUM_OPT_BLOCKS] ; options++ ) {
|
||||||
options < &dhcppkt->options[NUM_OPT_BLOCKS] ; options++ ) {
|
|
||||||
option = set_dhcp_option ( options, tag, data, len );
|
option = set_dhcp_option ( options, tag, data, len );
|
||||||
if ( option )
|
if ( option )
|
||||||
break;
|
break;
|
||||||
@ -144,7 +152,40 @@ static int set_dhcp_packet_option ( struct dhcp_packet *dhcppkt,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set options within DHCP packet
|
* Copy option into DHCP packet
|
||||||
|
*
|
||||||
|
* @v dhcppkt DHCP packet
|
||||||
|
* @v options DHCP option block, or NULL
|
||||||
|
* @v tag DHCP option tag to search for
|
||||||
|
* @v new_tag DHCP option tag to use for copied option
|
||||||
|
* @ret rc Return status code
|
||||||
|
*
|
||||||
|
* Copies a single option, if present, from the DHCP options block
|
||||||
|
* into a DHCP packet. The tag for the option may be changed if
|
||||||
|
* desired; this is required by other parts of the DHCP code.
|
||||||
|
*
|
||||||
|
* @c options may specify a single options block, or be left as NULL
|
||||||
|
* in order to search for the option within all registered options
|
||||||
|
* blocks.
|
||||||
|
*/
|
||||||
|
static int copy_dhcp_packet_option ( struct dhcp_packet *dhcppkt,
|
||||||
|
struct dhcp_option_block *options,
|
||||||
|
unsigned int tag, unsigned int new_tag ) {
|
||||||
|
struct dhcp_option *option;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
option = find_dhcp_option ( options, tag );
|
||||||
|
if ( option ) {
|
||||||
|
if ( ( rc = set_dhcp_packet_option ( dhcppkt, new_tag,
|
||||||
|
&option->data,
|
||||||
|
option->len ) ) != 0 )
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy options into DHCP packet
|
||||||
*
|
*
|
||||||
* @v dhcppkt DHCP packet
|
* @v dhcppkt DHCP packet
|
||||||
* @v options DHCP option block, or NULL
|
* @v options DHCP option block, or NULL
|
||||||
@ -158,12 +199,11 @@ static int set_dhcp_packet_option ( struct dhcp_packet *dhcppkt,
|
|||||||
* @c options may specify a single options block, or be left as NULL
|
* @c options may specify a single options block, or be left as NULL
|
||||||
* in order to copy options from all registered options blocks.
|
* in order to copy options from all registered options blocks.
|
||||||
*/
|
*/
|
||||||
static int set_dhcp_packet_encap_options ( struct dhcp_packet *dhcppkt,
|
static int copy_dhcp_packet_encap_options ( struct dhcp_packet *dhcppkt,
|
||||||
struct dhcp_option_block *options,
|
struct dhcp_option_block *options,
|
||||||
unsigned int encapsulator ) {
|
unsigned int encapsulator ) {
|
||||||
unsigned int subtag;
|
unsigned int subtag;
|
||||||
unsigned int tag;
|
unsigned int tag;
|
||||||
struct dhcp_option *option;
|
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
for ( subtag = DHCP_MIN_OPTION; subtag <= DHCP_MAX_OPTION; subtag++ ) {
|
for ( subtag = DHCP_MIN_OPTION; subtag <= DHCP_MAX_OPTION; subtag++ ) {
|
||||||
@ -172,19 +212,15 @@ static int set_dhcp_packet_encap_options ( struct dhcp_packet *dhcppkt,
|
|||||||
case DHCP_EB_ENCAP:
|
case DHCP_EB_ENCAP:
|
||||||
case DHCP_VENDOR_ENCAP:
|
case DHCP_VENDOR_ENCAP:
|
||||||
/* Process encapsulated options field */
|
/* Process encapsulated options field */
|
||||||
if ( ( rc = set_dhcp_packet_encap_options ( dhcppkt,
|
if ( ( rc = copy_dhcp_packet_encap_options ( dhcppkt,
|
||||||
options,
|
options,
|
||||||
tag )) !=0)
|
tag)) !=0)
|
||||||
return rc;
|
return rc;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
/* Copy option to reassembled packet */
|
/* Copy option to reassembled packet */
|
||||||
option = find_dhcp_option ( options, tag );
|
if ( ( rc = copy_dhcp_packet_option ( dhcppkt, options,
|
||||||
if ( ! option )
|
tag, tag ) ) !=0)
|
||||||
break;
|
|
||||||
if ( ( rc = set_dhcp_packet_option ( dhcppkt, tag,
|
|
||||||
&option->data,
|
|
||||||
option->len)) !=0)
|
|
||||||
return rc;
|
return rc;
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
@ -194,7 +230,7 @@ static int set_dhcp_packet_encap_options ( struct dhcp_packet *dhcppkt,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set options within DHCP packet
|
* Copy options into DHCP packet
|
||||||
*
|
*
|
||||||
* @v dhcppkt DHCP packet
|
* @v dhcppkt DHCP packet
|
||||||
* @v options DHCP option block, or NULL
|
* @v options DHCP option block, or NULL
|
||||||
@ -207,9 +243,9 @@ static int set_dhcp_packet_encap_options ( struct dhcp_packet *dhcppkt,
|
|||||||
* @c options may specify a single options block, or be left as NULL
|
* @c options may specify a single options block, or be left as NULL
|
||||||
* in order to copy options from all registered options blocks.
|
* in order to copy options from all registered options blocks.
|
||||||
*/
|
*/
|
||||||
static int set_dhcp_packet_options ( struct dhcp_packet *dhcppkt,
|
static int copy_dhcp_packet_options ( struct dhcp_packet *dhcppkt,
|
||||||
struct dhcp_option_block *options ) {
|
struct dhcp_option_block *options ) {
|
||||||
return set_dhcp_packet_encap_options ( dhcppkt, options, 0 );
|
return copy_dhcp_packet_encap_options ( dhcppkt, options, 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -224,7 +260,7 @@ static int set_dhcp_packet_options ( struct dhcp_packet *dhcppkt,
|
|||||||
*
|
*
|
||||||
* Creates a DHCP packet in the specified buffer, and fills out a @c
|
* Creates a DHCP packet in the specified buffer, and fills out a @c
|
||||||
* dhcp_packet structure that can be passed to
|
* dhcp_packet structure that can be passed to
|
||||||
* set_dhcp_packet_option() or set_dhcp_packet_options().
|
* set_dhcp_packet_option() or copy_dhcp_packet_options().
|
||||||
*/
|
*/
|
||||||
static int create_dhcp_packet ( struct dhcp_session *dhcp, uint8_t msgtype,
|
static int create_dhcp_packet ( struct dhcp_session *dhcp, uint8_t msgtype,
|
||||||
void *data, size_t max_len,
|
void *data, size_t max_len,
|
||||||
@ -232,6 +268,7 @@ static int create_dhcp_packet ( struct dhcp_session *dhcp, uint8_t msgtype,
|
|||||||
struct dhcphdr *dhcphdr = data;
|
struct dhcphdr *dhcphdr = data;
|
||||||
static const uint8_t overloading = ( DHCP_OPTION_OVERLOAD_FILE |
|
static const uint8_t overloading = ( DHCP_OPTION_OVERLOAD_FILE |
|
||||||
DHCP_OPTION_OVERLOAD_SNAME );
|
DHCP_OPTION_OVERLOAD_SNAME );
|
||||||
|
int rc;
|
||||||
|
|
||||||
/* Sanity check */
|
/* Sanity check */
|
||||||
if ( max_len < sizeof ( *dhcphdr ) )
|
if ( max_len < sizeof ( *dhcphdr ) )
|
||||||
@ -263,14 +300,11 @@ static int create_dhcp_packet ( struct dhcp_session *dhcp, uint8_t msgtype,
|
|||||||
sizeof ( overloading ) ) == NULL )
|
sizeof ( overloading ) ) == NULL )
|
||||||
return -ENOSPC;
|
return -ENOSPC;
|
||||||
|
|
||||||
/* Set DHCP_MESSAGE_TYPE option within the main options block.
|
/* Set DHCP_MESSAGE_TYPE option */
|
||||||
* This doesn't seem to be required by the RFCs, but at least
|
if ( ( rc = set_dhcp_packet_option ( dhcppkt, DHCP_MESSAGE_TYPE,
|
||||||
* ISC dhcpd and ethereal refuse to recognise it otherwise.
|
&msgtype,
|
||||||
*/
|
sizeof ( msgtype ) ) ) != 0 )
|
||||||
if ( set_dhcp_option ( &dhcppkt->options[OPTS_MAIN],
|
return rc;
|
||||||
DHCP_MESSAGE_TYPE, &msgtype,
|
|
||||||
sizeof ( msgtype ) ) == NULL )
|
|
||||||
return -ENOSPC;
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -435,6 +469,17 @@ udp_to_dhcp ( struct udp_connection *conn ) {
|
|||||||
return container_of ( conn, struct dhcp_session, udp );
|
return container_of ( conn, struct dhcp_session, udp );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark DHCP session as complete
|
||||||
|
*
|
||||||
|
* @v dhcp DHCP session
|
||||||
|
* @v rc Return status code
|
||||||
|
*/
|
||||||
|
static void dhcp_done ( struct dhcp_session *dhcp, int rc ) {
|
||||||
|
/* Mark async operation as complete */
|
||||||
|
async_done ( &dhcp->aop, rc );
|
||||||
|
}
|
||||||
|
|
||||||
/** Address for transmitting DHCP requests */
|
/** Address for transmitting DHCP requests */
|
||||||
static struct sockaddr sa_dhcp_server = {
|
static struct sockaddr sa_dhcp_server = {
|
||||||
.sa_family = AF_INET,
|
.sa_family = AF_INET,
|
||||||
@ -470,12 +515,28 @@ static void dhcp_senddata ( struct udp_connection *conn,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Copy in options common to all requests */
|
/* Copy in options common to all requests */
|
||||||
if ( ( rc = set_dhcp_packet_options ( &dhcppkt,
|
if ( ( rc = copy_dhcp_packet_options ( &dhcppkt,
|
||||||
&dhcp_request_options ) ) != 0 ){
|
&dhcp_request_options ) ) != 0){
|
||||||
DBG ( "Could not set common DHCP options\n" );
|
DBG ( "Could not set common DHCP options\n" );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Copy any required options from previous server repsonse */
|
||||||
|
if ( dhcp->options ) {
|
||||||
|
if ( ( rc = copy_dhcp_packet_option ( &dhcppkt, dhcp->options,
|
||||||
|
DHCP_SERVER_IDENTIFIER,
|
||||||
|
DHCP_SERVER_IDENTIFIER ) ) != 0 ) {
|
||||||
|
DBG ( "Could not set server identifier option\n" );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( ( rc = copy_dhcp_packet_option ( &dhcppkt, dhcp->options,
|
||||||
|
DHCP_EB_YIADDR,
|
||||||
|
DHCP_REQUESTED_ADDRESS ) ) != 0 ) {
|
||||||
|
DBG ( "Could not set requested address option\n" );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Transmit the packet */
|
/* Transmit the packet */
|
||||||
if ( ( rc = udp_sendto ( conn, &sa_dhcp_server,
|
if ( ( rc = udp_sendto ( conn, &sa_dhcp_server,
|
||||||
dhcppkt.dhcphdr, dhcppkt.len ) ) != 0 ) {
|
dhcppkt.dhcphdr, dhcppkt.len ) ) != 0 ) {
|
||||||
@ -484,6 +545,33 @@ static void dhcp_senddata ( struct udp_connection *conn,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transmit DHCP request
|
||||||
|
*
|
||||||
|
* @v dhcp DHCP session
|
||||||
|
*/
|
||||||
|
static void dhcp_send_request ( struct dhcp_session *dhcp ) {
|
||||||
|
start_timer ( &dhcp->timer );
|
||||||
|
udp_senddata ( &dhcp->udp );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle DHCP retry timer expiry
|
||||||
|
*
|
||||||
|
* @v timer DHCP retry timer
|
||||||
|
* @v fail Failure indicator
|
||||||
|
*/
|
||||||
|
static void dhcp_timer_expired ( struct retry_timer *timer, int fail ) {
|
||||||
|
struct dhcp_session *dhcp =
|
||||||
|
container_of ( timer, struct dhcp_session, timer );
|
||||||
|
|
||||||
|
if ( fail ) {
|
||||||
|
dhcp_done ( dhcp, -ETIMEDOUT );
|
||||||
|
} else {
|
||||||
|
dhcp_send_request ( dhcp );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Receive new data
|
* Receive new data
|
||||||
*
|
*
|
||||||
@ -496,6 +584,7 @@ static void dhcp_newdata ( struct udp_connection *conn,
|
|||||||
struct dhcp_session *dhcp = udp_to_dhcp ( conn );
|
struct dhcp_session *dhcp = udp_to_dhcp ( conn );
|
||||||
struct dhcphdr *dhcphdr = data;
|
struct dhcphdr *dhcphdr = data;
|
||||||
struct dhcp_option_block *options;
|
struct dhcp_option_block *options;
|
||||||
|
unsigned int msgtype;
|
||||||
|
|
||||||
/* Check for matching transaction ID */
|
/* Check for matching transaction ID */
|
||||||
if ( dhcphdr->xid != dhcp->xid ) {
|
if ( dhcphdr->xid != dhcp->xid ) {
|
||||||
@ -511,12 +600,42 @@ static void dhcp_newdata ( struct udp_connection *conn,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DBG ( "Received %s\n",
|
/* Determine message type */
|
||||||
dhcp_msgtype_name ( find_dhcp_num_option ( options,
|
msgtype = find_dhcp_num_option ( options, DHCP_MESSAGE_TYPE );
|
||||||
DHCP_MESSAGE_TYPE )));
|
DBG ( "Received %s\n", dhcp_msgtype_name ( msgtype ) );
|
||||||
|
|
||||||
/* Proof of concept: just dump out the parsed options */
|
/* Handle DHCP reply */
|
||||||
hex_dump ( options->data, options->len );
|
switch ( dhcp->state ) {
|
||||||
|
case DHCPDISCOVER:
|
||||||
|
if ( msgtype != DHCPOFFER )
|
||||||
|
goto out_discard;
|
||||||
|
dhcp->state = DHCPREQUEST;
|
||||||
|
break;
|
||||||
|
case DHCPREQUEST:
|
||||||
|
if ( msgtype != DHCPACK )
|
||||||
|
goto out_discard;
|
||||||
|
dhcp->state = DHCPACK;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert ( 0 );
|
||||||
|
goto out_discard;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stop timer and update stored options */
|
||||||
|
stop_timer ( &dhcp->timer );
|
||||||
|
if ( dhcp->options )
|
||||||
|
free_dhcp_options ( dhcp->options );
|
||||||
|
dhcp->options = options;
|
||||||
|
|
||||||
|
/* Transmit next packet, or terminate session */
|
||||||
|
if ( dhcp->state < DHCPACK ) {
|
||||||
|
dhcp_send_request ( dhcp );
|
||||||
|
} else {
|
||||||
|
dhcp_done ( dhcp, 0 );
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
|
||||||
|
out_discard:
|
||||||
free_dhcp_options ( options );
|
free_dhcp_options ( options );
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -535,7 +654,9 @@ static struct udp_operations dhcp_udp_operations = {
|
|||||||
struct async_operation * start_dhcp ( struct dhcp_session *dhcp ) {
|
struct async_operation * start_dhcp ( struct dhcp_session *dhcp ) {
|
||||||
int rc;
|
int rc;
|
||||||
|
|
||||||
|
/* Initialise DHCP session */
|
||||||
dhcp->udp.udp_op = &dhcp_udp_operations;
|
dhcp->udp.udp_op = &dhcp_udp_operations;
|
||||||
|
dhcp->timer.expired = dhcp_timer_expired;
|
||||||
dhcp->state = DHCPDISCOVER;
|
dhcp->state = DHCPDISCOVER;
|
||||||
/* Use least significant 32 bits of link-layer address as XID */
|
/* Use least significant 32 bits of link-layer address as XID */
|
||||||
memcpy ( &dhcp->xid, ( dhcp->netdev->ll_addr
|
memcpy ( &dhcp->xid, ( dhcp->netdev->ll_addr
|
||||||
@ -549,7 +670,7 @@ struct async_operation * start_dhcp ( struct dhcp_session *dhcp ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Proof of concept: just send a single DHCPDISCOVER */
|
/* Proof of concept: just send a single DHCPDISCOVER */
|
||||||
udp_senddata ( &dhcp->udp );
|
dhcp_send_request ( dhcp );
|
||||||
|
|
||||||
out:
|
out:
|
||||||
return &dhcp->aop;
|
return &dhcp->aop;
|
||||||
|
Reference in New Issue
Block a user