From 07dc294de8e984b450297037b53ac33b038dcd7b Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Wed, 27 Jun 2007 23:20:36 +0100 Subject: [PATCH] Update DHCP to use data-xfer interface (not yet tested). --- src/core/nvo.c | 4 +- src/include/gpxe/dhcp.h | 63 +++++---- src/include/gpxe/xfer.h | 2 + src/net/dhcpopts.c | 11 +- src/net/udp.c | 11 +- src/net/udp/dhcp.c | 294 ++++++++++++++++++++++++++-------------- src/tests/dhcptest.c | 2 +- src/usr/dhcpmgmt.c | 11 +- 8 files changed, 250 insertions(+), 148 deletions(-) diff --git a/src/core/nvo.c b/src/core/nvo.c index 14ed4e19..8c6c9c0f 100644 --- a/src/core/nvo.c +++ b/src/core/nvo.c @@ -194,7 +194,7 @@ int nvo_register ( struct nvo_block *nvo ) { return 0; err: - free_dhcp_options ( nvo->options ); + dhcpopt_put ( nvo->options ); nvo->options = NULL; return rc; } @@ -208,7 +208,7 @@ void nvo_unregister ( struct nvo_block *nvo ) { if ( nvo->options ) { unregister_dhcp_options ( nvo->options ); - free_dhcp_options ( nvo->options ); + dhcpopt_put ( nvo->options ); nvo->options = NULL; } diff --git a/src/include/gpxe/dhcp.h b/src/include/gpxe/dhcp.h index 16609baf..1f687425 100644 --- a/src/include/gpxe/dhcp.h +++ b/src/include/gpxe/dhcp.h @@ -10,9 +10,10 @@ #include #include #include -#include -#include -#include +#include + +struct net_device; +struct job_interface; /** BOOTP/DHCP server port */ #define BOOTPS_PORT 67 @@ -312,6 +313,8 @@ struct dhcp_option { /** A DHCP options block */ struct dhcp_option_block { + /** Reference counter */ + struct refcnt refcnt; /** List of option blocks */ struct list_head list; /** Option block raw data */ @@ -413,6 +416,13 @@ struct dhcphdr { /** DHCP magic cookie */ #define DHCP_MAGIC_COOKIE 0x63825363UL +/** DHCP minimum packet length + * + * This is the mandated minimum packet length that a DHCP participant + * must be prepared to receive. + */ +#define DHCP_MIN_LEN 552 + /** DHCP packet option block fill order * * This is the order in which option blocks are filled when @@ -448,30 +458,27 @@ struct dhcp_packet { struct dhcp_option_block options[NUM_OPT_BLOCKS]; }; -struct udp_connection {}; +/** + * Get reference to DHCP options block + * + * @v options DHCP options block + * @ret options DHCP options block + */ +static inline __attribute__ (( always_inline )) struct dhcp_option_block * +dhcpopt_get ( struct dhcp_option_block *options ) { + ref_get ( &options->refcnt ); + return options; +} -/** A DHCP session */ -struct dhcp_session { - /** UDP connection for this session */ - struct udp_connection udp; - - /** Network device being configured */ - struct net_device *netdev; - - /** Options obtained from server */ - struct dhcp_option_block *options; - - /** State of the session - * - * This is a value for the @c DHCP_MESSAGE_TYPE option - * (e.g. @c DHCPDISCOVER). - */ - int state; - /** Asynchronous operation for this DHCP session */ - struct async async; - /** Retransmission timer */ - struct retry_timer timer; -}; +/** + * Drop reference to DHCP options block + * + * @v options DHCP options block + */ +static inline __attribute__ (( always_inline )) void +dhcpopt_put ( struct dhcp_option_block *options ) { + ref_put ( &options->refcnt ); +} extern unsigned long dhcp_num_option ( struct dhcp_option *option ); extern void dhcp_ipv4_option ( struct dhcp_option *option, @@ -485,7 +492,6 @@ extern void unregister_dhcp_options ( struct dhcp_option_block *options ); extern void init_dhcp_options ( struct dhcp_option_block *options, void *data, size_t max_len ); extern struct dhcp_option_block * alloc_dhcp_options ( size_t max_len ); -extern void free_dhcp_options ( struct dhcp_option_block *options ); extern struct dhcp_option * set_dhcp_option ( struct dhcp_option_block *options, unsigned int tag, const void *data, size_t len ); @@ -506,6 +512,7 @@ extern int create_dhcp_packet ( struct net_device *netdev, uint8_t msgtype, struct dhcp_packet *dhcppkt ); extern int copy_dhcp_packet_options ( struct dhcp_packet *dhcppkt, struct dhcp_option_block *options ); -extern int start_dhcp ( struct dhcp_session *dhcp, struct async *parent ); +extern int start_dhcp ( struct job_interface *job, struct net_device *netdev, + int (*register_options) ( struct dhcp_option_block * )); #endif /* _GPXE_DHCP_H */ diff --git a/src/include/gpxe/xfer.h b/src/include/gpxe/xfer.h index 0224613b..55c5b176 100644 --- a/src/include/gpxe/xfer.h +++ b/src/include/gpxe/xfer.h @@ -115,6 +115,8 @@ struct xfer_metadata { struct sockaddr *src; /** Destination socket address, or NULL */ struct sockaddr *dest; + /** Network device, or NULL */ + struct net_device *netdev; }; /** Basis positions for seek() events */ diff --git a/src/net/dhcpopts.c b/src/net/dhcpopts.c index b2a22666..bb977317 100644 --- a/src/net/dhcpopts.c +++ b/src/net/dhcpopts.c @@ -280,6 +280,7 @@ void register_dhcp_options ( struct dhcp_option_block *options ) { if ( options->priority > existing->priority ) break; } + dhcpopt_get ( options ); list_add_tail ( &options->list, &existing->list ); } @@ -290,6 +291,7 @@ void register_dhcp_options ( struct dhcp_option_block *options ) { */ void unregister_dhcp_options ( struct dhcp_option_block *options ) { list_del ( &options->list ); + dhcpopt_put ( options ); } /** @@ -337,15 +339,6 @@ struct dhcp_option_block * alloc_dhcp_options ( size_t max_len ) { return options; } -/** - * Free DHCP options block - * - * @v options DHCP option block - */ -void free_dhcp_options ( struct dhcp_option_block *options ) { - free ( options ); -} - /** * Resize a DHCP option * diff --git a/src/net/udp.c b/src/net/udp.c index 404dd727..c99a3eb6 100644 --- a/src/net/udp.c +++ b/src/net/udp.c @@ -183,17 +183,16 @@ static void udp_close ( struct udp_connection *udp, int rc ) { * @v iobuf I/O buffer * @v src_port Source port, or 0 to use default * @v dest Destination address, or NULL to use default + * @v netdev Network device, or NULL to use default * @ret rc Return status code */ static int udp_tx ( struct udp_connection *udp, struct io_buffer *iobuf, - unsigned int src_port, struct sockaddr_tcpip *dest ) { + unsigned int src_port, struct sockaddr_tcpip *dest, + struct net_device *netdev ) { struct udp_header *udphdr; - struct net_device *netdev = NULL; size_t len; int rc; -#warning "netdev?" - /* Check we can accommodate the header */ if ( ( rc = iob_ensure_headroom ( iobuf, UDP_MAX_HLEN ) ) != 0 ) { free_iob ( iobuf ); @@ -394,6 +393,7 @@ static int udp_xfer_deliver_iob ( struct xfer_interface *xfer, container_of ( xfer, struct udp_connection, xfer ); struct sockaddr_tcpip *src; struct sockaddr_tcpip *dest = NULL; + struct net_device *netdev = NULL; unsigned int src_port = 0; /* Apply xfer metadata */ @@ -402,10 +402,11 @@ static int udp_xfer_deliver_iob ( struct xfer_interface *xfer, if ( src ) src_port = src->st_port; dest = ( struct sockaddr_tcpip * ) meta->dest; + netdev = meta->netdev; } /* Transmit data, if possible */ - udp_tx ( udp, iobuf, src_port, dest ); + udp_tx ( udp, iobuf, src_port, dest, netdev ); return 0; } diff --git a/src/net/udp/dhcp.c b/src/net/udp/dhcp.c index 4ecb17bf..e5a95b38 100644 --- a/src/net/udp/dhcp.c +++ b/src/net/udp/dhcp.c @@ -17,12 +17,16 @@ */ #include +#include #include #include #include #include #include -#include +#include +#include +#include +#include #include /** @file @@ -387,7 +391,6 @@ static void merge_dhcp_field ( struct dhcp_option_block *options, /** * Parse DHCP packet and construct DHCP options block * - * @v dhcp DHCP session * @v dhcphdr DHCP packet * @v len Length of DHCP packet * @ret options DHCP options block, or NULL @@ -407,8 +410,7 @@ static void merge_dhcp_field ( struct dhcp_option_block *options, * options block; it is the responsibility of the caller to eventually * free this memory. */ -static struct dhcp_option_block * dhcp_parse ( struct dhcp_session *dhcp, - struct dhcphdr *dhcphdr, +static struct dhcp_option_block * dhcp_parse ( const struct dhcphdr *dhcphdr, size_t len ) { struct dhcp_option_block *options; size_t options_len; @@ -443,8 +445,8 @@ static struct dhcp_option_block * dhcp_parse ( struct dhcp_session *dhcp, /* Allocate empty options block of required size */ options = alloc_dhcp_options ( options_len ); if ( ! options ) { - DBGC ( dhcp, "DHCP %p could not allocate %d-byte option " - "block\n", dhcp, options_len ); + DBG ( "DHCP could not allocate %d-byte option block\n", + options_len ); return NULL; } @@ -488,12 +490,45 @@ static struct dhcp_option_block * dhcp_parse ( struct dhcp_session *dhcp, * */ -static inline struct dhcp_session * -udp_to_dhcp ( struct udp_connection *conn ) { - return container_of ( conn, struct dhcp_session, udp ); -} +/** A DHCP session */ +struct dhcp_session { + /** Reference counter */ + struct refcnt refcnt; + /** Job control interface */ + struct job_interface job; + /** Data transfer interface */ + struct xfer_interface xfer; -#if 0 + /** Network device being configured */ + struct net_device *netdev; + /** Option block registration routine */ + int ( * register_options ) ( struct dhcp_option_block *options ); + + /** State of the session + * + * This is a value for the @c DHCP_MESSAGE_TYPE option + * (e.g. @c DHCPDISCOVER). + */ + int state; + /** Options obtained from server */ + struct dhcp_option_block *options; + /** Retransmission timer */ + struct retry_timer timer; +}; + +/** + * Free DHCP session + * + * @v refcnt Reference counter + */ +static void dhcp_free ( struct refcnt *refcnt ) { + struct dhcp_session *dhcp = + container_of ( refcnt, struct dhcp_session, refcnt ); + + netdev_put ( dhcp->netdev ); + dhcpopt_put ( dhcp->options ); + free ( dhcp ); +} /** * Mark DHCP session as complete @@ -501,50 +536,38 @@ udp_to_dhcp ( struct udp_connection *conn ) { * @v dhcp DHCP session * @v rc Return status code */ -static void dhcp_done ( struct dhcp_session *dhcp, int rc ) { +static void dhcp_finished ( struct dhcp_session *dhcp, int rc ) { - /* Free up options if we failed */ - if ( rc != 0 ) { - if ( dhcp->options ) { - free_dhcp_options ( dhcp->options ); - dhcp->options = NULL; - } - } + /* Block futher incoming messages */ + job_nullify ( &dhcp->job ); + xfer_nullify ( &dhcp->xfer ); /* Stop retry timer */ stop_timer ( &dhcp->timer ); - /* Close UDP connection */ - udp_close ( &dhcp->udp ); - - /* Mark async operation as complete */ - async_done ( &dhcp->async, rc ); + /* Free resources and close interfaces */ + xfer_close ( &dhcp->xfer, rc ); + job_done ( &dhcp->job, rc ); } -/** Address for transmitting DHCP requests */ -static union { - struct sockaddr_tcpip st; - struct sockaddr_in sin; -} sa_dhcp_server = { - .sin = { - .sin_family = AF_INET, - .sin_addr.s_addr = INADDR_BROADCAST, - .sin_port = htons ( BOOTPS_PORT ), - }, -}; +/**************************************************************************** + * + * Data transfer interface + * + */ /** * Transmit DHCP request * - * @v conn UDP connection - * @v buf Temporary data buffer - * @v len Length of temporary data buffer + * @v dhcp DHCP session * @ret rc Return status code */ -static int dhcp_senddata ( struct udp_connection *conn, - void *buf, size_t len ) { - struct dhcp_session *dhcp = udp_to_dhcp ( conn ); +static int dhcp_send_request ( struct dhcp_session *dhcp ) { + struct xfer_metadata meta = { + .netdev = dhcp->netdev, + }; struct dhcp_packet dhcppkt; + struct io_buffer *iobuf; int rc; DBGC ( dhcp, "DHCP %p transmitting %s\n", @@ -553,12 +576,23 @@ static int dhcp_senddata ( struct udp_connection *conn, assert ( ( dhcp->state == DHCPDISCOVER ) || ( dhcp->state == DHCPREQUEST ) ); + /* Start retry timer. Do this first so that failures to + * transmit will be retried. + */ + start_timer ( &dhcp->timer ); + + /* Allocate buffer for packet */ + iobuf = xfer_alloc_iob ( &dhcp->xfer, DHCP_MIN_LEN ); + if ( ! iobuf ) + return -ENOMEM; + /* Create DHCP packet in temporary buffer */ - if ( ( rc = create_dhcp_packet ( dhcp->netdev, dhcp->state, buf, len, + if ( ( rc = create_dhcp_packet ( dhcp->netdev, dhcp->state, + iobuf->data, iob_tailroom ( iobuf ), &dhcppkt ) ) != 0 ) { DBGC ( dhcp, "DHCP %p could not create DHCP packet: %s\n", dhcp, strerror ( rc ) ); - return rc; + goto done; } /* Copy in options common to all requests */ @@ -566,7 +600,7 @@ static int dhcp_senddata ( struct udp_connection *conn, &dhcp_request_options ) ) != 0){ DBGC ( dhcp, "DHCP %p could not set common DHCP options: %s\n", dhcp, strerror ( rc ) ); - return rc; + goto done; } /* Copy any required options from previous server repsonse */ @@ -576,36 +610,30 @@ static int dhcp_senddata ( struct udp_connection *conn, DHCP_SERVER_IDENTIFIER ) ) != 0 ) { DBGC ( dhcp, "DHCP %p could not set server identifier " "option: %s\n", dhcp, strerror ( rc ) ); - return rc; + goto done; } if ( ( rc = copy_dhcp_packet_option ( &dhcppkt, dhcp->options, DHCP_EB_YIADDR, DHCP_REQUESTED_ADDRESS ) ) != 0 ) { DBGC ( dhcp, "DHCP %p could not set requested address " "option: %s\n", dhcp, strerror ( rc ) ); - return rc; + goto done; } } /* Transmit the packet */ - if ( ( rc = udp_sendto_via ( conn, &sa_dhcp_server.st, dhcp->netdev, - dhcppkt.dhcphdr, dhcppkt.len ) ) != 0 ) { + iob_put ( iobuf, dhcppkt.len ); + rc = xfer_deliver_iob_meta ( &dhcp->xfer, iobuf, &meta ); + iobuf = NULL; + if ( rc != 0 ) { DBGC ( dhcp, "DHCP %p could not transmit UDP packet: %s\n", dhcp, strerror ( rc ) ); - return rc; + goto done; } - return 0; -} - -/** - * Transmit DHCP request - * - * @v dhcp DHCP session - */ -static void dhcp_send_request ( struct dhcp_session *dhcp ) { - start_timer ( &dhcp->timer ); - udp_senddata ( &dhcp->udp ); + done: + free_iob ( iobuf ); + return rc; } /** @@ -619,7 +647,7 @@ static void dhcp_timer_expired ( struct retry_timer *timer, int fail ) { container_of ( timer, struct dhcp_session, timer ); if ( fail ) { - dhcp_done ( dhcp, -ETIMEDOUT ); + dhcp_finished ( dhcp, -ETIMEDOUT ); } else { dhcp_send_request ( dhcp ); } @@ -628,17 +656,17 @@ static void dhcp_timer_expired ( struct retry_timer *timer, int fail ) { /** * Receive new data * - * @v udp UDP connection + * @v xfer Data transfer interface + * @v iobuf I/O buffer * @v data Received data * @v len Length of received data - * @v st_src Partially-filled source address - * @v st_dest Partially-filled destination address + * @ret rc Return status code */ -static int dhcp_newdata ( struct udp_connection *conn, void *data, size_t len, - struct sockaddr_tcpip *st_src __unused, - struct sockaddr_tcpip *st_dest __unused ) { - struct dhcp_session *dhcp = udp_to_dhcp ( conn ); - struct dhcphdr *dhcphdr = data; +static int dhcp_deliver_raw ( struct xfer_interface *xfer, + const void *data, size_t len ) { + struct dhcp_session *dhcp = + container_of ( xfer, struct dhcp_session, xfer ); + const struct dhcphdr *dhcphdr = data; struct dhcp_option_block *options; unsigned int msgtype; @@ -651,7 +679,7 @@ static int dhcp_newdata ( struct udp_connection *conn, void *data, size_t len, }; /* Parse packet and create options structure */ - options = dhcp_parse ( dhcp, dhcphdr, len ); + options = dhcp_parse ( dhcphdr, len ); if ( ! options ) { DBGC ( dhcp, "DHCP %p could not parse DHCP packet\n", dhcp ); return -EINVAL; @@ -682,58 +710,120 @@ static int dhcp_newdata ( struct udp_connection *conn, void *data, size_t len, /* Stop timer and update stored options */ stop_timer ( &dhcp->timer ); if ( dhcp->options ) - free_dhcp_options ( dhcp->options ); + dhcpopt_put ( dhcp->options ); dhcp->options = options; /* Transmit next packet, or terminate session */ if ( dhcp->state < DHCPACK ) { dhcp_send_request ( dhcp ); } else { - dhcp_done ( dhcp, 0 ); + dhcp->register_options ( dhcp->options ); + dhcp_finished ( dhcp, 0 ); } return 0; out_discard: - free_dhcp_options ( options ); + dhcpopt_put ( options ); return 0; } -/** DHCP UDP operations */ -static struct udp_operations dhcp_udp_operations = { - .senddata = dhcp_senddata, - .newdata = dhcp_newdata, +/** DHCP data transfer interface operations */ +static struct xfer_interface_operations dhcp_xfer_operations = { + .close = ignore_xfer_close, + .vredirect = xfer_vopen, + .request = ignore_xfer_request, + .seek = ignore_xfer_seek, + .deliver_iob = xfer_deliver_as_raw, + .deliver_raw = dhcp_deliver_raw, }; -/** - * Initiate DHCP on a network interface +/**************************************************************************** * - * @v dhcp DHCP session - * @v parent Parent asynchronous operation - * @ret rc Return status code + * Job control interface * - * If the DHCP operation completes successfully, the - * dhcp_session::options field will be filled in with the resulting - * options block. The caller takes responsibility for eventually - * calling free_dhcp_options(). */ -int start_dhcp ( struct dhcp_session *dhcp, struct async *parent ) { - int rc; - /* Initialise DHCP session */ - dhcp->udp.udp_op = &dhcp_udp_operations; - dhcp->timer.expired = dhcp_timer_expired; - dhcp->state = DHCPDISCOVER; +/** + * Handle kill() event received via job control interface + * + * @v job DHCP job control interface + */ +static void dhcp_job_kill ( struct job_interface *job ) { + struct dhcp_session *dhcp = + container_of ( job, struct dhcp_session, job ); - /* Bind to local port */ - if ( ( rc = udp_open ( &dhcp->udp, htons ( BOOTPC_PORT ) ) ) != 0 ) - return rc; - - /* Proof of concept: just send a single DHCPDISCOVER */ - dhcp_send_request ( dhcp ); - - async_init ( &dhcp->async, &default_async_operations, parent ); - return 0; + /* Terminate DHCP session */ + dhcp_finished ( dhcp, -ECANCELED ); } +/** DHCP job control interface operations */ +static struct job_interface_operations dhcp_job_operations = { + .start = ignore_job_start, + .done = ignore_job_done, + .kill = dhcp_job_kill, + .progress = ignore_job_progress, +}; -#endif +/**************************************************************************** + * + * Instantiator + * + */ + +/** + * Start DHCP on a network device + * + * @v job Job control interface + * @v netdev Network device + * @v register_options DHCP option block registration routine + * @ret rc Return status code + * + * Starts DHCP on the specified network device. If successful, the @c + * register_options() routine will be called with the acquired + * options. + */ +int start_dhcp ( struct job_interface *job, struct net_device *netdev, + int ( * register_options ) ( struct dhcp_option_block * ) ) { + static struct sockaddr_in server = { + .sin_family = AF_INET, + .sin_addr.s_addr = INADDR_BROADCAST, + .sin_port = htons ( BOOTPS_PORT ), + }; + static struct sockaddr_in client = { + .sin_family = AF_INET, + .sin_port = htons ( BOOTPC_PORT ), + }; + struct dhcp_session *dhcp; + int rc; + + /* Allocate and initialise structure */ + dhcp = malloc ( sizeof ( *dhcp ) ); + if ( ! dhcp ) + return -ENOMEM; + memset ( dhcp, 0, sizeof ( *dhcp ) ); + dhcp->refcnt.free = dhcp_free; + job_init ( &dhcp->job, &dhcp_job_operations, &dhcp->refcnt ); + xfer_init ( &dhcp->xfer, &dhcp_xfer_operations, &dhcp->refcnt ); + dhcp->netdev = netdev_get ( netdev ); + dhcp->register_options = register_options; + dhcp->timer.expired = dhcp_timer_expired; + + /* Instantiate child objects and attach to our interfaces */ + if ( ( rc = xfer_open_socket ( &dhcp->xfer, SOCK_DGRAM, + ( struct sockaddr * ) &server, + ( struct sockaddr * ) &client ) ) != 0 ) + goto err; + + /* Start timer to initiate initial DHCPREQUEST */ + start_timer ( &dhcp->timer ); + + /* Attach parent interface, mortalise self, and return */ + job_plug_plug ( &dhcp->job, job ); + ref_put ( &dhcp->refcnt ); + return 0; + + err: + dhcp_finished ( dhcp, rc ); + ref_put ( &dhcp->refcnt ); + return rc; +} diff --git a/src/tests/dhcptest.c b/src/tests/dhcptest.c index 7e4bedbd..dad2ecf3 100644 --- a/src/tests/dhcptest.c +++ b/src/tests/dhcptest.c @@ -261,7 +261,7 @@ int test_dhcp ( struct net_device *netdev ) { out: /* Unregister and free DHCP options */ unregister_dhcp_options ( dhcp.options ); - free_dhcp_options ( dhcp.options ); + dhcpopt_put ( dhcp.options ); out_no_options: out_no_del_ipv4: return rc; diff --git a/src/usr/dhcpmgmt.c b/src/usr/dhcpmgmt.c index 926cd749..45c7e1ee 100644 --- a/src/usr/dhcpmgmt.c +++ b/src/usr/dhcpmgmt.c @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -34,6 +35,12 @@ * */ +int dhcp ( struct net_device *netdev ) { + return -ENOTSUP; +} + +#if 0 + /* Avoid dragging in dns.o */ struct sockaddr_tcpip nameserver; @@ -63,7 +70,7 @@ int dhcp ( struct net_device *netdev ) { /* Free up any previously-acquired options */ if ( dhcp_options ) { unregister_dhcp_options ( dhcp_options ); - free_dhcp_options ( dhcp_options ); + dhcpopt_put ( dhcp_options ); dhcp_options = NULL; } @@ -108,3 +115,5 @@ int dhcp ( struct net_device *netdev ) { return 0; } + +#endif