david/ipxe
Archived
1
0
This repository has been archived on 2020-12-06. You can view files and clone it, but cannot push or open issues or pull requests.
ipxe/src/proto/tftm.c

209 lines
5.4 KiB
C
Raw Normal View History

#include "etherboot.h"
#include "proto.h"
#include "errno.h"
#include "tftp.h"
#include "tftpcore.h"
2005-03-08 19:53:11 +01:00
/** @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;
2005-03-08 19:53:11 +01:00
if ( ! tftp_process_opts ( state, oack ) )
return 0;
2005-03-08 19:53:11 +01:00
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 );
}
2005-03-08 19:53:11 +01:00
DBG ( "TFTM: I am a %s client\n",
( state->master ? "master" : "slave" ) );
2005-03-08 19:53:11 +01:00
return 1;
}
2005-03-08 19:53:11 +01:00
static inline int tftm_process_data ( struct tftp_state *state,
struct tftp_data *data,
struct buffer *buffer ) {
unsigned int blksize;
off_t offset;
2005-03-08 19:53:11 +01:00
/* 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;
2005-03-08 19:53:11 +01:00
/* 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;
2005-03-08 19:53:11 +01:00
}
/* 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;
2005-03-08 19:53:11 +01:00
}
/* 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;
}
2005-03-08 19:53:11 +01:00
/* Record the last received block */
state->block = ntohs ( data->block );
2005-03-08 19:53:11 +01:00
return 1;
}
2005-03-08 19:53:11 +01:00
static inline int tftm_next ( struct tftp_state *state,
union tftp_any **reply,
struct buffer *buffer ) {
long listen_timeout;
2005-03-08 19:53:11 +01:00
listen_timeout = rfc2131_sleep_interval ( TIMEOUT, MAX_TFTP_RETRIES );
2005-03-08 19:53:11 +01:00
/* 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;
2005-03-08 19:53:11 +01:00
}
if ( *reply ) {
/* Received an error packet */
return 0;
2005-03-08 19:53:11 +01:00
}
/* 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 );
}
2005-03-08 19:53:11 +01:00
/**
* 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;
}
2005-03-08 19:53:11 +01:00
/* 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;
2005-03-08 19:53:11 +01:00
}
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;
2005-03-08 19:53:11 +01:00
}
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;
2005-03-08 19:53:11 +01:00
}
/* 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;
2005-03-08 19:53:11 +01:00
}
}
/* ACK the final packet, as a courtesy to the server */
tftp_ack_nowait ( &state );
2005-03-08 19:53:11 +01:00
rc = 1;
out:
if ( state.multicast.sin_addr.s_addr ) {
leave_group ( IGMP_SERVER );
2005-03-08 19:53:11 +01:00
}
return rc;
2005-03-08 19:53:11 +01:00
}
2005-05-01 17:26:44 +02:00
static struct protocol tftm_protocol __protocol = {
.name = "x-tftm",
.default_port = TFTP_PORT,
.load = tftm,
2005-05-01 17:26:44 +02:00
};