123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541 |
- #include "tftp.h"
- #include "old_tcp.h" /* for struct tcphdr */
- #include "errno.h"
- #include "etherboot.h"
- #include "tftpcore.h"
-
- /** @file */
-
- /**
- * await_reply() filter for TFTP packets
- *
- * @v ptr Pointer to a struct tftp_state
- * @v tftp_state::server::sin_addr TFTP server IP address
- * @v tftp_state::lport Client UDP port
- * @v tftp_state::multicast::sin_addr Multicast IP address, or 0.0.0.0
- * @v tftp_state::multicast::sin_port Multicast UDP port, or 0
- * @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 and UDP port, or to our
- * multicast listening address and UDP port).
- *
- * Use await_tftp() in code such as
- *
- * @code
- *
- * if ( await_reply ( await_tftp, 0, &tftp_state, timeout ) ) {
- * ...
- * }
- *
- * @endcode
- */
- 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 ) {
- 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 ) {
- DBG2 ( "TFTPCORE: from %@, not from TFTP server %@\n",
- ip->src.s_addr, state->server.sin_addr.s_addr );
- return 0;
- }
- /* Packet may be addressed to our IP address and unicast UDP
- * port
- */
- if ( ( ip->dest.s_addr == arptable[ARP_CLIENT].ipaddr.s_addr ) &&
- ( ntohs ( udp->dest ) == state->lport ) ) {
- return 1;
- }
- /* Packet may be addressed to our multicast IP address and UDP
- * port, if we have one
- */
- if ( ( state->multicast.sin_addr.s_addr ) &&
- ( ip->dest.s_addr == state->multicast.sin_addr.s_addr ) &&
- ( ntohs ( udp->dest ) == state->multicast.sin_port ) ) {
- return 1;
- }
- DBG2 ( "TFTPCORE: to %@:%d, not to %@:%d (or %@:%d)\n",
- ip->dest.s_addr, ntohs ( udp->dest ),
- arptable[ARP_CLIENT].ipaddr.s_addr, state->lport,
- state->multicast.sin_addr.s_addr, state->multicast.sin_port );
- return 0;
- }
-
- /**
- * Retrieve a TFTP packet
- *
- * @v state TFTP transfer state
- * @v tftp_state::server::sin_addr TFTP server IP address
- * @v tftp_state::lport Client UDP port
- * @v tftp_state::multicast::sin_addr Multicast IP address, or 0.0.0.0
- * @v tftp_state::multicast::sin_port Multicast UDP port, or 0
- * @v timeout Time to wait for a response
- * @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 No response received in time
- * @err other As set by tftp_set_errno()
- *
- * Retrieve the next packet sent by the TFTP server, if any is sent
- * within the specified timeout period. The packet is returned via
- * #reply. If no packet is received within the timeout period, a NULL
- * value will be stored in #reply.
- *
- * If the response from the server is a TFTP ERROR packet, tftp_get()
- * will return False and #errno will be set accordingly.
- *
- * You can differentiate between "received no response" and "received
- * an error response" by checking #reply; if #reply is NULL then no
- * response was received.
- */
- int tftp_get ( struct tftp_state *state, long timeout,
- union tftp_any **reply ) {
-
- *reply = NULL;
-
- if ( ! await_reply ( await_tftp, 0, state, timeout ) ) {
- errno = PXENV_STATUS_TFTP_READ_TIMEOUT;
- return 0;
- }
-
- *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;
- }
- return 1;
- }
-
- /**
- * Issue a TFTP open request (RRQ)
- *
- * @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::lport Client UDP port, or 0
- * @v tftp_state::multicast::sin_addr Multicast IP address, or 0.0.0.0
- * @v tftp_state::multicast::sin_port Multicast UDP port, or 0
- * @v tftp_state::blksize Requested blksize, or 0
- * @v filename File name
- * @v multicast Enable/disable rfc2090 multicast TFTP
- * @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::lport Client UDP port
- * @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
- * (#TFTP_PORT) will be used.
- *
- * If tftp_state::lport is 0, the standard mechanism of
- * using a new, unique port number for each TFTP request will be used.
- *
- * If tftp_state::multicast::sin_addr is not 0.0.0.0, it (and
- * tftp_state::multicast::sin_port) will be used as a multicast
- * listening address for replies from the TFTP server.
- *
- * For the various different types of TFTP server, you should treat
- * tftp_state::lport and tftp_state::multicast as follows:
- *
- * - Standard TFTP server: set tftp_state::lport to 0,
- * tftp_state::multicast::sin_addr to 0.0.0.0 and
- * tftp_state::multicast::sin_port to 0. tftp_open() will set
- * tftp_state::lport to the assigned local UDP port.
- *
- * - TFTM server: set tftp_state::lport to 0,
- * tftp_state::multicast::sin_addr to 0.0.0.0 and
- * tftp_state::multicast::sin_port to 0. tftp_open() will set
- * tftp_state::lport to the assigned local UDP port. (Your call
- * to tftp_process_opts() will then overwrite both
- * tftp_state::multicast::sin_addr and
- * tftp_state::multicast::sin_port with the values specified in
- * the OACK packet.)
- *
- * - MTFTP server: set tftp_state::multicast::sin_addr to the
- * multicast address and both tftp_state::lport and
- * tftp_state::multicast::sin_port to the 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()).
- *
- * 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" and "tsize" will always be appended to a TFTP
- * open request. The option "multicast" will be appended to the
- * request if #multicast is True. 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.
- *
- * 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 **reply, int multicast ) {
- static unsigned short lport = 2000; /* local port */
- int fixed_lport;
- struct tftp_rrq rrq;
- char *p;
- 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->lport;
-
- /* Set up RRQ */
- rrq.opcode = htons ( TFTP_RRQ );
- p = rrq.data;
- p += sprintf ( p, "%s%coctet%cblksize%c%d%ctsize%c0",
- filename, 0, 0, 0, state->blksize, 0, 0 ) + 1;
- if ( multicast ) {
- p += sprintf ( p, "multicast%c", 0 ) + 1;
- }
- rrqlen = ( p - ( char * ) &rrq );
-
- /* Set negotiated blksize to default value */
- state->blksize = TFTP_DEFAULT_BLKSIZE;
-
- /* Nullify received packet pointer */
- *reply = 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->lport = ++lport;
-
- /* Send the RRQ */
- DBG ( "TFTPCORE: requesting %@:%d/%s from port %d\n",
- state->server.sin_addr.s_addr, state->server.sin_port,
- rrq.data, state->lport );
- if ( ! udp_transmit ( state->server.sin_addr.s_addr,
- state->lport, state->server.sin_port,
- rrqlen, &rrq ) )
- return 0;
-
- /* Wait for response */
- if ( tftp_get ( state, timeout, reply ) ) {
- /* We got a non-error response */
- state->server.sin_port
- = ntohs ( (*reply)->common.udp.src );
- DBG ( "TFTP server is at %@:%d\n",
- state->server.sin_addr.s_addr,
- state->server.sin_port );
- return 1;
- }
- if ( *reply ) {
- /* We got an error response; abort */
- return 0;
- }
- }
-
- DBG ( "TFTPCORE: open request timed out\n" );
- errno = PXENV_STATUS_TFTP_OPEN_TIMEOUT;
- return 0;
- }
-
- /**
- * Process a TFTP OACK packet
- *
- * @v state TFTP transfer state
- * @v oack The TFTP OACK packet
- * @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::multicast::sin_addr Multicast IP address, or 0.0.0.0
- * @ret tftp_state::multicast::sin_port Multicast UDP port, or 0
- * @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_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 );
-
- /* 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++;
- DBG ( "TFTPCORE: got blksize %d\n", state->blksize );
- } 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++;
- DBG ( "TFTPCORE: got tsize %d\n", state->tsize );
- } else if ( strcasecmp ( "multicast", p ) == 0 ) {
- p += 10;
- 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->multicast.sin_addr
- * unaltered.
- */
- if ( e != p ) {
- int rc;
- *e = '\0';
- rc = inet_aton ( p,
- &state->multicast.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->multicast.sin_port
- = strtoul ( p, &p, 10 );
- if ( *p != ',' ) {
- DBG ( "TFTPCORE: garbage \"%s\" "
- "after multicast port\n", p );
- return 0;
- }
- }
- 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++;
- DBG ( "TFTPCORE: got multicast %@:%d (%s)\n",
- state->multicast.sin_addr.s_addr,
- state->multicast.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 */
- }
- }
-
- if ( p > end ) {
- DBG ( "TFTPCORE: overran options in OACK\n" );
- return 0;
- }
-
- return 1;
- }
-
- /**
- * Acknowledge a TFTP packet
- *
- * @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::lport Client UDP port
- * @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_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,
- state->lport, state->server.sin_port,
- 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::lport 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 ( tftp_get ( state, timeout, reply ) ) {
- /* We got a non-error response */
- return 1;
- }
- if ( *reply ) {
- /* We got an error response */
- return 0;
- }
- }
- DBG ( "TFTP: timed out during read\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::lport Client UDP port
- * @v errcode TFTP error code
- * @v errmsg Descriptive error string, or NULL
- * @ret True Error packet was sent
- * @ret False Error packet was not sent
- *
- * Send a TFTP ERROR packet back to the server to terminate the
- * transfer.
- *
- * If #errmsg is NULL, the current error message string as returned by
- * strerror(errno) will be used as the error text.
- */
- int tftp_error ( struct tftp_state *state, int errcode, const char *errmsg ) {
- struct tftp_error error;
-
- DBG ( "TFTPCORE: aborting with error %d (%s)\n", errcode, errmsg );
- error.opcode = htons ( TFTP_ERROR );
- error.errcode = htons ( errcode );
- strncpy ( error.errmsg, errmsg ? errmsg : strerror ( errno ),
- sizeof ( error.errmsg ) );
- return udp_transmit ( state->server.sin_addr.s_addr,
- state->lport, 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;
- }
|