209 lines
5.4 KiB
C
209 lines
5.4 KiB
C
#include "etherboot.h"
|
|
#include "proto.h"
|
|
#include "errno.h"
|
|
#include "tftp.h"
|
|
#include "tftpcore.h"
|
|
|
|
/** @file
|
|
*
|
|
* TFTM protocol
|
|
*
|
|
* TFTM is a protocol defined in RFC2090 as a multicast extension to
|
|
* TFTP.
|
|
*/
|
|
|
|
static inline int tftm_process_opts ( struct tftp_state *state,
|
|
struct tftp_oack *oack ) {
|
|
struct in_addr old_mcast_addr = state->multicast.sin_addr;
|
|
|
|
if ( ! tftp_process_opts ( state, oack ) )
|
|
return 0;
|
|
|
|
if ( old_mcast_addr.s_addr != state->multicast.sin_addr.s_addr ) {
|
|
if ( old_mcast_addr.s_addr ) {
|
|
DBG ( "TFTM: Leaving multicast group %@\n",
|
|
old_mcast_addr.s_addr );
|
|
leave_group ( IGMP_SERVER );
|
|
}
|
|
DBG ( "TFTM: Joining multicast group %@\n",
|
|
state->multicast.sin_addr.s_addr );
|
|
join_group ( IGMP_SERVER, state->multicast.sin_addr.s_addr );
|
|
}
|
|
|
|
DBG ( "TFTM: I am a %s client\n",
|
|
( state->master ? "master" : "slave" ) );
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
static inline int tftm_process_data ( struct tftp_state *state,
|
|
struct tftp_data *data,
|
|
struct buffer *buffer ) {
|
|
unsigned int blksize;
|
|
off_t offset;
|
|
|
|
/* Calculate block size and offset within file */
|
|
blksize = ( ntohs ( data->udp.len )
|
|
+ offsetof ( typeof ( *data ), udp )
|
|
- offsetof ( typeof ( *data ), data ) );
|
|
offset = ( ntohs ( data->block ) - 1 ) * state->blksize;
|
|
|
|
/* Check for oversized block */
|
|
if ( blksize > state->blksize ) {
|
|
DBG ( "TFTM: 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, offset, blksize ) ) {
|
|
DBG ( "TFTM: could not place data in buffer: %m\n" );
|
|
return 0;
|
|
}
|
|
|
|
/* If this is the last block, record the filesize (in case the
|
|
* server didn't supply a tsize option.
|
|
*/
|
|
if ( blksize < state->blksize ) {
|
|
state->tsize = offset + blksize;
|
|
}
|
|
|
|
/* Record the last received block */
|
|
state->block = ntohs ( data->block );
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
static inline int tftm_next ( struct tftp_state *state,
|
|
union tftp_any **reply,
|
|
struct buffer *buffer ) {
|
|
long listen_timeout;
|
|
|
|
listen_timeout = rfc2131_sleep_interval ( TIMEOUT, MAX_TFTP_RETRIES );
|
|
|
|
/* If we are not the master client, just listen for the next
|
|
* packet
|
|
*/
|
|
if ( ! state->master ) {
|
|
if ( tftp_get ( state, listen_timeout, reply ) ) {
|
|
/* Heard a non-error packet */
|
|
return 1;
|
|
}
|
|
if ( *reply ) {
|
|
/* Received an error packet */
|
|
return 0;
|
|
}
|
|
/* Didn't hear anything; try prodding the server */
|
|
state->master = 1;
|
|
}
|
|
/* We are the master client; trigger the next packet
|
|
* that we want
|
|
*/
|
|
state->block = buffer->fill / state->blksize;
|
|
return tftp_ack ( state, reply );
|
|
}
|
|
|
|
/**
|
|
* Download a file via TFTM
|
|
*
|
|
* @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 using
|
|
* the TFTM protocol.
|
|
*/
|
|
static int tftm ( char *url __unused, struct sockaddr_in *server, char *file,
|
|
struct buffer *buffer ) {
|
|
struct tftp_state state;
|
|
union tftp_any *reply;
|
|
int rc = 0;
|
|
|
|
/* Initialise TFTP state */
|
|
memset ( &state, 0, sizeof ( state ) );
|
|
state.server = *server;
|
|
|
|
/* Start as the master. This means that if the TFTP server
|
|
* doesn't actually support multicast, we'll still ACK the
|
|
* packets and it should all proceed as for a normal TFTP
|
|
* connection.
|
|
*/
|
|
state.master = 1;
|
|
|
|
/* Open the file */
|
|
if ( ! tftp_open ( &state, file, &reply, 1 ) ) {
|
|
DBG ( "TFTM: 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();
|
|
/* Process the current packet */
|
|
switch ( ntohs ( reply->common.opcode ) ) {
|
|
case TFTP_OACK:
|
|
/* Options can be received at any time */
|
|
if ( ! tftm_process_opts ( &state, &reply->oack ) ) {
|
|
DBG ( "TFTM: failed to process OACK: %m\n" );
|
|
tftp_error ( &state, TFTP_ERR_BAD_OPTS, NULL );
|
|
goto out;
|
|
}
|
|
break;
|
|
case TFTP_DATA:
|
|
if ( ! tftm_process_data ( &state, &reply->data,
|
|
buffer ) ) {
|
|
DBG ( "TFTM: failed to process DATA: %m\n" );
|
|
tftp_error ( &state, TFTP_ERR_ILLEGAL_OP,
|
|
NULL );
|
|
goto out;
|
|
}
|
|
break;
|
|
default:
|
|
DBG ( "TFTM: unexpected packet type %d\n",
|
|
ntohs ( reply->common.opcode ) );
|
|
errno = PXENV_STATUS_TFTP_UNKNOWN_OPCODE;
|
|
tftp_error ( &state, TFTP_ERR_ILLEGAL_OP, NULL );
|
|
goto out;
|
|
}
|
|
/* If we know the filesize, and we have all the data, stop */
|
|
if ( state.tsize && ( buffer->fill == state.tsize ) )
|
|
break;
|
|
/* Fetch the next packet */
|
|
if ( ! tftm_next ( &state, &reply, buffer ) ) {
|
|
DBG ( "TFTM: could not get next block: %m\n" );
|
|
if ( ! reply ) {
|
|
tftp_error ( &state, TFTP_ERR_ILLEGAL_OP,
|
|
NULL );
|
|
}
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
/* ACK the final packet, as a courtesy to the server */
|
|
tftp_ack_nowait ( &state );
|
|
|
|
rc = 1;
|
|
out:
|
|
if ( state.multicast.sin_addr.s_addr ) {
|
|
leave_group ( IGMP_SERVER );
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static struct protocol tftm_protocol __protocol = {
|
|
.name = "x-tftm",
|
|
.default_port = TFTP_PORT,
|
|
.load = tftm,
|
|
};
|