diff --git a/src/include/etherboot.h b/src/include/etherboot.h index 9d364282..669ee8b2 100644 --- a/src/include/etherboot.h +++ b/src/include/etherboot.h @@ -154,7 +154,6 @@ enum { #include "udp.h" #include "tcp.h" #include "bootp.h" -#include "tftp.h" #include "igmp.h" #include "nfs.h" #include "console.h" diff --git a/src/include/pxe.h b/src/include/pxe.h index 3264df0c..f8e2de79 100644 --- a/src/include/pxe.h +++ b/src/include/pxe.h @@ -4,6 +4,7 @@ #include "pxe_types.h" #include "pxe_api.h" #include "etherboot.h" +#include "tftp.h" /* Union used for PXE API calls; we don't know the type of the * structure until we interpret the opcode. Also, Status is available @@ -88,7 +89,7 @@ typedef struct pxe_stack { uint32_t magic_cookie; unsigned int len; int eof; - char data[TFTP_MAX_PACKET]; + char data[TFTP_MAX_BLKSIZE]; } tftpdata; struct { char *buffer; diff --git a/src/include/tftp.h b/src/include/tftp.h index 70192067..22329e50 100644 --- a/src/include/tftp.h +++ b/src/include/tftp.h @@ -1,13 +1,17 @@ #ifndef TFTP_H #define TFTP_H +/** @file */ + #include "in.h" #include "buffer.h" #include "nic.h" +#include "ip.h" +#include "udp.h" -#define TFTP_PORT 69 -#define TFTP_DEFAULTSIZE_PACKET 512 -#define TFTP_MAX_PACKET 1432 /* 512 */ +#define TFTP_PORT 69 /**< Default TFTP server port */ +#define TFTP_DEFAULT_BLKSIZE 512 +#define TFTP_MAX_BLKSIZE 1432 /* 512 */ #define TFTP_RRQ 1 #define TFTP_WRQ 2 @@ -16,52 +20,131 @@ #define TFTP_ERROR 5 #define TFTP_OACK 6 -#define TFTP_CODE_EOF 1 -#define TFTP_CODE_MORE 2 -#define TFTP_CODE_ERROR 3 -#define TFTP_CODE_BOOT 4 -#define TFTP_CODE_CFG 5 +#define TFTP_ERR_FILE_NOT_FOUND 1 /**< File not found */ +#define TFTP_ERR_ACCESS_DENIED 2 /**< Access violation */ +#define TFTP_ERR_DISK_FULL 3 /**< Disk full or allocation exceeded */ +#define TFTP_ERR_ILLEGAL_OP 4 /**< Illegal TFTP operation */ +#define TFTP_ERR_UNKNOWN_TID 5 /**< Unknown transfer ID */ +#define TFTP_ERR_FILE_EXISTS 6 /**< File already exists */ +#define TFTP_ERR_UNKNOWN_USER 7 /**< No such user */ +#define TFTP_ERR_BAD_OPTS 8 /**< Option negotiation failed */ -struct tftp_t { +/** A TFTP request (RRQ) packet */ +struct tftp_rrq { struct iphdr ip; struct udphdr udp; uint16_t opcode; - union { - uint8_t rrq[TFTP_DEFAULTSIZE_PACKET]; - struct { - uint16_t block; - uint8_t download[TFTP_MAX_PACKET]; - } data; - struct { - uint16_t block; - } ack; - struct { - uint16_t errcode; - uint8_t errmsg[TFTP_DEFAULTSIZE_PACKET]; - } err; - struct { - uint8_t data[TFTP_DEFAULTSIZE_PACKET+2]; - } oack; - } u; + char data[TFTP_DEFAULT_BLKSIZE]; } PACKED; -/* define a smaller tftp packet solely for making requests to conserve stack - 512 bytes should be enough */ -struct tftpreq_t { +/** A TFTP data (DATA) packet */ +struct tftp_data { struct iphdr ip; struct udphdr udp; uint16_t opcode; - union { - uint8_t rrq[512]; - struct { - uint16_t block; - } ack; - struct { - uint16_t errcode; - uint8_t errmsg[512-2]; - } err; - } u; + uint16_t block; + uint8_t data[TFTP_MAX_BLKSIZE]; } PACKED; + +/** A TFTP acknowledgement (ACK) packet */ +struct tftp_ack { + struct iphdr ip; + struct udphdr udp; + uint16_t opcode; + uint16_t block; +} PACKED; + +/** A TFTP error (ERROR) packet */ +struct tftp_error { + struct iphdr ip; + struct udphdr udp; + uint16_t opcode; + uint16_t errcode; + char errmsg[TFTP_DEFAULT_BLKSIZE]; +} PACKED; + +/** A TFTP options acknowledgement (OACK) packet */ +struct tftp_oack { + struct iphdr ip; + struct udphdr udp; + uint16_t opcode; + uint8_t data[TFTP_DEFAULT_BLKSIZE]; +} PACKED; + +/** The common header of all TFTP packets */ +struct tftp_common { + struct iphdr ip; + struct udphdr udp; + uint16_t opcode; +} PACKED; + +/** A union encapsulating all TFTP packet types */ +union tftp_any { + struct tftp_common common; + struct tftp_rrq rrq; + struct tftp_data data; + struct tftp_ack ack; + struct tftp_error error; + struct tftp_oack oack; +}; + +/** + * TFTP state + * + * This data structure holds the state for an ongoing TFTP transfer. + */ +struct tftp_state { + /** TFTP server address + * + * This is the IP address and UDP port from which data packets + * will be sent, and to which ACK packets should be sent. + */ + struct sockaddr_in server; + /** TFTP client address + * + * The IP address, if any, is the multicast address to which + * data packets will be sent. The client will always send + * packets from its own IP address. + * + * The UDP port is the port from which the open request will + * be sent, and to which data packets will be sent. (Due to + * the "design" of the MTFTP protocol, the master client will + * receive its first data packet as unicast, and subsequent + * packets as multicast.) + */ + struct sockaddr_in client; + /** Master client + * + * This will be true if the client is the master client for a + * multicast protocol (i.e. MTFTP or TFTM). (It will always + * be true for a non-multicast protocol, i.e. plain old TFTP). + */ + int master; + /** Data block size + * + * This is the "blksize" option negotiated with the TFTP + * server. (If the TFTP server does not support TFTP options, + * this will default to 512). + */ + unsigned int blksize; + /** File size + * + * This is the value returned in the "tsize" option from the + * TFTP server. If the TFTP server does not support the + * "tsize" option, this value will be zero. + */ + off_t tsize; + /** Last received block + * + * The block number of the most recent block received from the + * TFTP server. Note that the first data block is block 1; a + * value of 0 indicates that no data blocks have yet been + * received. + */ + unsigned int block; +}; + + struct tftpreq_info_t { struct sockaddr_in *server; diff --git a/src/interface/pxe/pxe_tftp.c b/src/interface/pxe/pxe_tftp.c index 1faaf25b..2d824e11 100644 --- a/src/interface/pxe/pxe_tftp.c +++ b/src/interface/pxe/pxe_tftp.c @@ -131,7 +131,7 @@ PXENV_EXIT_t pxenv_tftp_open ( struct s_PXENV_TFTP_OPEN *tftp_open ) { request.blksize = tftp_open->PacketSize; DBG ( " %@:%d/%s (%d)", tftp_open->ServerIPAddress, tftp_open->TFTPPort, request.name, request.blksize ); - if ( !request.blksize ) request.blksize = TFTP_DEFAULTSIZE_PACKET; + if ( !request.blksize ) request.blksize = TFTP_DEFAULT_BLKSIZE; /* Make request and get first packet */ if ( !tftp_block ( &request, &block ) ) { tftp_open->Status = PXENV_STATUS_TFTP_FILE_NOT_FOUND; diff --git a/src/proto/tcp.c b/src/proto/tcp.c index 197bfce2..1e0531d5 100644 --- a/src/proto/tcp.c +++ b/src/proto/tcp.c @@ -1,7 +1,7 @@ #include "etherboot.h" #include "ip.h" #include "tcp.h" - +#include "nic.h" void build_tcp_hdr(unsigned long destip, unsigned int srcsock, unsigned int destsock, long send_seq, long recv_seq, diff --git a/src/proto/tftm.c b/src/proto/tftm.c index 426d0dda..6dbf1af2 100644 --- a/src/proto/tftm.c +++ b/src/proto/tftm.c @@ -1,3 +1,5 @@ +#if 0 + /************************************************************************** * * proto_tftm.c -- Etherboot Multicast TFTP @@ -481,3 +483,5 @@ static struct protocol tftm_protocol __protocol = { .default_port = TFTM_PORT, .load = url_tftm, }; + +#endif diff --git a/src/proto/tftp.c b/src/proto/tftp.c index b154f575..e5bf5a83 100644 --- a/src/proto/tftp.c +++ b/src/proto/tftp.c @@ -1,169 +1,111 @@ #include "etherboot.h" -#include "in.h" -#include "nic.h" #include "proto.h" +#include "errno.h" #include "tftp.h" +#include "tftpcore.h" -/* Utility function for tftp_block() */ -static int await_tftp ( int ival, void *ptr __unused, - unsigned short ptype __unused, struct iphdr *ip, - struct udphdr *udp, struct tcphdr *tcp __unused ) { - if ( ! udp ) { +/** @file + * + * TFTP protocol + */ + +/** + * Process a TFTP block + * + */ +static inline int process_tftp_data ( struct tftp_state *state, + struct tftp_data *data, + struct buffer *buffer, + int *eof ) { + unsigned int blksize; + + /* Check it's the correct DATA block */ + if ( ntohs ( data->block ) != ( state->block + 1 ) ) { + DBG ( "TFTP: got block %d, wanted block %d\n", + ntohs ( data->block ), state->block + 1 ); + return 1; + } + /* Check it's an acceptable size */ + blksize = ( ntohs ( data->udp.len ) + + offsetof ( typeof ( *data ), udp ) + - offsetof ( typeof ( *data ), data ) ); + if ( blksize > state->blksize ) { + DBG ( "TFTP: oversized block size %d (max %d)\n", + blksize, state->blksize ); + return 1; + } + /* Place block in the buffer */ + if ( ! fill_buffer ( buffer, data->data, state->block * state->blksize, + blksize ) ) { + DBG ( "TFTP: could not place data in buffer: %m\n" ); return 0; } - if ( arptable[ARP_CLIENT].ipaddr.s_addr != ip->dest.s_addr ) - return 0; - if ( ntohs ( udp->dest ) != ival ) - return 0; + /* Increment block counter */ + state->block++; + /* Set EOF marker */ + *eof = ( blksize < state->blksize ); return 1; } -/* - * Download a single block via TFTP. This function is non-static so - * that pxe_export.c can call it. - * - */ -int tftp_block ( struct tftpreq_info_t *request, - struct tftpblk_info_t *block ) { - static struct sockaddr_in server; - static unsigned short lport = 2000; /* local port */ - struct tftp_t *rcvd = NULL; - static struct tftpreq_t xmit; - static unsigned short xmitlen = 0; - static unsigned short blockidx = 0; /* Last block received */ - static unsigned short retry = 0; /* Retry attempts on last block */ - static int blksize = 0; - unsigned short recvlen = 0; - - /* If this is a new request (i.e. if name is set), fill in - * transmit block with RRQ and send it. - */ - if ( request ) { - rx_qdrain(); /* Flush receive queue */ - xmit.opcode = htons(TFTP_RRQ); - xmitlen = (void*)&xmit.u.rrq - (void*)&xmit + - sprintf((char*)xmit.u.rrq, "%s%coctet%cblksize%c%d", - request->name, 0, 0, 0, request->blksize) - + 1; /* null terminator */ - blockidx = 0; /* Reset counters */ - retry = 0; - blksize = TFTP_DEFAULTSIZE_PACKET; - lport++; /* Use new local port */ - server = *(request->server); - if ( !udp_transmit(server.sin_addr.s_addr, lport, - server.sin_port, xmitlen, &xmit) ) - return (0); - } - /* Exit if no transfer in progress */ - if ( !blksize ) return (0); - /* Loop to wait until we get a packet we're interested in */ - block->data = NULL; /* Used as flag */ - while ( block->data == NULL ) { - long timeout = rfc2131_sleep_interval ( blockidx ? TFTP_REXMT : - TIMEOUT, retry ); - if ( !await_reply(await_tftp, lport, NULL, timeout) ) { - /* No packet received */ - if ( retry++ > MAX_TFTP_RETRIES ) break; - /* Retransmit last packet */ - if ( !blockidx ) lport++; /* New lport if new RRQ */ - if ( !udp_transmit(server.sin_addr.s_addr, lport, - server.sin_port, xmitlen, &xmit) ) - return (0); - continue; /* Back to waiting for packet */ - } - /* Packet has been received */ - rcvd = (struct tftp_t *)&nic.packet[ETH_HLEN]; - recvlen = ntohs(rcvd->udp.len) - sizeof(struct udphdr) - - sizeof(rcvd->opcode); - server.sin_port = ntohs(rcvd->udp.src); - retry = 0; /* Reset retry counter */ - switch ( htons(rcvd->opcode) ) { - case TFTP_ERROR : { - printf ( "TFTP error %d (%s)\n", - ntohs(rcvd->u.err.errcode), - rcvd->u.err.errmsg ); - return (0); /* abort */ - } - case TFTP_OACK : { - const char *p = rcvd->u.oack.data; - const char *e = p + recvlen - 10; /* "blksize\0\d\0" */ - - *((char*)(p+recvlen-1)) = '\0'; /* Force final 0 */ - if ( blockidx || !request ) break; /* Too late */ - if ( recvlen <= TFTP_MAX_PACKET ) /* sanity */ { - /* Check for blksize option honoured */ - while ( p < e ) { - if ( strcasecmp("blksize",p) == 0 && - p[7] == '\0' ) { - blksize = strtoul(p+8,&p,10); - p++; /* skip null */ - } - while ( *(p++) ) {}; - } - } - if ( blksize < TFTP_DEFAULTSIZE_PACKET || - blksize > request->blksize ) { - /* Incorrect blksize - error and abort */ - xmit.opcode = htons(TFTP_ERROR); - xmit.u.err.errcode = 8; - xmitlen = (void*)&xmit.u.err.errmsg - - (void*)&xmit - + sprintf((char*)xmit.u.err.errmsg, - "RFC1782 error") - + 1; - udp_transmit(server.sin_addr.s_addr, lport, - server.sin_port, xmitlen, &xmit); - return (0); - } - } break; - case TFTP_DATA : - if ( ntohs(rcvd->u.data.block) != ( blockidx + 1 ) ) - break; /* Re-ACK last block sent */ - if ( recvlen > ( blksize+sizeof(rcvd->u.data.block) ) ) - break; /* Too large; ignore */ - block->data = rcvd->u.data.download; - block->block = ++blockidx; - block->len = recvlen - sizeof(rcvd->u.data.block); - block->eof = ( (unsigned short)block->len < blksize ); - /* If EOF, zero blksize to indicate transfer done */ - if ( block->eof ) blksize = 0; - break; - default: break; /* Do nothing */ - } - /* Send ACK */ - xmit.opcode = htons(TFTP_ACK); - xmit.u.ack.block = htons(blockidx); - xmitlen = TFTP_MIN_PACKET; - udp_transmit ( server.sin_addr.s_addr, lport, server.sin_port, - xmitlen, &xmit ); - } - return ( block->data ? 1 : 0 ); -} - -/* +/** * Download a file via TFTP * */ int tftp ( char *url __unused, struct sockaddr_in *server, char *file, struct buffer *buffer ) { - struct tftpreq_info_t request_data = { - .server = server, - .name = file, - .blksize = TFTP_MAX_PACKET, - }; - struct tftpreq_info_t *request = &request_data; - struct tftpblk_info_t block; - off_t offset = 0; + struct tftp_state state; + union tftp_any *reply; + int eof = 0; + /* Initialise TFTP state */ + memset ( &state, 0, sizeof ( state ) ); + state.server = *server; + + /* Open the file */ + if ( ! tftp_open ( &state, file, &reply ) ) { + DBG ( "TFTP: could not open %@:%d/%s : %m\n", + server->sin_addr.s_addr, server->sin_port, file ); + return 0; + } + + /* Process OACK, if any */ + if ( ntohs ( reply->common.opcode ) == TFTP_OACK ) { + if ( ! tftp_process_opts ( &state, &reply->oack ) ) { + DBG ( "TFTP: option processing failed : %m\n" ); + return 0; + } + reply = NULL; + } + + /* Fetch file, a block at a time */ do { - if ( ! tftp_block ( request, &block ) ) - return 0; - if ( ! fill_buffer ( buffer, block.data, offset, block.len ) ) - return 0; + /* Get next block to process. (On the first time + * through, we may already have a block from + * tftp_open()). + */ + if ( ! reply ) { + if ( ! tftp_ack ( &state, &reply ) ) { + DBG ( "TFTP: could not get next block: %m\n" ); + return 0; + } + } twiddle(); - offset += block.len; - request = NULL; /* Send request only once */ - } while ( ! block.eof ); + /* Check it's a DATA block */ + if ( ntohs ( reply->common.opcode ) != TFTP_DATA ) { + DBG ( "TFTP: unexpected opcode %d\n", + ntohs ( reply->common.opcode ) ); + errno = PXENV_STATUS_TFTP_UNKNOWN_OPCODE; + return 0; + } + /* Process the DATA block */ + if ( ! process_tftp_data ( &state, &reply->data, buffer, + &eof ) ) + return 0; + reply = NULL; + } while ( ! eof ); + + /* ACK the final packet, as a courtesy to the server */ + tftp_ack_nowait ( &state ); return 1; } diff --git a/src/proto/tftpcore.c b/src/proto/tftpcore.c index 51ad8b43..491bab6d 100644 --- a/src/proto/tftpcore.c +++ b/src/proto/tftpcore.c @@ -47,21 +47,32 @@ int await_tftp ( int ival __unused, void *ptr, unsigned short ptype __unused, /* Must have valid UDP (and, therefore, also IP) headers */ if ( ! udp ) { + DBG2 ( "TFTPCORE: not UDP\n" ); return 0; } /* Packet must come from the TFTP server */ - if ( ip->src.s_addr != state->server.sin_addr.s_addr ) + if ( ip->src.s_addr != state->server.sin_addr.s_addr ) { + DBG2 ( "TFTPCORE: from %@, not from TFTP server %@\n", + 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 ) + if ( ntohs ( udp->dest ) != state->client.sin_port ) { + DBG2 ( "TFTPCORE: to UDP port %d, not to TFTP port %d\n", + 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 ) ) ) ) + ( ip->dest.s_addr == state->client.sin_addr.s_addr ) ) ) ) { + DBG2 ( "TFTPCORE: to %@, not to %@ (or %@)\n", + ip->dest.s_addr, arptable[ARP_CLIENT].ipaddr.s_addr, + state->client.sin_addr.s_addr ); return 0; + } return 1; } @@ -164,7 +175,7 @@ int tftp_open ( struct tftp_state *state, const char *filename, state->server.sin_port = TFTP_PORT; /* Determine whether or not to use lport */ - fixed_lport = state->server.sin_port; + fixed_lport = state->client.sin_port; /* Set up RRQ */ rrq.opcode = htons ( TFTP_RRQ ); @@ -202,7 +213,11 @@ int tftp_open ( struct tftp_state *state, const char *filename, if ( await_reply ( await_tftp, 0, state, timeout ) ) { *reply = ( union tftp_any * ) &nic.packet[ETH_HLEN]; state->server.sin_port = - ntohs ( (*reply)->common.udp.dest ); + ntohs ( (*reply)->common.udp.src ); + DBG ( "TFTPCORE: got reply from %@:%d (type %d)\n", + state->server.sin_addr.s_addr, + state->server.sin_port, + ntohs ( (*reply)->common.opcode ) ); if ( ntohs ( (*reply)->common.opcode ) == TFTP_ERROR ){ tftp_set_errno ( &(*reply)->error ); return 0; @@ -211,6 +226,7 @@ int tftp_open ( struct tftp_state *state, const char *filename, } } + DBG ( "TFTPCORE: open request timed out\n" ); errno = PXENV_STATUS_TFTP_OPEN_TIMEOUT; return 0; } @@ -250,6 +266,8 @@ int tftp_process_opts ( struct tftp_state *state, struct tftp_oack *oack ) { const char *p; const char *end; + DBG ( "TFTPCORE: processing OACK\n" ); + /* End of options */ end = ( ( char * ) &oack->udp ) + ntohs ( oack->udp.len ); @@ -266,6 +284,7 @@ int tftp_process_opts ( struct tftp_state *state, struct tftp_oack *oack ) { return 0; } p++; + DBG ( "TFTPCORE: got blksize %d\n", state->blksize ); } else if ( strcasecmp ( "tsize", p ) == 0 ) { p += 6; state->tsize = strtoul ( p, &p, 10 ); @@ -275,6 +294,7 @@ int tftp_process_opts ( struct tftp_state *state, struct tftp_oack *oack ) { return 0; } p++; + DBG ( "TFTPCORE: got tsize %d\n", state->tsize ); } else if ( strcasecmp ( "multicast", p ) == 0 ) { char *e = strchr ( p, ',' ); if ( ( ! e ) || ( e >= end ) ) { @@ -317,7 +337,12 @@ int tftp_process_opts ( struct tftp_state *state, struct tftp_oack *oack ) { return 0; } p++; + DBG ( "TFTPCORE: got multicast %@:%d (%s)\n", + state->client.sin_addr.s_addr, + state->client.sin_port, + ( state->master ? "master" : "not master" ) ); } else { + DBG ( "TFTPCORE: unknown option \"%s\"\n", p ); p += strlen ( p ) + 1; /* skip option name */ p += strlen ( p ) + 1; /* skip option value */ } @@ -351,6 +376,7 @@ int tftp_process_opts ( struct tftp_state *state, struct tftp_oack *oack ) { int tftp_ack_nowait ( struct tftp_state *state ) { struct tftp_ack ack; + DBG ( "TFTPCORE: acknowledging data block %d\n", state->block ); ack.opcode = htons ( TFTP_ACK ); ack.block = htons ( state->block ); return udp_transmit ( state->server.sin_addr.s_addr, @@ -396,9 +422,11 @@ int tftp_ack ( struct tftp_state *state, union tftp_any **reply ) { DBG ( "TFTP: could not send ACK: %m\n" ); return 0; } - if ( await_reply ( await_tftp, 0, &state, timeout ) ) { + if ( await_reply ( await_tftp, 0, state, timeout ) ) { /* We received a reply */ *reply = ( union tftp_any * ) &nic.packet[ETH_HLEN]; + DBG ( "TFTPCORE: got reply (type %d)\n", + ntohs ( (*reply)->common.opcode ) ); if ( ntohs ( (*reply)->common.opcode ) == TFTP_ERROR ){ tftp_set_errno ( &(*reply)->error ); return 0; @@ -406,7 +434,7 @@ int tftp_ack ( struct tftp_state *state, union tftp_any **reply ) { return 1; } } - DBG ( "TFTP: ACK retries exceeded\n" ); + DBG ( "TFTP: timed out during read\n" ); errno = PXENV_STATUS_TFTP_READ_TIMEOUT; return 0; }