Now have enough functions to implement a standard TFTP client in around 50
authorMichael Brown <mcb30@etherboot.org>
Wed, 1 Jun 2005 11:01:59 +0000 (11:01 +0000)
committerMichael Brown <mcb30@etherboot.org>
Wed, 1 Jun 2005 11:01:59 +0000 (11:01 +0000)
lines of code.

src/include/tftpcore.h [new file with mode: 0644]
src/proto/tftpcore.c

diff --git a/src/include/tftpcore.h b/src/include/tftpcore.h
new file mode 100644 (file)
index 0000000..cbe86bd
--- /dev/null
@@ -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 */
index 0bdffef..51ad8b4 100644 (file)
@@ -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;
+}