#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; }