From 0b048e9cfb741e5f2c9a4b12ef79502d63cef93f Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Wed, 1 Jun 2005 11:01:59 +0000 Subject: [PATCH] Now have enough functions to implement a standard TFTP client in around 50 lines of code. --- src/include/tftpcore.h | 25 +++++++++ src/proto/tftpcore.c | 118 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 132 insertions(+), 11 deletions(-) create mode 100644 src/include/tftpcore.h diff --git a/src/include/tftpcore.h b/src/include/tftpcore.h new file mode 100644 index 00000000..cbe86bdc --- /dev/null +++ b/src/include/tftpcore.h @@ -0,0 +1,25 @@ +#ifndef TFTPCORE_H +#define TFTPCORE_H + +#include "tftp.h" + +extern int await_tftp ( int ival, void *ptr, unsigned short ptype, + struct iphdr *ip, struct udphdr *udp, + struct tcphdr *tcp ); + +extern int tftp_open ( struct tftp_state *state, const char *filename, + union tftp_any **reply ); + +extern int tftp_process_opts ( struct tftp_state *state, + struct tftp_oack *oack ); + +extern int tftp_ack_nowait ( struct tftp_state *state ); + +extern int tftp_ack ( struct tftp_state *state, union tftp_any **reply ); + +extern int tftp_error ( struct tftp_state *state, int errcode, + const char *errmsg ); + +extern void tftp_set_errno ( struct tftp_error *error ); + +#endif /* TFTPCORE_H */ diff --git a/src/proto/tftpcore.c b/src/proto/tftpcore.c index 0bdffef7..51ad8b43 100644 --- a/src/proto/tftpcore.c +++ b/src/proto/tftpcore.c @@ -2,6 +2,7 @@ #include "tcp.h" /* for struct tcphdr */ #include "errno.h" #include "etherboot.h" +#include "tftpcore.h" /** @file * @@ -16,6 +17,9 @@ * Wait for a TFTP packet * * @v ptr Pointer to a struct tftp_state + * @v tftp_state::server::sin_addr TFTP server IP address + * @v tftp_state::client::sin_addr Client multicast IP address, or 0.0.0.0 + * @v tftp_state::client::sin_port Client UDP port * @v ip IP header * @v udp UDP header * @ret True This is our TFTP packet @@ -74,16 +78,20 @@ int await_tftp ( int ival __unused, void *ptr, unsigned short ptype __unused, * @v filename File name * @ret True Received a non-error response * @ret False Received error response / no response + * @ret tftp_state::server::sin_port TFTP server UDP port * @ret tftp_state::client::sin_port Client UDP port - * @ret tftp_state::client::blksize Always #TFTP_DEFAULT_BLKSIZE - * @ret *tftp The server's response, if any + * @ret tftp_state::blksize Always #TFTP_DEFAULT_BLKSIZE + * @ret *reply The server's response, if any + * @err #PXENV_STATUS_TFTP_OPEN_TIMEOUT TFTP open timed out + * @err other As returned by udp_transmit() + * @err other As set by tftp_set_errno() * * Send a TFTP/TFTM/MTFTP RRQ (read request) to a TFTP server, and * return the server's reply (which may be an OACK, DATA or ERROR * packet). The server's reply will not be acknowledged, or processed * in any way. * - * If tftp_state::server::sin_port is 0, the standard tftp server port + * If tftp_state::server::sin_port is 0, the standard TFTP server port * (#TFTP_PORT) will be used. * * If tftp_state::client::sin_addr is not 0.0.0.0, it will be used as @@ -121,6 +129,10 @@ int await_tftp ( int ival __unused, void *ptr, unsigned short ptype __unused, * be assumed until the OACK packet is processed (by a subsequent call * to tftp_process_opts()). * + * tftp_state::server::sin_port will be set to the UDP port from which + * the server's response originated. This may or may not be the port + * to which the open request was sent. + * * The options "blksize", "tsize" and "multicast" will always be * appended to a TFTP open request. Servers that do not understand * any of these options should simply ignore them. @@ -129,9 +141,11 @@ int await_tftp ( int ival __unused, void *ptr, unsigned short ptype __unused, * the caller is responsible for calling join_group() and * leave_group() at appropriate times. * + * If the response from the server is a TFTP ERROR packet, tftp_open() + * will return False and #errno will be set accordingly. */ int tftp_open ( struct tftp_state *state, const char *filename, - union tftp_any **tftp ) { + union tftp_any **reply ) { static unsigned short lport = 2000; /* local port */ int fixed_lport; struct tftp_rrq rrq; @@ -164,7 +178,7 @@ int tftp_open ( struct tftp_state *state, const char *filename, state->blksize = TFTP_DEFAULT_BLKSIZE; /* Nullify received packet pointer */ - *tftp = NULL; + *reply = NULL; /* Transmit RRQ until we get a response */ for ( retry = 0 ; retry < MAX_TFTP_RETRIES ; retry++ ) { @@ -186,7 +200,13 @@ int tftp_open ( struct tftp_state *state, const char *filename, /* Wait for response */ if ( await_reply ( await_tftp, 0, state, timeout ) ) { - *tftp = ( union tftp_any * ) &nic.packet[ETH_HLEN]; + *reply = ( union tftp_any * ) &nic.packet[ETH_HLEN]; + state->server.sin_port = + ntohs ( (*reply)->common.udp.dest ); + if ( ntohs ( (*reply)->common.opcode ) == TFTP_ERROR ){ + tftp_set_errno ( &(*reply)->error ); + return 0; + } return 1; } } @@ -321,13 +341,14 @@ int tftp_process_opts ( struct tftp_state *state, struct tftp_oack *oack ) { * @v tftp_state::block Most recently received block number * @ret True Acknowledgement packet was sent * @ret False Acknowledgement packet was not sent + * @err other As returned by udp_transmit() * * Send a TFTP ACK packet for the most recently received block. * * This sends only a single ACK packet; it does not wait for the * server's response. */ -int tftp_ack ( struct tftp_state *state ) { +int tftp_ack_nowait ( struct tftp_state *state ) { struct tftp_ack ack; ack.opcode = htons ( TFTP_ACK ); @@ -337,6 +358,59 @@ int tftp_ack ( struct tftp_state *state ) { sizeof ( ack ), &ack ); } +/** + * Acknowledge a TFTP packet and wait for a response + * + * @v state TFTP transfer state + * @v tftp_state::server::sin_addr TFTP server IP address + * @v tftp_state::server::sin_port TFTP server UDP port + * @v tftp_state::client::sin_port Client UDP port + * @v tftp_state::block Most recently received block number + * @ret True Received a non-error response + * @ret False Received error response / no response + * @ret *reply The server's response, if any + * @err #PXENV_STATUS_TFTP_READ_TIMEOUT Timed out waiting for a response + * @err other As returned by tftp_ack_nowait() + * @err other As set by tftp_set_errno() + * + * Send a TFTP ACK packet for the most recently received data block, + * and keep transmitting this ACK until we get a response from the + * server (e.g. a new data block). + * + * If the response is a TFTP DATA packet, no processing is done. + * Specifically, the block number is not checked to ensure that this + * is indeed the next data block in the sequence, nor is + * tftp_state::block updated with the new block number. + * + * If the response from the server is a TFTP ERROR packet, tftp_open() + * will return False and #errno will be set accordingly. + */ +int tftp_ack ( struct tftp_state *state, union tftp_any **reply ) { + int retry; + + *reply = NULL; + for ( retry = 0 ; retry < MAX_TFTP_RETRIES ; retry++ ) { + long timeout = rfc2131_sleep_interval ( TFTP_REXMT, retry ); + /* ACK the last data block */ + if ( ! tftp_ack_nowait ( state ) ) { + DBG ( "TFTP: could not send ACK: %m\n" ); + return 0; + } + if ( await_reply ( await_tftp, 0, &state, timeout ) ) { + /* We received a reply */ + *reply = ( union tftp_any * ) &nic.packet[ETH_HLEN]; + if ( ntohs ( (*reply)->common.opcode ) == TFTP_ERROR ){ + tftp_set_errno ( &(*reply)->error ); + return 0; + } + return 1; + } + } + DBG ( "TFTP: ACK retries exceeded\n" ); + errno = PXENV_STATUS_TFTP_READ_TIMEOUT; + return 0; +} + /** * Send a TFTP error * @@ -344,7 +418,7 @@ int tftp_ack ( struct tftp_state *state ) { * @v tftp_state::server::sin_addr TFTP server IP address * @v tftp_state::server::sin_port TFTP server UDP port * @v tftp_state::client::sin_port Client UDP port - * @v err TFTP error code + * @v errcode TFTP error code * @v errmsg Descriptive error string * @ret True Error packet was sent * @ret False Error packet was not sent @@ -352,14 +426,36 @@ int tftp_ack ( struct tftp_state *state ) { * Send a TFTP ERROR packet back to the server to terminate the * transfer. */ -int tftp_error ( struct tftp_state *state, int err, const char *errmsg ) { +int tftp_error ( struct tftp_state *state, int errcode, const char *errmsg ) { struct tftp_error error; - DBG ( "TFTPCORE: aborting with error %d (%s)\n", err, errmsg ); + DBG ( "TFTPCORE: aborting with error %d (%s)\n", errcode, errmsg ); error.opcode = htons ( TFTP_ERROR ); - error.errcode = htons ( err ); + error.errcode = htons ( errcode ); strncpy ( error.errmsg, errmsg, sizeof ( error.errmsg ) ); return udp_transmit ( state->server.sin_addr.s_addr, state->client.sin_port, state->server.sin_port, sizeof ( error ), &error ); } + +/** + * Interpret a TFTP error + * + * @v error Pointer to a struct tftp_error + * + * Sets #errno based on the error code in a TFTP ERROR packet. + */ +void tftp_set_errno ( struct tftp_error *error ) { + static int errmap[] = { + [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, + }; + unsigned int errcode = ntohs ( error->errcode ); + + errno = 0; + if ( errcode < ( sizeof(errmap) / sizeof(errmap[0]) ) ) + errno = errmap[errcode]; + if ( ! errno ) + errno = PXENV_STATUS_TFTP_ERROR_OPCODE; +}