summaryrefslogtreecommitdiffstats
path: root/src/proto
diff options
context:
space:
mode:
authorMichael Brown2005-05-31 16:47:58 +0200
committerMichael Brown2005-05-31 16:47:58 +0200
commit8ae966720d7063f88dec5e9927d4dc158e9fb2e8 (patch)
treecf02793bec42aa48aa5f0e73db71c854516bac25 /src/proto
parentpatch of Helge Wagner (diff)
downloadipxe-8ae966720d7063f88dec5e9927d4dc158e9fb2e8.tar.gz
ipxe-8ae966720d7063f88dec5e9927d4dc158e9fb2e8.tar.xz
ipxe-8ae966720d7063f88dec5e9927d4dc158e9fb2e8.zip
First version
Diffstat (limited to 'src/proto')
-rw-r--r--src/proto/tftpcore.c299
1 files changed, 299 insertions, 0 deletions
diff --git a/src/proto/tftpcore.c b/src/proto/tftpcore.c
new file mode 100644
index 00000000..da4399e5
--- /dev/null
+++ b/src/proto/tftpcore.c
@@ -0,0 +1,299 @@
+#include "tftp.h"
+#include "tcp.h" /* for struct tcphdr */
+#include "errno.h"
+#include "etherboot.h"
+
+/** @file
+ *
+ * TFTP core functions
+ *
+ * This file provides functions that are common to the TFTP (rfc1350),
+ * TFTM (rfc2090) and MTFTP (PXE) protocols.
+ *
+ */
+
+/**
+ * Wait for a TFTP packet
+ *
+ * @v ptr Pointer to a struct tftp_state
+ * @v ip IP header
+ * @v udp UDP header
+ * @ret True This is our TFTP packet
+ * @ret False This is not one of our TFTP packets
+ *
+ * Wait for a TFTP packet that is part of the current connection
+ * (i.e. comes from the TFTP server, has the correct destination port,
+ * and is addressed either to our IP address or to our multicast
+ * listening address).
+ */
+static int await_tftp ( int ival __unused, void *ptr,
+ unsigned short ptype __unused, struct iphdr *ip,
+ struct udphdr *udp, struct tcphdr *tcp __unused ) {
+ struct tftp_state *state = ptr;
+
+ /* Must have valid UDP (and, therefore, also IP) headers */
+ if ( ! udp ) {
+ return 0;
+ }
+ /* Packet must come from the TFTP server */
+ if ( ip->src.s_addr != state->server.sin_addr.s_addr )
+ return 0;
+ /* Packet must be addressed to the correct UDP port */
+ if ( ntohs ( udp->dest ) != state->client.sin_port )
+ return 0;
+ /* Packet must be addressed to us, or to our multicast
+ * listening address (if we have one).
+ */
+ if ( ! ( ( ip->dest.s_addr == arptable[ARP_CLIENT].ipaddr.s_addr ) ||
+ ( ( state->client.sin_addr.s_addr ) &&
+ ( ip->dest.s_addr == state->client.sin_addr.s_addr ) ) ) )
+ return 0;
+ return 1;
+}
+
+
+/**
+ * Issue a TFTP open request (RRQ)
+ *
+ * @v filename File name
+ * @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, or 0
+ * @v tftp_state::client::sin_addr Client multicast IP address, or 0.0.0.0
+ * @v tftp_state::client::sin_port Client UDP port, or 0
+ * @v tftp_state::blksize Requested blksize, or 0
+ * @ret True Received a non-error response
+ * @ret False Received error response / no response
+ * @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
+ *
+ * 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
+ * (#TFTP_PORT) will be used.
+ *
+ * If tftp_state::client::sin_addr is not 0.0.0.0, it will be used as
+ * a multicast listening address for replies from the TFTP server.
+ *
+ * If tftp_state::client::sin_port is 0, the standard mechanism of
+ * using a new, unique port number for each TFTP request will be used.
+ *
+ * For the various different types of TFTP server, you should treat
+ * tftp_state::client as follows:
+ *
+ * - Standard TFTP server: set tftp_state::client::sin_addr to
+ * 0.0.0.0 and tftp_state::client::sin_port to 0. tftp_open()
+ * will set tftp_state::client::sin_port to the assigned local UDP
+ * port.
+ *
+ * - TFTM server: set tftp_state::client::sin_addr to 0.0.0.0 and
+ * tftp_state::client::sin_port to 0. tftp_open() will set
+ * tftp_state::client::sin_port to the assigned local UDP port.
+ * (Your call to tftp_process_opts() will then overwrite both
+ * tftp_state::client::sin_addr and tftp_state::client::sin_port
+ * with the values return in the OACK packet.)
+ *
+ * - MTFTP server: set tftp_state::client::sin_addr to the client
+ * multicast address and tftp_state::client::sin_port to the
+ * client multicast port (both of which must be previously known,
+ * e.g. provided by a DHCP server). tftp_open() will not alter
+ * these values.
+ *
+ * If tftp_state::blksize is 0, the maximum blocksize
+ * (#TFTP_MAX_BLKSIZE) will be requested.
+ *
+ * On exit, tftp_state::blksize will always contain
+ * #TFTP_DEFAULT_BLKSIZE, since this is the blocksize value that must
+ * be assumed until the OACK packet is processed (by a subsequent call
+ * to tftp_process_opts()).
+ *
+ * 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.
+ *
+ * tftp_open() will not automatically join or leave multicast groups;
+ * the caller is responsible for calling join_group() and
+ * leave_group() at appropriate times.
+ *
+ */
+int tftp_open ( const char *filename, struct tftp_state *state,
+ union tftp_any **tftp ) {
+ static unsigned short lport = 2000; /* local port */
+ int fixed_lport;
+ struct tftp_rrq rrq;
+ unsigned int rrqlen;
+ int retry;
+
+ /* Flush receive queue */
+ rx_qdrain();
+
+ /* Default to blksize of TFTP_MAX_BLKSIZE if none specified */
+ if ( ! state->blksize )
+ state->blksize = TFTP_MAX_BLKSIZE;
+
+ /* Use default TFTP server port if none specified */
+ if ( ! state->server.sin_port )
+ state->server.sin_port = TFTP_PORT;
+
+ /* Determine whether or not to use lport */
+ fixed_lport = state->server.sin_port;
+
+ /* Set up RRQ */
+ rrq.opcode = htons ( TFTP_RRQ );
+ rrqlen = ( offsetof ( typeof ( rrq ), data ) +
+ sprintf ( rrq.data,
+ "%s%coctet%cblksize%c%d%ctsize%c0%cmulticast%c",
+ filename, 0, 0, 0, state->blksize, 0, 0, 0, 0 )
+ + 1 );
+
+ /* Set negotiated blksize to default value */
+ state->blksize = TFTP_DEFAULT_BLKSIZE;
+
+ /* Nullify received packet pointer */
+ *tftp = NULL;
+
+ /* Transmit RRQ until we get a response */
+ for ( retry = 0 ; retry < MAX_TFTP_RETRIES ; retry++ ) {
+ long timeout = rfc2131_sleep_interval ( TIMEOUT, retry );
+
+ /* Set client UDP port, if not already fixed */
+ if ( ! fixed_lport )
+ state->client.sin_port = ++lport;
+
+ /* Send the RRQ */
+ if ( ! udp_transmit ( state->server.sin_addr.s_addr,
+ state->client.sin_port,
+ state->server.sin_port,
+ rrqlen, &rrq ) )
+ return 0;
+
+ /* Wait for response */
+ if ( await_reply ( await_tftp, 0, state, timeout ) ) {
+ *tftp = ( union tftp_any * ) &nic.packet[ETH_HLEN];
+ return 1;
+ }
+ }
+
+ errno = PXENV_STATUS_TFTP_OPEN_TIMEOUT;
+ return 0;
+}
+
+/**
+ * Process a TFTP OACK packet
+ *
+ * @v oack The TFTP OACK packet
+ * @v state TFTP transfer state
+ * @ret True Options were processed successfully
+ * @ret False Options were not processed successfully
+ * @ret tftp_state::blksize Negotiated blksize
+ * @ret tftp_state::tsize File size (if known), or 0
+ * @ret tftp_state::client::sin_addr Client multicast IP address, or 0.0.0.0
+ * @ret tftp_state::client::sin_port Client UDP port
+ * @ret tftp_state::master Client is master
+ * @err EINVAL An invalid option value was encountered
+ *
+ * Process the options returned by the TFTP server in an rfc2347 OACK
+ * packet. The options "blksize" (rfc2348), "tsize" (rfc2349) and
+ * "multicast" (rfc2090) are recognised and processed; any other
+ * options are silently ignored.
+ *
+ * Where an option is not present in the OACK packet, the
+ * corresponding field(s) in #state will be left unaltered.
+ *
+ * Calling tftp_process_opts() does not send an acknowledgement for
+ * the OACK packet; this is the responsibility of the caller.
+ *
+ * @note If the "blksize" option is not present, tftp_state::blksize
+ * will @b not be implicitly set to #TFTP_DEFAULT_BLKSIZE. However,
+ * since tftp_open() always sets tftp_state::blksize to
+ * #TFTP_DEFAULT_BLKSIZE before returning, you probably don't need to
+ * worry about this.
+ */
+int tftp_process_opts ( struct tftp_oack *oack, struct tftp_state *state ) {
+ const char *p;
+ const char *end;
+
+ /* End of options */
+ end = ( ( char * ) &oack->udp ) + ntohs ( oack->udp.len );
+
+ /* Only possible error */
+ errno = EINVAL;
+
+ for ( p = oack->data ; p < end ; ) {
+ if ( strcasecmp ( "blksize", p ) == 0 ) {
+ p += 8;
+ state->blksize = strtoul ( p, &p, 10 );
+ if ( *p ) {
+ DBG ( "TFTPCORE: garbage \"%s\" "
+ "after blksize\n", p );
+ return 0;
+ }
+ p++;
+ } else if ( strcasecmp ( "tsize", p ) == 0 ) {
+ p += 6;
+ state->tsize = strtoul ( p, &p, 10 );
+ if ( *p ) {
+ DBG ( "TFTPCORE: garbage \"%s\" "
+ "after tsize\n", p );
+ return 0;
+ }
+ p++;
+ } else if ( strcasecmp ( "multicast", p ) == 0 ) {
+ char *e = strchr ( p, ',' );
+ if ( ( ! e ) || ( e >= end ) ) {
+ DBG ( "TFTPCORE: malformed multicast field "
+ "\"%s\"\n", p );
+ return 0;
+ }
+ /* IP address may be missing, in which case we
+ * should leave state->client.sin_addr
+ * unaltered.
+ */
+ if ( e != p ) {
+ int rc;
+ *e = '\0';
+ rc = inet_aton ( p, &state->client.sin_addr );
+ *e = ',';
+ if ( ! rc ) {
+ DBG ( "TFTPCORE: malformed multicast "
+ "IP address \"%s\"\n", p );
+ return 0;
+ }
+ }
+ p = e + 1;
+ /* UDP port may also be missing */
+ if ( *p != ',' ) {
+ state->client.sin_port = strtoul ( p, &p, 10 );
+ if ( *p != ',' ) {
+ DBG ( "TFTPCORE: garbage \"%s\" "
+ "after multicast port\n", p );
+ return 0;
+ }
+ } else {
+ p++;
+ }
+ /* "Master Client" must always be present */
+ state->master = strtoul ( p, &p, 10 );
+ if ( *p ) {
+ DBG ( "TFTPCORE: garbage \"%s\" "
+ "after multicast mc\n", p );
+ return 0;
+ }
+ p++;
+ } else {
+ p += strlen ( p ) + 1; /* skip option name */
+ p += strlen ( p ) + 1; /* skip option value */
+ }
+ }
+
+ if ( p > end ) {
+ DBG ( "TFTPCORE: overran options in OACK\n" );
+ return 0;
+ }
+
+ return 1;
+}