From 01b4bde8a061773d563dfe3086ca04ad7ec7ca30 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Mon, 11 Jun 2007 18:11:29 +0100 Subject: [PATCH] Updated TFTP and PXE UDP API code to use not-yet-implemented data-xfer UDP API. --- src/include/gpxe/tftp.h | 65 +--- src/interface/pxe/pxe_tftp.c | 9 +- src/interface/pxe/pxe_udp.c | 171 +++++----- src/net/udp/tftp.c | 644 +++++++++++++++++++++-------------- 4 files changed, 478 insertions(+), 411 deletions(-) diff --git a/src/include/gpxe/tftp.h b/src/include/gpxe/tftp.h index cb643d9c6..87d629ee0 100644 --- a/src/include/gpxe/tftp.h +++ b/src/include/gpxe/tftp.h @@ -78,71 +78,8 @@ union tftp_any { struct tftp_ack ack; struct tftp_error error; struct tftp_oack oack; -}; - -/** - * A TFTP session - * - * This data structure holds the state for an ongoing TFTP transfer. - */ -struct tftp_session { - /** URI being fetched */ - struct uri *uri; - /** Data buffer to fill */ - struct buffer *buffer; - /** Asynchronous operation */ - struct async async; - - /** Requested data block size - * - * This is the "blksize" option requested from the TFTP - * server. It may or may not be honoured. - */ - unsigned int request_blksize; - - /** Data block size - * - * This is the "blksize" option negotiated with the TFTP - * server. (If the TFTP server does not support TFTP options, - * this will default to 512). - */ - unsigned int blksize; - /** File size - * - * This is the value returned in the "tsize" option from the - * TFTP server. If the TFTP server does not support the - * "tsize" option, this value will be zero. - */ - unsigned long tsize; - - /** - * Transfer ID - * - * This is the transfer ID allocated by the server, used as - * the server UDP port for all packets except the initial read - * request. - */ - uint16_t tid; - /** Session state - * - * This is the block number to be used in the next ACK sent - * back to the server, i.e. the number of the last received - * data block. The value zero indicates that the last - * received block was an OACK (i.e. that the next ACK will - * contain a block number of zero), and any value less than - * zero indicates that the connection has not yet been opened - * (i.e. that no blocks have yet been received). - */ - int state; - /** UDP connection */ - struct udp_connection udp; - /** Retransmission timer */ - struct retry_timer timer; }; -/* Function prototypes */ - -extern int tftp_get ( struct uri *uri, struct buffer *buffer, - struct async *parent ); +extern void tftp_set_request_blksize ( unsigned int blksize ); #endif /* _GPXE_TFTP_H */ diff --git a/src/interface/pxe/pxe_tftp.c b/src/interface/pxe/pxe_tftp.c index d6cb97281..919d5c9e2 100644 --- a/src/interface/pxe/pxe_tftp.c +++ b/src/interface/pxe/pxe_tftp.c @@ -71,10 +71,11 @@ static void pxe_tftp_build_uri ( char uri_string[PXE_URI_LEN], port = htons ( TFTP_PORT ); if ( ! blksize ) blksize = TFTP_MAX_BLKSIZE; - snprintf ( uri_string, sizeof ( uri_string ), - "tftp://%s:%d%s%s?blksize=%d", inet_ntoa ( address ), - ntohs ( port ), ( ( filename[0] == '/' ) ? "" : "/" ), - filename, blksize ); + tftp_set_request_blksize ( blksize ); + + snprintf ( uri_string, sizeof ( uri_string ), "tftp://%s:%d%s%s", + inet_ntoa ( address ), ntohs ( port ), + ( ( filename[0] == '/' ) ? "" : "/" ), filename ); } /** diff --git a/src/interface/pxe/pxe_udp.c b/src/interface/pxe/pxe_udp.c index ba007ebef..be2bc130a 100644 --- a/src/interface/pxe/pxe_udp.c +++ b/src/interface/pxe/pxe_udp.c @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -31,85 +32,63 @@ /** A PXE UDP connection */ struct pxe_udp_connection { - /** Etherboot UDP connection */ - struct udp_connection udp; + /** Data transfer interface to UDP stack */ + struct xfer_interface xfer; /** "Connection is open" flag */ int open; - /** Current pxenv_udp_read() operation, if any */ + /** Local address */ + struct sockaddr_in local; + /** Current PXENV_UDP_READ parameter block */ struct s_PXENV_UDP_READ *pxenv_udp_read; - /** Current pxenv_udp_write() operation, if any */ - struct s_PXENV_UDP_WRITE *pxenv_udp_write; }; -static inline struct pxe_udp_connection * -udp_to_pxe ( struct udp_connection *conn ) { - return container_of ( conn, struct pxe_udp_connection, udp ); -} - -/** - * Send PXE UDP data - * - * @v conn UDP connection - * @v data Temporary data buffer - * @v len Size of temporary data buffer - * @ret rc Return status code - * - * Sends the packet belonging to the current pxenv_udp_write() - * operation. - */ -static int pxe_udp_senddata ( struct udp_connection *conn, void *data, - size_t len ) { - struct pxe_udp_connection *pxe_udp = udp_to_pxe ( conn ); - struct s_PXENV_UDP_WRITE *pxenv_udp_write = pxe_udp->pxenv_udp_write; - userptr_t buffer; - - /* Transmit packet */ - buffer = real_to_user ( pxenv_udp_write->buffer.segment, - pxenv_udp_write->buffer.offset ); - if ( len > pxenv_udp_write->buffer_size ) - len = pxenv_udp_write->buffer_size; - copy_from_user ( data, buffer, 0, len ); - return udp_send ( conn, data, len ); -} - /** * Receive PXE UDP data * - * @v conn UDP connection - * @v data Received data - * @v len Length of received data - * @v st_src Source address - * @v st_dest Destination address + * @v xfer Data transfer interface + * @v iobuf I/O buffer + * @v meta Data transfer metadata + * @ret rc Return status code * * Receives a packet as part of the current pxenv_udp_read() * operation. */ -static int pxe_udp_newdata ( struct udp_connection *conn, void *data, - size_t len, struct sockaddr_tcpip *st_src, - struct sockaddr_tcpip *st_dest ) { - struct pxe_udp_connection *pxe_udp = udp_to_pxe ( conn ); +static int pxe_udp_deliver_iob ( struct xfer_interface *xfer, + struct io_buffer *iobuf, + struct xfer_metadata *meta ) { + struct pxe_udp_connection *pxe_udp = + container_of ( xfer, struct pxe_udp_connection, xfer ); struct s_PXENV_UDP_READ *pxenv_udp_read = pxe_udp->pxenv_udp_read; - struct sockaddr_in *sin_src = ( ( struct sockaddr_in * ) st_src ); - struct sockaddr_in *sin_dest = ( ( struct sockaddr_in * ) st_dest ); + struct sockaddr_in *sin_src; + struct sockaddr_in *sin_dest; userptr_t buffer; + size_t len; + int rc = 0; if ( ! pxenv_udp_read ) { DBG ( "PXE discarded UDP packet\n" ); - return -ENOBUFS; + rc = -ENOBUFS; + goto done; } /* Copy packet to buffer and record length */ buffer = real_to_user ( pxenv_udp_read->buffer.segment, pxenv_udp_read->buffer.offset ); + len = iob_len ( iobuf ); if ( len > pxenv_udp_read->buffer_size ) len = pxenv_udp_read->buffer_size; - copy_to_user ( buffer, 0, data, len ); + copy_to_user ( buffer, 0, iobuf->data, len ); pxenv_udp_read->buffer_size = len; /* Fill in source/dest information */ + assert ( meta ); + sin_src = ( struct sockaddr_in * ) meta->src; + assert ( sin_src ); assert ( sin_src->sin_family == AF_INET ); pxenv_udp_read->src_ip = sin_src->sin_addr.s_addr; pxenv_udp_read->s_port = sin_src->sin_port; + sin_dest = ( struct sockaddr_in * ) meta->dest; + assert ( sin_dest ); assert ( sin_dest->sin_family == AF_INET ); pxenv_udp_read->dest_ip = sin_dest->sin_addr.s_addr; pxenv_udp_read->d_port = sin_dest->sin_port; @@ -117,18 +96,34 @@ static int pxe_udp_newdata ( struct udp_connection *conn, void *data, /* Mark as received */ pxe_udp->pxenv_udp_read = NULL; - return 0; + done: + free_iob ( iobuf ); + return rc; } -/** PXE UDP operations */ -static struct udp_operations pxe_udp_operations = { - .senddata = pxe_udp_senddata, - .newdata = pxe_udp_newdata, +/** PXE UDP data transfer interface operations */ +static struct xfer_interface_operations pxe_udp_xfer_operations = { + .close = ignore_xfer_close, + .vredirect = ignore_xfer_vredirect, + .request = ignore_xfer_request, + .seek = ignore_xfer_seek, + .alloc_iob = default_xfer_alloc_iob, + .deliver_iob = pxe_udp_deliver_iob, + .deliver_raw = xfer_deliver_as_iob, }; /** The PXE UDP connection */ static struct pxe_udp_connection pxe_udp = { - .udp.udp_op = &pxe_udp_operations, + .xfer = { + .intf = { + .dest = &null_xfer.intf, + .refcnt = NULL, + }, + .op = &pxe_udp_xfer_operations, + }, + .local = { + .sin_family = AF_INET, + }, }; /** @@ -174,7 +169,6 @@ static struct pxe_udp_connection pxe_udp = { * */ PXENV_EXIT_t pxenv_udp_open ( struct s_PXENV_UDP_OPEN *pxenv_udp_open ) { - struct in_addr new_ip = { .s_addr = pxenv_udp_open->src_ip }; DBG ( "PXENV_UDP_OPEN" ); @@ -184,17 +178,11 @@ PXENV_EXIT_t pxenv_udp_open ( struct s_PXENV_UDP_OPEN *pxenv_udp_open ) { return PXENV_EXIT_FAILURE; } - /* Set IP address if specified */ - if ( new_ip.s_addr ) { - /* FIXME: actually do something here */ - DBG ( " with new IP address %s", inet_ntoa ( new_ip ) ); - } + /* Record source IP address */ + pxe_udp.local.sin_addr.s_addr = pxenv_udp_open->src_ip; - /* Open UDP connection */ - if ( udp_open ( &pxe_udp.udp, 0 ) != 0 ) { - pxenv_udp_open->Status = PXENV_STATUS_OUT_OF_RESOURCES; - return PXENV_EXIT_FAILURE; - } + /* Open promiscuous UDP connection */ + udp_open_promisc ( &pxe_udp.xfer ); pxe_udp.open = 1; pxenv_udp_open->Status = PXENV_STATUS_SUCCESS; @@ -232,7 +220,7 @@ PXENV_EXIT_t pxenv_udp_close ( struct s_PXENV_UDP_CLOSE *pxenv_udp_close ) { } /* Close UDP connection */ - udp_close ( &pxe_udp.udp ); + udp_close_promisc ( &pxe_udp.xfer ); pxe_udp.open = 0; pxenv_udp_close->Status = PXENV_STATUS_SUCCESS; @@ -281,10 +269,14 @@ PXENV_EXIT_t pxenv_udp_close ( struct s_PXENV_UDP_CLOSE *pxenv_udp_close ) { * */ PXENV_EXIT_t pxenv_udp_write ( struct s_PXENV_UDP_WRITE *pxenv_udp_write ) { - union { - struct sockaddr_in sin; - struct sockaddr_tcpip st; - } dest; + struct sockaddr_in dest; + struct xfer_metadata meta = { + .src = ( struct sockaddr * ) &pxe_udp.local, + .dest = ( struct sockaddr * ) &dest, + }; + size_t len; + struct io_buffer *iobuf; + userptr_t buffer; int rc; DBG ( "PXENV_UDP_WRITE" ); @@ -297,36 +289,44 @@ PXENV_EXIT_t pxenv_udp_write ( struct s_PXENV_UDP_WRITE *pxenv_udp_write ) { /* Construct destination socket address */ memset ( &dest, 0, sizeof ( dest ) ); - dest.sin.sin_family = AF_INET; - dest.sin.sin_addr.s_addr = pxenv_udp_write->ip; - dest.sin.sin_port = pxenv_udp_write->dst_port; - udp_connect ( &pxe_udp.udp, &dest.st ); + dest.sin_family = AF_INET; + dest.sin_addr.s_addr = pxenv_udp_write->ip; + dest.sin_port = pxenv_udp_write->dst_port; /* Set local (source) port. PXE spec says source port is 2069 * if not specified. Really, this ought to be set at UDP open * time but hey, we didn't design this API. */ - if ( ! pxenv_udp_write->src_port ) - pxenv_udp_write->src_port = htons ( 2069 ); - udp_bind ( &pxe_udp.udp, pxenv_udp_write->src_port ); + pxe_udp.local.sin_port = pxenv_udp_write->src_port; + if ( ! pxe_udp.local.sin_port ) + pxe_udp.local.sin_port = htons ( 2069 ); /* FIXME: we ignore the gateway specified, since we're * confident of being able to do our own routing. We should * probably allow for multiple gateways. */ + /* Allocate and fill data buffer */ + len = pxenv_udp_write->buffer_size; + iobuf = xfer_alloc_iob ( &pxe_udp.xfer, len ); + if ( ! iobuf ) { + pxenv_udp_write->Status = PXENV_STATUS_OUT_OF_RESOURCES; + return PXENV_EXIT_FAILURE; + } + buffer = real_to_user ( pxenv_udp_write->buffer.segment, + pxenv_udp_write->buffer.offset ); + copy_from_user ( iob_put ( iobuf, len ), buffer, 0, len ); + DBG ( " %04x:%04x+%x %d->%s:%d", pxenv_udp_write->buffer.segment, pxenv_udp_write->buffer.offset, pxenv_udp_write->buffer_size, ntohs ( pxenv_udp_write->src_port ), - inet_ntoa ( dest.sin.sin_addr ), + inet_ntoa ( dest.sin_addr ), ntohs ( pxenv_udp_write->dst_port ) ); /* Transmit packet */ - pxe_udp.pxenv_udp_write = pxenv_udp_write; - rc = udp_senddata ( &pxe_udp.udp ); - pxe_udp.pxenv_udp_write = NULL; - if ( rc != 0 ) { - pxenv_udp_write->Status = PXENV_STATUS_UNDI_TRANSMIT_ERROR; + if ( ( rc = xfer_deliver_iob_meta ( &pxe_udp.xfer, iobuf, + &meta ) ) != 0 ) { + pxenv_udp_write->Status = PXENV_STATUS ( rc ); return PXENV_EXIT_FAILURE; } @@ -389,9 +389,6 @@ PXENV_EXIT_t pxenv_udp_read ( struct s_PXENV_UDP_READ *pxenv_udp_read ) { return PXENV_EXIT_FAILURE; } - /* Bind promiscuously; we will do our own filtering */ - udp_bind_promisc ( &pxe_udp.udp ); - /* Try receiving a packet */ pxe_udp.pxenv_udp_read = pxenv_udp_read; step(); diff --git a/src/net/udp/tftp.c b/src/net/udp/tftp.c index b07c150aa..027ada1cd 100644 --- a/src/net/udp/tftp.c +++ b/src/net/udp/tftp.c @@ -24,9 +24,11 @@ #include #include #include -#include -#include +#include +#include +#include #include +#include /** @file * @@ -34,19 +36,224 @@ * */ -/** A TFTP option */ -struct tftp_option { - /** Option name */ - const char *name; - /** Option processor +/** + * A TFTP request + * + * This data structure holds the state for an ongoing TFTP transfer. + */ +struct tftp_request { + /** Reference count */ + struct refcnt refcnt; + /** Data transfer interface */ + struct xfer_interface xfer; + + /** URI being fetched */ + struct uri *uri; + /** Transport layer interface */ + struct xfer_interface socket; + + /** Data block size * - * @v tftp TFTP connection - * @v value Option value - * @ret rc Return status code + * This is the "blksize" option negotiated with the TFTP + * server. (If the TFTP server does not support TFTP options, + * this will default to 512). */ - int ( * process ) ( struct tftp_session *tftp, const char *value ); + unsigned int blksize; + /** File size + * + * This is the value returned in the "tsize" option from the + * TFTP server. If the TFTP server does not support the + * "tsize" option, this value will be zero. + */ + unsigned long tsize; + + /** Request state + * + * This is the block number to be used in the next ACK sent + * back to the server, i.e. the number of the last received + * data block. The value zero indicates that the last + * received block was an OACK (i.e. that the next ACK will + * contain a block number of zero), and any value less than + * zero indicates that the connection has not yet been opened + * (i.e. that no blocks have yet been received). + */ + int state; + /** Peer address + * + * The peer address is determined by the first response + * received to the TFTP RRQ. + */ + struct sockaddr_tcpip peer; + /** Retransmission timer */ + struct retry_timer timer; }; +/** + * Free TFTP request + * + * @v refcnt Reference counter + */ +static void tftp_free ( struct refcnt *refcnt ) { + struct tftp_request *tftp = + container_of ( refcnt, struct tftp_request, refcnt ); + + uri_put ( tftp->uri ); + free ( tftp ); +} + +/** + * Mark TFTP request as complete + * + * @v tftp TFTP connection + * @v rc Return status code + */ +static void tftp_done ( struct tftp_request *tftp, int rc ) { + + /* Stop the retry timer */ + stop_timer ( &tftp->timer ); + + /* Close all data transfer interfaces */ + xfer_nullify ( &tftp->socket ); + xfer_close ( &tftp->socket, rc ); + xfer_nullify ( &tftp->xfer ); + xfer_close ( &tftp->xfer, rc ); +} + +/** + * TFTP requested blocksize + * + * This is treated as a global configuration parameter. + */ +static unsigned int tftp_request_blksize = TFTP_MAX_BLKSIZE; + +/** + * Set TFTP request blocksize + * + * @v blksize Requested block size + */ +void tftp_set_request_blksize ( unsigned int blksize ) { + if ( blksize < TFTP_DEFAULT_BLKSIZE ) + blksize = TFTP_DEFAULT_BLKSIZE; + tftp_request_blksize = blksize; +} + +/** + * Transmit RRQ + * + * @v tftp TFTP connection + * @ret rc Return status code + */ +static int tftp_send_rrq ( struct tftp_request *tftp ) { + struct tftp_rrq *rrq; + const char *path = tftp->uri->path; + size_t len = ( sizeof ( *rrq ) + strlen ( path ) + 1 /* NUL */ + + 5 + 1 /* "octet" + NUL */ + + 7 + 1 + 5 + 1 /* "blksize" + NUL + ddddd + NUL */ + + 5 + 1 + 1 + 1 /* "tsize" + NUL + "0" + NUL */ ); + struct io_buffer *iobuf; + + DBGC ( tftp, "TFTP %p requesting \"%s\"\n", tftp, path ); + + /* Allocate buffer */ + iobuf = xfer_alloc_iob ( &tftp->socket, len ); + if ( ! iobuf ) + return -ENOMEM; + + /* Build request */ + rrq = iob_put ( iobuf, sizeof ( *rrq ) ); + rrq->opcode = htons ( TFTP_RRQ ); + iob_put ( iobuf, + snprintf ( iobuf->data, iob_tailroom ( iobuf ), + "%s%coctet%cblksize%c%d%ctsize%c0", path, 0, + 0, 0, tftp_request_blksize, 0, 0 ) + 1 ); + + /* RRQ always goes to the address specified in the initial + * xfer_open() call + */ + return xfer_deliver_iob ( &tftp->socket, iobuf ); +} + +/** + * Transmit ACK + * + * @v tftp TFTP connection + * @ret rc Return status code + */ +static int tftp_send_ack ( struct tftp_request *tftp ) { + struct tftp_ack *ack; + struct io_buffer *iobuf; + struct xfer_metadata meta = { + .dest = ( struct sockaddr * ) &tftp->peer, + }; + + /* Allocate buffer */ + iobuf = xfer_alloc_iob ( &tftp->socket, sizeof ( *ack ) ); + if ( ! iobuf ) + return -ENOMEM; + + /* Build ACK */ + ack = iob_put ( iobuf, sizeof ( *ack ) ); + ack->opcode = htons ( TFTP_ACK ); + ack->block = htons ( tftp->state ); + + /* ACK always goes to the peer recorded from the RRQ response */ + return xfer_deliver_iob_meta ( &tftp->socket, iobuf, &meta ); +} + +/** + * Transmit data + * + * @v tftp TFTP connection + * @ret rc Return status code + */ +static int tftp_send_packet ( struct tftp_request *tftp ) { + + /* Start retransmission timer */ + start_timer ( &tftp->timer ); + + /* Send RRQ or ACK as appropriate */ + if ( tftp->state < 0 ) { + return tftp_send_rrq ( tftp ); + } else { + return tftp_send_ack ( tftp ); + } +} + +/** + * Handle TFTP retransmission timer expiry + * + * @v timer Retry timer + * @v fail Failure indicator + */ +static void tftp_timer_expired ( struct retry_timer *timer, int fail ) { + struct tftp_request *tftp = + container_of ( timer, struct tftp_request, timer ); + + if ( fail ) { + tftp_done ( tftp, -ETIMEDOUT ); + } else { + tftp_send_packet ( tftp ); + } +} + +/** + * Mark TFTP block as received + * + * @v tftp TFTP connection + * @v block Block number + */ +static void tftp_received ( struct tftp_request *tftp, unsigned int block ) { + + /* Stop the retry timer */ + stop_timer ( &tftp->timer ); + + /* Update state to indicate which block we're now waiting for */ + tftp->state = block; + + /* Send next packet */ + tftp_send_packet ( tftp ); +} + /** * Process TFTP "blksize" option * @@ -54,7 +261,7 @@ struct tftp_option { * @v value Option value * @ret rc Return status code */ -static int tftp_process_blksize ( struct tftp_session *tftp, +static int tftp_process_blksize ( struct tftp_request *tftp, const char *value ) { char *end; @@ -76,7 +283,7 @@ static int tftp_process_blksize ( struct tftp_session *tftp, * @v value Option value * @ret rc Return status code */ -static int tftp_process_tsize ( struct tftp_session *tftp, +static int tftp_process_tsize ( struct tftp_request *tftp, const char *value ) { char *end; @@ -88,9 +295,26 @@ static int tftp_process_tsize ( struct tftp_session *tftp, } DBGC ( tftp, "TFTP %p tsize=%ld\n", tftp, tftp->tsize ); + /* Notify recipient of file size */ + xfer_seek ( &tftp->xfer, tftp->tsize, SEEK_SET ); + xfer_seek ( &tftp->xfer, 0, SEEK_SET ); + return 0; } +/** A TFTP option */ +struct tftp_option { + /** Option name */ + const char *name; + /** Option processor + * + * @v tftp TFTP connection + * @v value Option value + * @ret rc Return status code + */ + int ( * process ) ( struct tftp_request *tftp, const char *value ); +}; + /** Recognised TFTP options */ static struct tftp_option tftp_options[] = { { "blksize", tftp_process_blksize }, @@ -106,7 +330,7 @@ static struct tftp_option tftp_options[] = { * @v value Option value * @ret rc Return status code */ -static int tftp_process_option ( struct tftp_session *tftp, +static int tftp_process_option ( struct tftp_request *tftp, const char *name, const char *value ) { struct tftp_option *option; @@ -121,110 +345,6 @@ static int tftp_process_option ( struct tftp_session *tftp, return -EINVAL; } -/** Translation between TFTP errors and internal error numbers */ -static const uint8_t tftp_errors[] = { - [TFTP_ERR_FILE_NOT_FOUND] = PXENV_STATUS_TFTP_FILE_NOT_FOUND, - [TFTP_ERR_ACCESS_DENIED] = PXENV_STATUS_TFTP_ACCESS_VIOLATION, - [TFTP_ERR_ILLEGAL_OP] = PXENV_STATUS_TFTP_UNKNOWN_OPCODE, -}; - -/** - * Mark TFTP session as complete - * - * @v tftp TFTP connection - * @v rc Return status code - */ -static void tftp_done ( struct tftp_session *tftp, int rc ) { - - /* Stop the retry timer */ - stop_timer ( &tftp->timer ); - - /* Close UDP connection */ - udp_close ( &tftp->udp ); - - /* Mark async operation as complete */ - async_done ( &tftp->async, rc ); -} - -/** - * Send next packet in TFTP session - * - * @v tftp TFTP connection - */ -static void tftp_send_packet ( struct tftp_session *tftp ) { - start_timer ( &tftp->timer ); - udp_senddata ( &tftp->udp ); -} - -/** - * Handle TFTP retransmission timer expiry - * - * @v timer Retry timer - * @v fail Failure indicator - */ -static void tftp_timer_expired ( struct retry_timer *timer, int fail ) { - struct tftp_session *tftp = - container_of ( timer, struct tftp_session, timer ); - - if ( fail ) { - tftp_done ( tftp, -ETIMEDOUT ); - } else { - tftp_send_packet ( tftp ); - } -} - -/** - * Mark TFTP block as received - * - * @v tftp TFTP connection - * @v block Block number - */ -static void tftp_received ( struct tftp_session *tftp, unsigned int block ) { - - /* Stop the retry timer */ - stop_timer ( &tftp->timer ); - - /* Update state to indicate which block we're now waiting for */ - tftp->state = block; - - /* Send next packet */ - tftp_send_packet ( tftp ); -} - -/** - * Transmit RRQ - * - * @v tftp TFTP connection - * @v buf Temporary data buffer - * @v len Length of temporary data buffer - * @ret rc Return status code - */ -static int tftp_send_rrq ( struct tftp_session *tftp, void *buf, size_t len ) { - struct tftp_rrq *rrq = buf; - void *data; - void *end; - - DBGC ( tftp, "TFTP %p requesting \"%s\"\n", tftp, tftp->uri->path ); - - data = rrq->data; - end = ( buf + len ); - if ( data > end ) - goto overflow; - data += ( snprintf ( data, ( end - data ), - "%s%coctet%cblksize%c%d%ctsize%c0", - tftp->uri->path, 0, 0, 0, - tftp->request_blksize, 0, 0 ) + 1 ); - if ( data > end ) - goto overflow; - rrq->opcode = htons ( TFTP_RRQ ); - - return udp_send ( &tftp->udp, buf, ( data - buf ) ); - - overflow: - DBGC ( tftp, "TFTP %p RRQ out of space\n", tftp ); - return -ENOBUFS; -} - /** * Receive OACK * @@ -233,7 +353,7 @@ static int tftp_send_rrq ( struct tftp_session *tftp, void *buf, size_t len ) { * @v len Length of temporary data buffer * @ret rc Return status code */ -static int tftp_rx_oack ( struct tftp_session *tftp, void *buf, size_t len ) { +static int tftp_rx_oack ( struct tftp_request *tftp, void *buf, size_t len ) { struct tftp_oack *oack = buf; char *end = buf + len; char *name; @@ -276,31 +396,34 @@ static int tftp_rx_oack ( struct tftp_session *tftp, void *buf, size_t len ) { * Receive DATA * * @v tftp TFTP connection - * @v buf Temporary data buffer - * @v len Length of temporary data buffer + * @v iobuf I/O buffer * @ret rc Return status code + * + * Takes ownership of I/O buffer. */ -static int tftp_rx_data ( struct tftp_session *tftp, void *buf, size_t len ) { - struct tftp_data *data = buf; +static int tftp_rx_data ( struct tftp_request *tftp, + struct io_buffer *iobuf ) { + struct tftp_data *data = iobuf->data; unsigned int block; - size_t data_offset; size_t data_len; int rc; /* Sanity check */ - if ( len < sizeof ( *data ) ) { + if ( iob_len ( iobuf ) < sizeof ( *data ) ) { DBGC ( tftp, "TFTP %p received underlength DATA packet " - "length %d\n", tftp, len ); + "length %d\n", tftp, iob_len ( iobuf ) ); + free_iob ( iobuf ); return -EINVAL; } - /* Fill data buffer */ + /* Extract data */ block = ntohs ( data->block ); - data_offset = ( ( block - 1 ) * tftp->blksize ); - data_len = ( len - offsetof ( typeof ( *data ), data ) ); - if ( ( rc = fill_buffer ( tftp->buffer, data->data, data_offset, - data_len ) ) != 0 ) { - DBGC ( tftp, "TFTP %p could not fill data buffer: %s\n", + iob_pull ( iobuf, sizeof ( *data ) ); + data_len = iob_len ( iobuf ); + + /* Deliver data */ + if ( ( rc = xfer_deliver_iob ( &tftp->xfer, iobuf ) ) != 0 ) { + DBGC ( tftp, "TFTP %p could not deliver data: %s\n", tftp, strerror ( rc ) ); tftp_done ( tftp, rc ); return rc; @@ -316,21 +439,12 @@ static int tftp_rx_data ( struct tftp_session *tftp, void *buf, size_t len ) { return 0; } -/** - * Transmit ACK - * - * @v tftp TFTP connection - * @v buf Temporary data buffer - * @v len Length of temporary data buffer - * @ret rc Return status code - */ -static int tftp_send_ack ( struct tftp_session *tftp ) { - struct tftp_ack ack; - - ack.opcode = htons ( TFTP_ACK ); - ack.block = htons ( tftp->state ); - return udp_send ( &tftp->udp, &ack, sizeof ( ack ) ); -} +/** Translation between TFTP errors and internal error numbers */ +static const uint8_t tftp_errors[] = { + [TFTP_ERR_FILE_NOT_FOUND] = PXENV_STATUS_TFTP_FILE_NOT_FOUND, + [TFTP_ERR_ACCESS_DENIED] = PXENV_STATUS_TFTP_ACCESS_VIOLATION, + [TFTP_ERR_ILLEGAL_OP] = PXENV_STATUS_TFTP_UNKNOWN_OPCODE, +}; /** * Receive ERROR @@ -340,7 +454,7 @@ static int tftp_send_ack ( struct tftp_session *tftp ) { * @v len Length of temporary data buffer * @ret rc Return status code */ -static int tftp_rx_error ( struct tftp_session *tftp, void *buf, size_t len ) { +static int tftp_rx_error ( struct tftp_request *tftp, void *buf, size_t len ) { struct tftp_error *error = buf; unsigned int err; int rc = 0; @@ -362,32 +476,12 @@ static int tftp_rx_error ( struct tftp_session *tftp, void *buf, size_t len ) { if ( ! rc ) rc = -PXENV_STATUS_TFTP_CANNOT_OPEN_CONNECTION; - /* Close TFTP session */ + /* Close TFTP request */ tftp_done ( tftp, rc ); return 0; } -/** - * Transmit data - * - * @v conn UDP connection - * @v buf Temporary data buffer - * @v len Length of temporary data buffer - * @ret rc Return status code - */ -static int tftp_senddata ( struct udp_connection *conn, - void *buf, size_t len ) { - struct tftp_session *tftp = - container_of ( conn, struct tftp_session, udp ); - - if ( tftp->state < 0 ) { - return tftp_send_rrq ( tftp, buf, len ); - } else { - return tftp_send_ack ( tftp ); - } -} - /** * Receive new data * @@ -397,141 +491,179 @@ static int tftp_senddata ( struct udp_connection *conn, * @v st_src Partially-filled source address * @v st_dest Partially-filled destination address */ -static int tftp_newdata ( struct udp_connection *conn, void *data, size_t len, - struct sockaddr_tcpip *st_src __unused, - struct sockaddr_tcpip *st_dest __unused ) { - struct tftp_session *tftp = - container_of ( conn, struct tftp_session, udp ); - struct tftp_common *common = data; +static int tftp_socket_deliver_iob ( struct xfer_interface *socket, + struct io_buffer *iobuf, + struct xfer_metadata *meta ) { + struct tftp_request *tftp = + container_of ( socket, struct tftp_request, socket ); + struct sockaddr_tcpip *st_src; + struct tftp_common *common = iobuf->data; + size_t len = iob_len ( iobuf ); + int rc = -EINVAL; + /* Sanity checks */ if ( len < sizeof ( *common ) ) { DBGC ( tftp, "TFTP %p received underlength packet length %d\n", tftp, len ); - return -EINVAL; + goto done; + } + if ( ! meta ) { + DBGC ( tftp, "TFTP %p received packet without metadata\n", + tftp ); + goto done; + } + if ( ! meta->src ) { + DBGC ( tftp, "TFTP %p received packet without source port\n", + tftp ); + goto done; } /* Filter by TID. Set TID on first response received */ - if ( tftp->tid ) { - if ( tftp->tid != st_src->st_port ) { - DBGC ( tftp, "TFTP %p received packet from wrong port " - "(got %d, wanted %d)\n", tftp, - ntohs ( st_src->st_port ), ntohs ( tftp->tid )); - return -EINVAL; - } - } else { - tftp->tid = st_src->st_port; + st_src = ( struct sockaddr_tcpip * ) meta->src; + if ( tftp->state < 0 ) { + memcpy ( &tftp->peer, st_src, sizeof ( tftp->peer ) ); DBGC ( tftp, "TFTP %p using remote port %d\n", tftp, - ntohs ( tftp->tid ) ); - udp_connect_port ( &tftp->udp, tftp->tid ); - } - - /* Filter by source address */ - if ( memcmp ( st_src, udp_peer ( &tftp->udp ), - sizeof ( *st_src ) ) != 0 ) { - DBGC ( tftp, "TFTP %p received packet from foreign source\n", - tftp ); - return -EINVAL; + ntohs ( tftp->peer.st_port ) ); + } else if ( memcmp ( &tftp->peer, st_src, + sizeof ( tftp->peer ) ) != 0 ) { + DBGC ( tftp, "TFTP %p received packet from wrong source (got " + "%d, wanted %d)\n", tftp, ntohs ( st_src->st_port ), + ntohs ( tftp->peer.st_port ) ); + goto done; } switch ( common->opcode ) { case htons ( TFTP_OACK ): - return tftp_rx_oack ( tftp, data, len ); + rc = tftp_rx_oack ( tftp, iobuf->data, len ); + break; case htons ( TFTP_DATA ): - return tftp_rx_data ( tftp, data, len ); + rc = tftp_rx_data ( tftp, iobuf ); + iobuf = NULL; + break; case htons ( TFTP_ERROR ): - return tftp_rx_error ( tftp, data, len ); + rc = tftp_rx_error ( tftp, iobuf->data, len ); + break; default: - DBGC ( tftp, "TFTP %p received strange packet type %d\n", tftp, - ntohs ( common->opcode ) ); - return -EINVAL; + DBGC ( tftp, "TFTP %p received strange packet type %d\n", + tftp, ntohs ( common->opcode ) ); + break; }; -} -/** TFTP UDP operations */ -static struct udp_operations tftp_udp_operations = { - .senddata = tftp_senddata, - .newdata = tftp_newdata, -}; + done: + free_iob ( iobuf ); + return rc; +} /** - * Reap asynchronous operation + * TFTP connection closed by network stack * - * @v async Asynchronous operation + * @v socket Transport layer interface + * @v rc Reason for close */ -static void tftp_reap ( struct async *async ) { - struct tftp_session *tftp = - container_of ( async, struct tftp_session, async ); +static void tftp_socket_close ( struct xfer_interface *socket, int rc ) { + struct tftp_request *tftp = + container_of ( socket, struct tftp_request, socket ); - free ( tftp ); + DBGC ( tftp, "TFTP %p socket closed: %s\n", + tftp, strerror ( rc ) ); + + tftp_done ( tftp, rc ); } -/** TFTP asynchronous operations */ -static struct async_operations tftp_async_operations = { - .reap = tftp_reap, +/** TFTP socket operations */ +static struct xfer_interface_operations tftp_socket_operations = { + .close = tftp_socket_close, + .vredirect = xfer_vopen, + .request = ignore_xfer_request, + .seek = ignore_xfer_seek, + .alloc_iob = default_xfer_alloc_iob, + .deliver_iob = tftp_socket_deliver_iob, + .deliver_raw = xfer_deliver_as_iob, +}; + +/** + * Close TFTP data transfer interface + * + * @v xfer Data transfer interface + * @v rc Reason for close + */ +static void tftp_xfer_close ( struct xfer_interface *xfer, int rc ) { + struct tftp_request *tftp = + container_of ( xfer, struct tftp_request, xfer ); + + DBGC ( tftp, "TFTP %p interface closed: %s\n", + tftp, strerror ( rc ) ); + + tftp_done ( tftp, rc ); +} + +/** TFTP data transfer interface operations */ +static struct xfer_interface_operations tftp_xfer_operations = { + .close = tftp_xfer_close, + .vredirect = ignore_xfer_vredirect, + .request = ignore_xfer_request, + .seek = ignore_xfer_seek, + .alloc_iob = default_xfer_alloc_iob, + .deliver_iob = xfer_deliver_as_raw, + .deliver_raw = ignore_xfer_deliver_raw, }; /** * Initiate TFTP download * + * @v xfer Data transfer interface * @v uri Uniform Resource Identifier - * @v buffer Buffer into which to download file - * @v parent Parent asynchronous operation * @ret rc Return status code */ -int tftp_get ( struct uri *uri, struct buffer *buffer, struct async *parent ) { - struct tftp_session *tftp = NULL; +int tftp_open ( struct xfer_interface *xfer, struct uri *uri ) { + struct tftp_request *tftp; + struct sockaddr_tcpip server; int rc; /* Sanity checks */ - if ( ! uri->path ) { - rc = -EINVAL; - goto err; - } + if ( ! uri->host ) + return -EINVAL; + if ( ! uri->path ) + return -EINVAL; /* Allocate and populate TFTP structure */ tftp = malloc ( sizeof ( *tftp ) ); - if ( ! tftp ) { - rc = -ENOMEM; - goto err; - } + if ( ! tftp ) + return -ENOMEM; memset ( tftp, 0, sizeof ( *tftp ) ); - tftp->uri = uri; - tftp->buffer = buffer; - if ( ! tftp->request_blksize ) - tftp->request_blksize = TFTP_MAX_BLKSIZE; - tftp->blksize = TFTP_DEFAULT_BLKSIZE; + tftp->refcnt.free = tftp_free; + xfer_init ( &tftp->xfer, &tftp_xfer_operations, &tftp->refcnt ); + tftp->uri = uri_get ( uri ); + xfer_init ( &tftp->socket, &tftp_socket_operations, &tftp->refcnt ); tftp->state = -1; - tftp->udp.udp_op = &tftp_udp_operations; tftp->timer.expired = tftp_timer_expired; - -#warning "Quick name resolution hack" - union { - struct sockaddr_tcpip st; - struct sockaddr_in sin; - } server; - server.sin.sin_port = htons ( TFTP_PORT ); - server.sin.sin_family = AF_INET; - if ( inet_aton ( uri->host, &server.sin.sin_addr ) == 0 ) { - rc = -EINVAL; - goto err; - } - udp_connect ( &tftp->udp, &server.st ); - - - /* Open UDP connection */ - if ( ( rc = udp_open ( &tftp->udp, 0 ) ) != 0 ) + /* Open socket */ + memset ( &server, 0, sizeof ( server ) ); + server.st_port = htons ( uri_port ( tftp->uri, TFTP_PORT ) ); + if ( ( rc = xfer_open_named_socket ( &tftp->socket, SOCK_DGRAM, + ( struct sockaddr * ) &server, + uri->host, NULL ) ) != 0 ) goto err; - /* Transmit initial RRQ */ - tftp_send_packet ( tftp ); + /* Start timer to initiate RRQ */ + start_timer ( &tftp->timer ); - async_init ( &tftp->async, &tftp_async_operations, parent ); + /* Attach to parent interface, mortalise self, and return */ + xfer_plug_plug ( &tftp->xfer, xfer ); + ref_put ( &tftp->refcnt ); return 0; err: - DBGC ( tftp, "TFTP %p could not create session: %s\n", + DBGC ( tftp, "TFTP %p could not create request: %s\n", tftp, strerror ( rc ) ); - free ( tftp ); + tftp_done ( tftp, rc ); + ref_put ( &tftp->refcnt ); return rc; } + +/** TFTP URI opener */ +struct uri_opener tftp_uri_opener __uri_opener = { + .scheme = "tftp", + .open = tftp_open, +};