123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 |
- #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,
- };
|