summaryrefslogtreecommitdiffstats
path: root/src/proto
diff options
context:
space:
mode:
authorMichael Brown2005-06-01 13:01:59 +0200
committerMichael Brown2005-06-01 13:01:59 +0200
commit0b048e9cfb741e5f2c9a4b12ef79502d63cef93f (patch)
tree205e5718cee8585b8b7dbb96f99f481134860ab4 /src/proto
parentAdd tftp_ack() and tftp_error() (diff)
downloadipxe-0b048e9cfb741e5f2c9a4b12ef79502d63cef93f.tar.gz
ipxe-0b048e9cfb741e5f2c9a4b12ef79502d63cef93f.tar.xz
ipxe-0b048e9cfb741e5f2c9a4b12ef79502d63cef93f.zip
Now have enough functions to implement a standard TFTP client in around 50
lines of code.
Diffstat (limited to 'src/proto')
-rw-r--r--src/proto/tftpcore.c118
1 files changed, 107 insertions, 11 deletions
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 );
@@ -338,13 +359,66 @@ int tftp_ack ( struct tftp_state *state ) {
}
/**
+ * 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
*
* @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 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;
+}