david/ipxe
david
/
ipxe
Archived
1
0
Fork 0

Initial (untested) implementation of TFTP over the new UDP API.

This commit is contained in:
Michael Brown 2006-08-08 23:45:52 +00:00
parent eda79ec32b
commit 3611cb17b7
3 changed files with 491 additions and 167 deletions

View File

@ -9,6 +9,8 @@
#include <stdint.h>
#include <gpxe/udp.h>
#include <gpxe/async.h>
#include <gpxe/retry.h>
#define TFTP_PORT 69 /**< Default TFTP server port */
#define TFTP_DEFAULT_BLKSIZE 512 /**< Default TFTP data block size */
@ -59,7 +61,7 @@ struct tftp_error {
/** A TFTP options acknowledgement (OACK) packet */
struct tftp_oack {
uint16_t opcode;
uint8_t data[0];
char data[0];
} __attribute__ (( packed ));
/** The common header of all TFTP packets */
@ -87,6 +89,17 @@ struct tftp_session {
struct udp_connection udp;
/** Filename */
const char *filename;
/**
* Callback function
*
* @v tftp TFTP connection
* @v block Block number
* @v data Data
* @v len Length of data
*/
void ( * callback ) ( struct tftp_session *tftp, unsigned int block,
void *data, size_t len );
/**
* Transfer ID
*
@ -119,7 +132,12 @@ struct tftp_session {
* TFTP server. If the TFTP server does not support the
* "tsize" option, this value will be zero.
*/
off_t tsize;
unsigned long tsize;
/** Asynchronous operation for this session */
struct async_operation aop;
/** Retransmission timer */
struct retry_timer timer;
};
#endif /* _GPXE_TFTP_H */

471
src/net/udp/tftp.c Normal file
View File

@ -0,0 +1,471 @@
/*
* Copyright (C) 2006 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <byteswap.h>
#include <errno.h>
#include <assert.h>
#include <vsprintf.h>
#include <gpxe/async.h>
#include <gpxe/tftp.h>
/** @file
*
* TFTP protocol
*
*/
/** 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_session *tftp, const char *value );
};
/**
* Process TFTP "blksize" option
*
* @v tftp TFTP connection
* @v value Option value
* @ret rc Return status code
*/
static int tftp_process_blksize ( struct tftp_session *tftp,
const char *value ) {
char *end;
tftp->blksize = strtoul ( value, &end, 10 );
if ( *end ) {
DBG ( "TFTP %p got invalid blksize \"%s\"\n", tftp, value );
return -EINVAL;
}
DBG ( "TFTP %p blksize=%d\n", tftp, tftp->blksize );
return 0;
}
/**
* Process TFTP "tsize" option
*
* @v tftp TFTP connection
* @v value Option value
* @ret rc Return status code
*/
static int tftp_process_tsize ( struct tftp_session *tftp,
const char *value ) {
char *end;
tftp->tsize = strtoul ( value, &end, 10 );
if ( *end ) {
DBG ( "TFTP %p got invalid tsize \"%s\"\n", tftp, value );
return -EINVAL;
}
DBG ( "TFTP %p tsize=%ld\n", tftp, tftp->tsize );
return 0;
}
/** Recognised TFTP options */
static struct tftp_option tftp_options[] = {
{ "blksize", tftp_process_blksize },
{ "tsize", tftp_process_tsize },
{ NULL, NULL }
};
/**
* Process TFTP option
*
* @v tftp TFTP connection
* @v name Option name
* @v value Option value
* @ret rc Return status code
*/
static int tftp_process_option ( struct tftp_session *tftp,
const char *name, const char *value ) {
struct tftp_option *option;
for ( option = tftp_options ; option->name ; option++ ) {
if ( strcasecmp ( name, option->name ) == 0 )
return option->process ( tftp, value );
}
DBG ( "TFTP %p received unknown option \"%s\" = \"%s\"\n",
tftp, name, value );
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->aop, 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;
DBG ( "TFTP %p requesting \"%s\"\n", tftp, tftp->filename );
data = rrq->data;
end = ( buf + len );
if ( data > end )
goto overflow;
data += snprintf ( data, ( end - data ),
"%s%coctet%cblksize%c%d%ctsize%c0",
tftp->filename, 0, 0, 0, tftp->blksize, 0, 0 ) + 1;
if ( data > end )
goto overflow;
rrq->opcode = htons ( TFTP_RRQ );
return udp_send ( &tftp->udp, buf, ( data - buf ) );
overflow:
DBG ( "TFTP %p RRQ out of space\n", tftp );
return -ENOBUFS;
}
/**
* Receive OACK
*
* @v tftp TFTP connection
* @v buf Temporary data buffer
* @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 ) {
struct tftp_oack *oack = buf;
char *end = buf + len;
char *name;
char *value;
int rc;
/* Sanity check */
if ( len < sizeof ( *oack ) ) {
DBG ( "TFTP %p received underlength OACK packet length %d\n",
tftp, len );
return -EINVAL;
}
if ( end[-1] != '\0' ) {
DBG ( "TFTP %p received OACK missing final NUL\n", tftp );
return -EINVAL;
}
/* Process each option in turn */
name = oack->data;
while ( name < end ) {
value = ( name + strlen ( name ) + 1 );
if ( value == end ) {
DBG ( "TFTP %p received OACK missing value for option "
"\"%s\"\n", tftp, name );
return -EINVAL;
}
if ( ( rc = tftp_process_option ( tftp, name, value ) ) != 0 )
return rc;
name = ( value + strlen ( value ) + 1 );
}
/* Mark as received block 0 (the OACK) */
tftp_received ( tftp, 0 );
return 0;
}
/**
* Receive DATA
*
* @v tftp TFTP connection
* @v buf Temporary data buffer
* @v len Length of temporary data buffer
* @ret rc Return status code
*/
static int tftp_rx_data ( struct tftp_session *tftp, void *buf, size_t len ) {
struct tftp_data *data = buf;
unsigned int block;
size_t data_len;
/* Sanity check */
if ( len < sizeof ( *data ) ) {
DBG ( "TFTP %p received underlength DATA packet length %d\n",
tftp, len );
return -EINVAL;
}
/* Pass to callback */
block = ntohs ( data->block );
data_len = ( len - offsetof ( typeof ( *data ), data ) );
tftp->callback ( tftp, block, data->data, data_len );
/* Mark block as received */
tftp_received ( tftp, block );
/* Finish when final block received */
if ( data_len < tftp->blksize )
tftp_done ( tftp, 0 );
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 ) );
}
/**
* Receive ERROR
*
* @v tftp TFTP connection
* @v buf Temporary data buffer
* @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 ) {
struct tftp_error *error = buf;
unsigned int err;
int rc = 0;
/* Sanity check */
if ( len < sizeof ( *error ) ) {
DBG ( "TFTP %p received underlength ERROR packet length %d\n",
tftp, len );
return -EINVAL;
}
DBG ( "TFTP %p received ERROR packet with code %d, message \"%s\"\n",
tftp, ntohs ( error->errcode ), error->errmsg );
/* Determine final operation result */
err = ntohs ( error->errcode );
if ( err < ( sizeof ( tftp_errors ) / sizeof ( tftp_errors[0] ) ) )
rc = -tftp_errors[err];
if ( ! rc )
rc = -PXENV_STATUS_TFTP_CANNOT_OPEN_CONNECTION;
/* Close TFTP session */
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
*
* @v udp UDP connection
* @v data Received data
* @v len Length of received data
* @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;
if ( len < sizeof ( *common ) ) {
DBG ( "TFTP %p received underlength packet length %d\n",
tftp, len );
return -EINVAL;
}
/* Filter by TID. Set TID on first response received */
if ( tftp->tid ) {
if ( tftp->tid != st_src->st_port ) {
DBG ( "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;
DBG ( "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 ) {
DBG ( "TFTP %p received packet from foreign source\n", tftp );
return -EINVAL;
}
switch ( common->opcode ) {
case htons ( TFTP_OACK ):
return tftp_rx_oack ( tftp, data, len );
case htons ( TFTP_DATA ):
return tftp_rx_data ( tftp, data, len );
case htons ( TFTP_ERROR ):
return tftp_rx_error ( tftp, data, len );
default:
DBG ( "TFTP %p received strange packet type %d\n", tftp,
ntohs ( common->opcode ) );
return -EINVAL;
};
}
/** TFTP UDP operations */
static struct udp_operations tftp_udp_operations = {
.senddata = tftp_senddata,
.newdata = tftp_newdata,
};
/**
* Initiate TFTP download
*
* @v tftp TFTP session
* @ret aop Asynchronous operation
*/
struct async_operation * tftp_get ( struct tftp_session *tftp ) {
int rc;
assert ( tftp->filename != NULL );
assert ( tftp->callback != NULL );
assert ( tftp->udp.peer.st_family != 0 );
/* Initialise TFTP session */
tftp->udp.udp_op = &tftp_udp_operations;
tftp->timer.expired = tftp_timer_expired;
tftp->state = -1;
tftp->blksize = TFTP_DEFAULT_BLKSIZE;
/* Open UDP connection */
if ( ( rc = udp_open ( &tftp->udp, 0 ) ) != 0 ) {
async_done ( &tftp->aop, rc );
goto out;
}
/* Transmit initial RRQ */
tftp_send_packet ( tftp );
out:
return &tftp->aop;
}

View File

@ -1,165 +0,0 @@
#include "etherboot.h"
#include "proto.h"
#include "errno.h"
#include "tftp.h"
#include "tftpcore.h"
/** @file
*
* TFTP protocol
*/
/**
* Process a TFTP block
*
* @v state TFTP transfer state
* @v tftp_state::block Last received data block
* @v tftp_state::blksize Transfer block size
* @v data The data block to process
* @v buffer The buffer to fill with the data
* @ret True Block processed successfully
* @ret False Block not processed successfully
* @ret tftp_state::block Incremented if applicable
* @ret *eof End-of-file marker
* @err #PXENV_STATUS_TFTP_INVALID_PACKET_SIZE Packet is too large
* @err other As returned by fill_buffer()
*
* Process a TFTP DATA packet that has been received. If the data
* packet is the next data packet in the stream, its contents will be
* placed in the #buffer and tftp_state::block will be incremented.
* If the packet is the final packet, end-of-file will be indicated
* via #eof.
*
* If the data packet is a duplicate, then process_tftp_data() will
* still return True, though nothing will be done with the packet. A
* False return value always indicates an error that should abort the
* transfer.
*/
static inline int tftp_process_data ( struct tftp_state *state,
struct tftp_data *data,
struct buffer *buffer,
int *eof ) {
unsigned int blksize;
/* Check it's the correct DATA block */
if ( ntohs ( data->block ) != ( state->block + 1 ) ) {
DBG ( "TFTP: got block %d, wanted block %d\n",
ntohs ( data->block ), state->block + 1 );
return 1;
}
/* Check it's an acceptable size */
blksize = ( ntohs ( data->udp.len )
+ offsetof ( typeof ( *data ), udp )
- offsetof ( typeof ( *data ), data ) );
if ( blksize > state->blksize ) {
DBG ( "TFTP: oversized block size %d (max %d)\n",
blksize, state->blksize );
errno = PXENV_STATUS_TFTP_INVALID_PACKET_SIZE;
return 0;
}
/* Place block in the buffer */
if ( ! fill_buffer ( buffer, data->data, state->block * state->blksize,
blksize ) ) {
DBG ( "TFTP: could not place data in buffer: %m\n" );
return 0;
}
/* Increment block counter */
state->block++;
/* Set EOF marker */
*eof = ( blksize < state->blksize );
return 1;
}
/**
* Download a file via TFTP
*
* @v server TFTP server
* @v file File name
* @v buffer Buffer into which to load file
* @ret True File was downloaded successfully
* @ret False File was not downloaded successfully
* @err #PXENV_STATUS_TFTP_UNKNOWN_OPCODE Unknown type of TFTP block received
* @err other As returned by tftp_open()
* @err other As returned by tftp_process_opts()
* @err other As returned by tftp_ack()
* @err other As returned by tftp_process_data()
*
* Download a file from a TFTP server into the specified buffer.
*/
static int tftp ( char *url __unused, struct sockaddr_in *server, char *file,
struct buffer *buffer ) {
struct tftp_state state;
union tftp_any *reply;
int eof = 0;
/* Initialise TFTP state */
memset ( &state, 0, sizeof ( state ) );
state.server = *server;
/* Open the file */
if ( ! tftp_open ( &state, file, &reply, 0 ) ) {
DBG ( "TFTP: could not open %@:%d/%s : %m\n",
server->sin_addr.s_addr, server->sin_port, file );
return 0;
}
/* Fetch file, a block at a time */
while ( 1 ) {
twiddle();
switch ( ntohs ( reply->common.opcode ) ) {
case TFTP_DATA:
if ( ! tftp_process_data ( &state, &reply->data,
buffer, &eof ) ) {
tftp_error ( &state, TFTP_ERR_ILLEGAL_OP,
NULL );
return 0;
}
break;
case TFTP_OACK:
if ( state.block ) {
/* OACK must be first block, if present */
DBG ( "TFTP: OACK after block %d\n",
state.block );
errno = PXENV_STATUS_TFTP_UNKNOWN_OPCODE;
tftp_error ( &state, TFTP_ERR_ILLEGAL_OP,
NULL );
return 0;
}
if ( ! tftp_process_opts ( &state, &reply->oack ) ) {
DBG ( "TFTP: option processing failed: %m\n" );
tftp_error ( &state, TFTP_ERR_BAD_OPTS, NULL );
return 0;
}
break;
default:
DBG ( "TFTP: unexpected opcode %d\n",
ntohs ( reply->common.opcode ) );
errno = PXENV_STATUS_TFTP_UNKNOWN_OPCODE;
tftp_error ( &state, TFTP_ERR_ILLEGAL_OP, NULL );
return 0;
}
/* If we have reached EOF, stop here */
if ( eof )
break;
/* Fetch the next data block */
if ( ! tftp_ack ( &state, &reply ) ) {
DBG ( "TFTP: could not get next block: %m\n" );
if ( ! reply ) {
tftp_error ( &state, TFTP_ERR_ILLEGAL_OP,
NULL );
}
return 0;
}
}
/* ACK the final packet, as a courtesy to the server */
tftp_ack_nowait ( &state );
return 1;
}
struct protocol tftp_protocol __default_protocol = {
.name = "tftp",
.default_port = TFTP_PORT,
.load = tftp,
};