/* SPDX-License-Identifier: BSD-2-Clause */ /** * @file * * @ingroup RTEMSImplTFTPFS * * @brief This source file contains the implementation of * a Trivial File Transfer Protocol (TFTP) client library. * * The code in this file provides the ability to read files from and * to write files to remote servers using the Trivial File Transfer * Protocol (TFTP). It is used by the @ref tftpfs.c "TFTP file system" and * tested through its test suite. The * following RFCs are implemented: * * + RFC 1350 "The TFTP Protocol (Revision 2)" * + RFC 2347 "TFTP Option Extension" * + RFC 2348 "TFTP Blocksize Option" * + RFC 7440 "TFTP Windowsize Option" */ /* * Copyright (C) 1998 W. Eric Norum * Copyright (C) 2012, 2022 embedded brains GmbH (http://www.embedded-brains.de) * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tftp_driver.h" /* * Range of UDP ports to try */ #define UDP_PORT_BASE 3180 /* * Default limits */ #define PACKET_FIRST_TIMEOUT_MILLISECONDS 400L #define TFTP_WINDOW_SIZE_MIN 1 #define TFTP_BLOCK_SIZE_MIN 8 #define TFTP_BLOCK_SIZE_MAX 65464 #define TFTP_BLOCK_SIZE_OPTION "blksize" #define TFTP_WINDOW_SIZE_OPTION "windowsize" #define TFTP_DECIMAL_BASE 10 #define TFTP_DEFAULT_SERVER_PORT 69 /* * These values are suggested by RFC 7440. */ #define TFTP_RFC7440_DATA_RETRANSMISSIONS 6 #define TFTP_RFC7440_TIMEOUT_MILLISECONDS 1000 /* * TFTP opcodes */ #define TFTP_OPCODE_RRQ 1 #define TFTP_OPCODE_WRQ 2 #define TFTP_OPCODE_DATA 3 #define TFTP_OPCODE_ACK 4 #define TFTP_OPCODE_ERROR 5 #define TFTP_OPCODE_OACK 6 /* * TFTP error codes */ #define TFTP_ERROR_CODE_NOT_DEFINED 0 #define TFTP_ERROR_CODE_NOT_FOUND 1 #define TFTP_ERROR_CODE_NO_ACCESS 2 #define TFTP_ERROR_CODE_DISK_FULL 3 #define TFTP_ERROR_CODE_ILLEGAL 4 #define TFTP_ERROR_CODE_UNKNOWN_ID 5 #define TFTP_ERROR_CODE_FILE_EXISTS 6 #define TFTP_ERROR_CODE_NO_USER 7 #define TFTP_ERROR_CODE_OPTION_NEGO 8 /* * Special return values for process_*_packet() functions * (other return values are POSIX errors) */ #define GOT_EXPECTED_PACKET 0 #define GOT_DUPLICATE_OF_CURRENT_PACKET -1 #define GOT_OLD_PACKET -2 #define GOT_FIRST_OUT_OF_ORDER_PACKET -3 /* * Special argument value for getPacket() */ #define GET_PACKET_DONT_WAIT -1 /* * Special return value for prepare_*_packet_for_sending() functions * (other return values are the length of the packet to be send) */ #define DO_NOT_SEND_PACKET 0 #define PKT_SIZE_FROM_BLK_SIZE(_blksize) ((_blksize) + 2 * sizeof (uint16_t)) #define BLK_SIZE_FROM_PKT_SIZE(_pktsize) ((_pktsize) - 2 * sizeof (uint16_t)) #define MUST_SEND_OPTIONS(_options) (\ (_options).block_size != TFTP_RFC1350_BLOCK_SIZE || \ (_options).window_size != TFTP_RFC1350_WINDOW_SIZE ) /* * Packets transferred between machines */ union tftpPacket { /* * RRQ/WRQ packet */ struct tftpRWRQ { uint16_t opcode; char filename_mode[]; } tftpRWRQ; /* * DATA packet */ struct tftpDATA { uint16_t opcode; uint16_t blocknum; uint8_t data[]; } tftpDATA; /* * ACK packet */ struct tftpACK { uint16_t opcode; uint16_t blocknum; } tftpACK; /* * OACK packet */ struct tftpOACK { uint16_t opcode; char options[]; } tftpOACK; /* * ERROR packet */ struct tftpERROR { uint16_t opcode; uint16_t errorCode; char errorMessage[]; } tftpERROR; }; /* * State of each TFTP stream */ struct tftpStream { /* * Buffer for storing packets for sending and receiving. Can point * to the same address when only one buffer is needed for reading. */ union tftpPacket *receive_buf; union tftpPacket *send_buf; /* * Current block number - i.e. the block currently send or received */ uint16_t blocknum; /* * Size of the data area in a DATA single packet. */ size_t block_size; /* * The maximum size of a packet. It depends linearly on the block_size. * The receive_buf and (the packets in) the send_buf are of this size. */ size_t packet_size; /* * The number of packets which can be stored in the send buffer. * During option negotiation and for reading a file from the server * only a buffer for a single packet is needed. In those cases, this * value is always one. When a file is written to the server, * the value equals the window size: * send_buf_size_in_pkts == server_options.window_size * * Packet N is stored in * send_buf + packet_size * (N % send_buf_size_in_pkts) */ uint16_t send_buf_size_in_pkts; /* * When writing files with windowsize > 1, the number of the completely * filled packet with the highest block number in the send buffer. * When the user calls write(), the data will be written into * the block after this one. */ uint16_t blocknum_last_filled; /* * When writing files with windowsize > 1, the number of the packet * which is the last one in the whole file (i.e. the user * called close()). This block is never full (but maybe empty). */ uint16_t blocknum_eof_block; /* * Data transfer socket */ int socket; struct sockaddr_in myAddress; struct sockaddr_in farAddress; /* * Indices into buffer * In case of sending a file with windowsize > 1, these values apply * only to the packet with the highest number in the send buffer * (blocknum_last_filled + 1). */ int nleft; int nused; /* * Flags */ int firstReply; bool at_eof; bool is_for_reading; /* * Function pointers and members for use by communicate_with_server(). */ ssize_t (*prepare_packet_for_sending) ( struct tftpStream *tp, bool force_retransmission, union tftpPacket **send_buf, bool *wait_for_packet_reception, const void *create_packet_data ); int (*process_data_packet) (struct tftpStream *tp, ssize_t len); int (*process_ack_packet) (struct tftpStream *tp, ssize_t len); int (*process_oack_packet) (struct tftpStream *tp, ssize_t len); int (*process_error_packet) (struct tftpStream *tp, ssize_t len); int retransmission_error_code; bool ignore_out_of_order_packets; int32_t blocknum_of_first_packet_of_window; int error; /* * Configuration and TFTP options * * * config.options are options desired by the user (i.e. the values * send to the server). * * server_options are the options agreed by the server * (the option values actually used for the transfer of data). */ tftp_net_config config; tftp_options server_options; }; /* * Forward declaration cannot be avoided. */ static ssize_t prepare_data_packet_for_sending ( struct tftpStream *tp, bool force_retransmission, union tftpPacket **send_buf, bool *wait_for_packet_reception, const void *path_name ); static ssize_t prepare_ack_packet_for_sending ( struct tftpStream *tp, bool force_retransmission, union tftpPacket **send_buf, bool *wait_for_packet_reception, const void *path_name ); /* * Calculate the address of packet N in the send buffer */ static union tftpPacket *get_send_buffer_packet ( struct tftpStream *tp, uint16_t packet_num ) { return (union tftpPacket *) ( ( (char *) tp->send_buf) + tp->packet_size * (packet_num % tp->send_buf_size_in_pkts) ); } /* * Create read or write request */ static size_t create_request ( union tftpPacket *send_buf, size_t data_size, bool is_for_reading, const char *path, const tftp_options *options ) { size_t res_size; char *cur = send_buf->tftpRWRQ.filename_mode; send_buf->tftpRWRQ.opcode = htons ( is_for_reading ? TFTP_OPCODE_RRQ : TFTP_OPCODE_WRQ ); res_size = snprintf (cur, data_size, "%s%c%s", path, 0, "octet"); if (res_size >= data_size) { return -1; } res_size++; data_size -= res_size; cur += res_size; if (options->block_size != TFTP_RFC1350_BLOCK_SIZE) { res_size = snprintf ( cur, data_size, "%s%c%"PRIu16, TFTP_BLOCK_SIZE_OPTION, 0, options->block_size ); if (res_size >= data_size) { return -1; } res_size++; data_size -= res_size; cur += res_size; } if (options->window_size != TFTP_RFC1350_WINDOW_SIZE) { res_size = snprintf ( cur, data_size, "%s%c%"PRIu16, TFTP_WINDOW_SIZE_OPTION, 0, options->window_size ); if (res_size >= data_size) { return -1; } res_size++; data_size -= res_size; cur += res_size; } return cur - (char *)send_buf; } static bool parse_decimal_number ( char **pos, size_t *remain, long min, long max, uint16_t *variable ) { long value; const char *start = *pos; if (*remain < 2) { return false; } value = strtoul(start, pos, TFTP_DECIMAL_BASE); if (value < min || value > max || **pos != 0) { return false; } *variable = (uint16_t) value; (*pos)++; *remain -= *pos - start; return true; } /* * Map error message */ static int tftpErrno (uint16_t error_code) { unsigned int tftpError; static const int errorMap[] = { EINVAL, ENOENT, EPERM, ENOSPC, EINVAL, ENXIO, EEXIST, ESRCH, ENOTSUP, /* Error: Option negotiation failed (RFC 2347) */ }; tftpError = ntohs (error_code); if (tftpError < (sizeof errorMap / sizeof errorMap[0])) return errorMap[tftpError]; else return 1000 + tftpError; } /* * Parse options from an OACK packet */ static bool parse_options ( union tftpPacket *receive_buf, size_t packet_size, tftp_options *options_in, tftp_options *options_out ) { char *pos = receive_buf->tftpOACK.options; size_t remain = packet_size - sizeof (receive_buf->tftpOACK.opcode); /* * Make sure there is a 0 byte in the end before comparing strings */ if (remain > 0 && pos[remain - 1] != 0) { return false; } while (remain > 0) { if (strcasecmp(pos, TFTP_BLOCK_SIZE_OPTION) == 0 && options_in->block_size != TFTP_RFC1350_BLOCK_SIZE) { remain -= sizeof (TFTP_BLOCK_SIZE_OPTION); pos += sizeof (TFTP_BLOCK_SIZE_OPTION); if (!parse_decimal_number ( &pos, &remain, TFTP_BLOCK_SIZE_MIN, options_in->block_size, &options_out->block_size)) { return false; }; } else if (strcasecmp(pos, TFTP_WINDOW_SIZE_OPTION) == 0 && options_in->window_size != TFTP_RFC1350_WINDOW_SIZE) { remain -= sizeof (TFTP_WINDOW_SIZE_OPTION); pos += sizeof (TFTP_WINDOW_SIZE_OPTION); if (!parse_decimal_number ( &pos, &remain, TFTP_WINDOW_SIZE_MIN, options_in->window_size, &options_out->window_size)) { return false; }; } else { return false; /* Unknown option */ } } return true; } /* * Send an error message */ static void send_error ( struct tftpStream *tp, struct sockaddr_in *to, uint8_t error_code, const char *error_message ) { int len; struct { uint16_t opcode; uint16_t errorCode; char errorMessage[80]; } msg; /* * Create the error packet (Unknown transfer ID). */ msg.opcode = htons (TFTP_OPCODE_ERROR); msg.errorCode = htons (error_code); len = snprintf (msg.errorMessage, sizeof (msg.errorMessage), error_message); if (len >= sizeof (msg.errorMessage)) { len = sizeof (msg.errorMessage) - 1; } len += sizeof (msg.opcode) + sizeof (msg.errorCode) + 1; /* * Send it */ sendto (tp->socket, (char *)&msg, len, 0, (struct sockaddr *)to, sizeof *to); } /* * Send a message to make the other end shut up */ static void sendStifle (struct tftpStream *tp, struct sockaddr_in *to) { send_error (tp, to, TFTP_ERROR_CODE_UNKNOWN_ID, "GO AWAY"); } /* * Wait for a packet */ static ssize_t getPacket (struct tftpStream *tp, int retryCount) { ssize_t len; struct timeval tv; int flags = 0; if (retryCount == GET_PACKET_DONT_WAIT) { flags = MSG_DONTWAIT; } else if (retryCount == 0) { tv.tv_sec = tp->config.first_timeout / 1000L; tv.tv_usec = (tp->config.first_timeout % 1000L) * 1000L; setsockopt (tp->socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv); } else { tv.tv_sec = tp->config.timeout / 1000L; tv.tv_usec = (tp->config.timeout % 1000L) * 1000L; setsockopt (tp->socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv); } for (;;) { union { struct sockaddr s; struct sockaddr_in i; } from; socklen_t fromlen = sizeof from; len = recvfrom (tp->socket, tp->receive_buf, tp->packet_size, flags, &from.s, &fromlen ); if (len < 0) break; if (from.i.sin_addr.s_addr == tp->farAddress.sin_addr.s_addr) { if (tp->firstReply) { tp->firstReply = 0; tp->farAddress.sin_port = from.i.sin_port; } if (tp->farAddress.sin_port == from.i.sin_port) break; } /* * Packet is from someone with whom we are * not interested. Tell them to go away. */ sendStifle (tp, &from.i); } if (retryCount != GET_PACKET_DONT_WAIT) { tv.tv_sec = 0; tv.tv_usec = 0; setsockopt (tp->socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv); } return len; } static int process_unexpected_packet (struct tftpStream *tp, ssize_t len) { (void) len; send_error ( tp, &tp->farAddress, TFTP_ERROR_CODE_ILLEGAL, "Got packet with unexpected opcode from server" ); return EPROTO; } static int process_malformed_packet (struct tftpStream *tp, ssize_t len) { (void) len; send_error ( tp, &tp->farAddress, TFTP_ERROR_CODE_ILLEGAL, "Got malformed packet from server" ); return EPROTO; } static int process_error_packet (struct tftpStream *tp, ssize_t len) { (void) len; return tftpErrno (tp->receive_buf->tftpERROR.errorCode); } /* * When an RRQ or a WRQ with options is sent and the server responds with * an error, this function will trigger a re-sent of an RRQ or WRQ * without options (falling back to old RFC1350). * * If someone wants to change the implementation to force using options * (i.e. prevent fallback to RFC1350), at least these points must be * considered: * * * Use `process_error_packet()` instead of * `process_error_packet_option_negotiation()` * * React to DATA and ACK packets, which are an immediate response to * an RRQ or a WRQ with options, with an error packet. * * Check the option values in the OACK whether they are in the * desired range. */ static int process_error_packet_option_negotiation ( struct tftpStream *tp, ssize_t len ) { (void) len; /* * Setting tp->config.options causes an RRQ or a WRQ to be created without * options. * Setting tp->server_option is defensive programming as these fields * should already have these values. */ tp->config.options.block_size = TFTP_RFC1350_BLOCK_SIZE; tp->config.options.window_size = TFTP_RFC1350_WINDOW_SIZE; tp->server_options.block_size = TFTP_RFC1350_BLOCK_SIZE; tp->server_options.window_size = TFTP_RFC1350_WINDOW_SIZE; tp->process_error_packet = process_error_packet; /* * GOT_FIRST_OUT_OF_ORDER_PACKET will trigger a re-send of the RRQ or WRQ. */ return GOT_FIRST_OUT_OF_ORDER_PACKET; } static int process_data_packet (struct tftpStream *tp, ssize_t len) { ssize_t plen; int32_t pkt_blocknum; union tftpPacket *send_buf; if (len < sizeof (tp->receive_buf->tftpACK)) { return process_malformed_packet (tp, len); } pkt_blocknum = (int32_t) ntohs (tp->receive_buf->tftpACK.blocknum); if (pkt_blocknum == 0) { return process_malformed_packet (tp, len); } /* * In case of reading a file from the server: * If the latest ACK packet(s) did not reach the server, the server * starts the window from the last ACK it received. This if-clause * ensures, the client sends an ACK after having seen `windowsize` * packets. */ if (pkt_blocknum < tp->blocknum_of_first_packet_of_window && pkt_blocknum >= (int32_t) tp->blocknum + 1 - (int32_t) tp->server_options.window_size) { tp->blocknum_of_first_packet_of_window = pkt_blocknum; } if (!tp->ignore_out_of_order_packets && pkt_blocknum > (int32_t) tp->blocknum + 1) { tp->ignore_out_of_order_packets = true; return GOT_FIRST_OUT_OF_ORDER_PACKET; } else if (pkt_blocknum == (int32_t) tp->blocknum) { /* * In case of reading a file from the server: * If the last ACK packet send by the client did not reach the * server, the server re-sends all packets of the window. In this * case, the client must re-send the ACK packet after having * received the last packet of the window (even through it has * already received that packet before). * GOT_OLD_PACKET would wrongly suppress this. */ return GOT_DUPLICATE_OF_CURRENT_PACKET; } else if (pkt_blocknum != (int32_t) tp->blocknum + 1) { return GOT_OLD_PACKET; } tp->ignore_out_of_order_packets = false; tp->blocknum++; tp->nused = 0; /* Only for 2nd, 3rd, 4th DATA packet received */ tp->nleft = BLK_SIZE_FROM_PKT_SIZE (len); tp->at_eof = (tp->nleft < tp->server_options.block_size); /* * After the last DATA packet, the client must send a final ACK */ if (tp->at_eof) { plen = prepare_ack_packet_for_sending (tp, true, &send_buf, NULL, NULL); /* * Send it. Errors during send will not matter for this last ACK. */ sendto ( tp->socket, send_buf, plen, 0, (struct sockaddr *) &tp->farAddress, sizeof (tp->farAddress) ); } tp->prepare_packet_for_sending = prepare_ack_packet_for_sending; return GOT_EXPECTED_PACKET; } static int process_ack_packet (struct tftpStream *tp, ssize_t len) { uint16_t blocknum_ack; if (len < sizeof (tp->receive_buf->tftpACK)) { return process_malformed_packet (tp, len); } blocknum_ack = ntohs (tp->receive_buf->tftpACK.blocknum); if ((int32_t) blocknum_ack == tp->blocknum_of_first_packet_of_window - 1 && blocknum_ack != 0 ) { tp->blocknum = tp->blocknum_of_first_packet_of_window; return GOT_DUPLICATE_OF_CURRENT_PACKET; } if ((int32_t) blocknum_ack < tp->blocknum_of_first_packet_of_window || blocknum_ack > tp->blocknum_last_filled) { return GOT_OLD_PACKET; } tp->blocknum = blocknum_ack + 1; tp->blocknum_of_first_packet_of_window = (int32_t) tp->blocknum; tp->prepare_packet_for_sending = prepare_data_packet_for_sending; return GOT_EXPECTED_PACKET; } static ssize_t prepare_data_packet_for_sending ( struct tftpStream *tp, bool force_retransmission, union tftpPacket **send_buf, bool *wait_for_packet_reception, const void *not_used ) { (void) not_used; ssize_t len; *send_buf = get_send_buffer_packet (tp, tp->blocknum); len = PKT_SIZE_FROM_BLK_SIZE ( (tp->blocknum == tp->blocknum_eof_block) ? tp->nused : tp->block_size ); (*send_buf)->tftpDATA.opcode = htons (TFTP_OPCODE_DATA); (*send_buf)->tftpDATA.blocknum = htons (tp->blocknum); /* * If the client sends the last packet of a window, * it must wait for an ACK and - in case no ACK is received - begin * a retransmission with the first packet of the window. * Note that the last DATA block for the whole transfer is also * a "last packet of a window". */ if ((int32_t) tp->blocknum + 1 >= tp->blocknum_of_first_packet_of_window + tp->send_buf_size_in_pkts || tp->blocknum == tp->blocknum_eof_block) { tp->blocknum = (uint16_t) tp->blocknum_of_first_packet_of_window; } else { tp->blocknum++; *wait_for_packet_reception = false; } tp->process_data_packet = process_unexpected_packet; tp->process_ack_packet = process_ack_packet; tp->process_oack_packet = process_unexpected_packet; tp->process_error_packet = process_error_packet; /* * Our last packet won't necessarily be acknowledged! */ if (tp->blocknum == tp->blocknum_eof_block) { tp->retransmission_error_code = 0; } return len; } static ssize_t prepare_ack_packet_for_sending ( struct tftpStream *tp, bool force_retransmission, union tftpPacket **send_buf, bool *wait_for_packet_reception, const void *path_name ) { (void) wait_for_packet_reception; if (!force_retransmission && tp->blocknum_of_first_packet_of_window - 1 + (int32_t) tp->server_options.window_size > (int32_t) tp->blocknum) { return DO_NOT_SEND_PACKET; } tp->blocknum_of_first_packet_of_window = (int32_t) tp->blocknum + 1; /* * Create the acknowledgement */ *send_buf = tp->send_buf; (*send_buf)->tftpACK.opcode = htons (TFTP_OPCODE_ACK); (*send_buf)->tftpACK.blocknum = htons (tp->blocknum); tp->process_data_packet = process_data_packet; tp->process_ack_packet = process_unexpected_packet; tp->process_oack_packet = process_unexpected_packet; tp->process_error_packet = process_error_packet; return sizeof (tp->send_buf->tftpACK); } static int process_oack_packet (struct tftpStream *tp, ssize_t len) { if (!parse_options(tp->receive_buf, len, &tp->config.options, &tp->server_options)) { send_error ( tp, &tp->farAddress, TFTP_ERROR_CODE_OPTION_NEGO, "Bad options, option values or malformed OACK packet" ); return EPROTO; } if (tp->is_for_reading) { /* * Since no DATA packet has been received yet, tell * tftp_read() there is no data left. */ tp->nleft = 0; tp->prepare_packet_for_sending = prepare_ack_packet_for_sending; } else { tp->blocknum_of_first_packet_of_window = 1; tp->blocknum = (uint16_t) tp->blocknum_of_first_packet_of_window; tp->prepare_packet_for_sending = prepare_data_packet_for_sending; } return GOT_EXPECTED_PACKET; } static ssize_t prepare_request_packet_for_sending ( struct tftpStream *tp, bool force_retransmission, union tftpPacket **send_buf, bool *wait_for_packet_reception, const void *path_name ) { (void) wait_for_packet_reception; ssize_t len; *send_buf = tp->send_buf; len = create_request ( *send_buf, tp->block_size, tp->is_for_reading, path_name, &tp->config.options ); if (len < 0) { tp->error = ENAMETOOLONG; } else { tp->process_data_packet = tp->is_for_reading ? process_data_packet : process_unexpected_packet; tp->process_ack_packet = tp->is_for_reading ? process_unexpected_packet : process_ack_packet; tp->process_oack_packet = MUST_SEND_OPTIONS(tp->config.options) ? process_oack_packet : process_unexpected_packet; tp->process_error_packet = MUST_SEND_OPTIONS(tp->config.options) ? process_error_packet_option_negotiation : process_error_packet; } /* * getPacket() will change these values when the first packet is * received. Yet, this first packet may be an unexpected one * (e.g. an ERROR or having a wrong block number). * If a second, third, forth, ... RRQ/WRQ is to be sent, it should * be directed to the server port again and not to the port the * first unexpected packet came from. */ tp->farAddress.sin_port = htons (tp->config.server_port); tp->firstReply = 1; return len; } /* * Conduct one communication step with the server. For windowsize == 1, * one step is: * a) Send a packet to the server * b) Receive a reply packet from the server * c) Handle errors (if any) * d) If no packet has been received from the server and the maximum * retransmission count has not yet been reached, start over with a) * The flow of packets (i.e. which packet to send and which packet(s) to * expect from the server) is controlled by function pointers found in * struct tftpStream. * * Besides of handling errors and retransmissions, the essential data exchange * follows these patterns: * * Connection establishment and option negotiation: * * Send RRQ/WRQ (read or write request packet) * * Receive OACK (read and write) or ACK (write only) or DATA (read only) * * Read step with windowsize == 1: * * Send ACK packet * * Receive DATA packet * Sending the very last ACK packet for a "read" session is treated as a * special case. * * Write step with windowsize == 1: * * Send DATA packet * * Receive ACK packet * * A windowsize lager than one makes thinks more complicated. * In this case, a step normally only receives (read) or only sends (write) * a packet. The sending or receiving of the ACK packets is skipped normally * and happens only at the last step of the window (in which case this last * step is similar to the windowsize == 1 case): * * Normal read step with windowsize > 1: * * Receive DATA packet * Last read step of a window with windowsize > 1: * * Send ACK packet * * Receive DATA packet * * Normal write step with windowsize > 1: * * Send DATA packet * * Check for an ACK packet but do not wait for it * Last write step of a window with windowsize > 1: * * Send DATA packet * * Receive ACK packet * * The "normal write step for windowsize > 1" checks whether an ACK packet * has been received after each sending of a DATA packet. Package lost and * exchanges in the network can give rise to situations in which the server * sends more than a single ACK packet during a window. If these packets * are not reacted on immediately, the network would be flooded with * surplus packets. (An example where two ACK packets appear in a window * appears in test case "read_file_windowsize_trouble" where the client/server * roles are exchanged.) */ static int communicate_with_server ( struct tftpStream *tp, const void *create_packet_data ) { ssize_t len; uint16_t opcode; union tftpPacket *send_buf; bool received_duplicated_or_old_package = false; bool force_retransmission = false; bool wait_for_packet_reception; int retryCount = 0; while (tp->error == 0) { if (!received_duplicated_or_old_package) { wait_for_packet_reception = true; len = tp->prepare_packet_for_sending ( tp, force_retransmission, &send_buf, &wait_for_packet_reception, create_packet_data ); if (len < 0) { if (tp->error == 0) { tp->error = EIO; } break; } if (len != DO_NOT_SEND_PACKET) { /* * Send the packet */ if (sendto (tp->socket, send_buf, len, 0, (struct sockaddr *)&tp->farAddress, sizeof tp->farAddress) < 0) { tp->error = EIO; break; } } } received_duplicated_or_old_package = false; force_retransmission = false; /* * Get reply */ len = getPacket ( tp, wait_for_packet_reception ? retryCount : GET_PACKET_DONT_WAIT ); if (len >= (int) sizeof (tp->receive_buf->tftpDATA.opcode)) { opcode = ntohs (tp->receive_buf->tftpDATA.opcode); switch (opcode) { case TFTP_OPCODE_DATA: tp->error = tp->process_data_packet (tp, len); break; case TFTP_OPCODE_ACK: tp->error = tp->process_ack_packet (tp, len); break; case TFTP_OPCODE_OACK: tp->error = tp->process_oack_packet (tp, len); break; case TFTP_OPCODE_ERROR: tp->error = tp->process_error_packet (tp, len); break; default: tp->error = process_unexpected_packet (tp, len); break; } if (tp->error == GOT_EXPECTED_PACKET) { break; } else if (tp->error == GOT_DUPLICATE_OF_CURRENT_PACKET) { tp->error = 0; } else if (tp->error == GOT_OLD_PACKET) { received_duplicated_or_old_package = true; tp->error = 0; } else if (tp->error <= GOT_FIRST_OUT_OF_ORDER_PACKET) { force_retransmission = true; tp->error = 0; } /* else ... tp->error > 0 means "exit this function with error" */ } else if (len >= 0) { tp->error = process_malformed_packet (tp, len); } else if (len < 0 && !wait_for_packet_reception) { tp->error = 0; break; } else { /* * Timeout or other problems to receive packets * Attempt a retransmission */ if (++retryCount >= (int) tp->config.retransmissions) { tp->error = tp->retransmission_error_code; break; } force_retransmission = true; } } return tp->error; } /* * Allocate and initialize an struct tftpStream object. * * This function does not check whether the config values are in valid ranges. */ static struct tftpStream *create_stream( const tftp_net_config *config, const struct in_addr *farAddress, bool is_for_reading ) { struct tftpStream *tp = NULL; tp = malloc (sizeof (struct tftpStream)); if (tp == NULL) { return NULL; } /* * Initialize fields accessed by _Tftp_Destroy(). */ tp->receive_buf = NULL; tp->send_buf = NULL; tp->socket = 0; /* * Allocate send and receive buffer for exchange of RRQ/WRQ and ACK/OACK. */ tp->block_size = TFTP_RFC1350_BLOCK_SIZE; tp->packet_size = PKT_SIZE_FROM_BLK_SIZE (tp->block_size); tp->receive_buf = malloc (tp->packet_size); if (tp->receive_buf == NULL) { _Tftp_Destroy (tp); return NULL; } tp->send_buf = tp->receive_buf; tp->send_buf_size_in_pkts = 1; /* * Create the socket */ if ((tp->socket = socket (AF_INET, SOCK_DGRAM, 0)) < 0) { _Tftp_Destroy (tp); return NULL; } /* * Setup configuration and options */ if ( config == NULL ) { tftp_initialize_net_config (&tp->config); } else { tp->config = *config; } /* * If the server does not confirm our option values later on, * use numbers from the original RFC 1350 for the actual transfer. */ tp->server_options.block_size = TFTP_RFC1350_BLOCK_SIZE; tp->server_options.window_size = TFTP_RFC1350_WINDOW_SIZE; /* * Set the UDP destination to the TFTP server * port on the remote machine. */ tp->farAddress.sin_family = AF_INET; tp->farAddress.sin_addr = *farAddress; tp->farAddress.sin_port = htons (tp->config.server_port); tp->nleft = 0; tp->nused = 0; tp->blocknum = 0; tp->blocknum_last_filled = 0; tp->blocknum_eof_block = UINT16_MAX; tp->firstReply = 1; tp->at_eof = false; tp->is_for_reading = is_for_reading; tp->prepare_packet_for_sending = prepare_request_packet_for_sending; tp->process_data_packet = process_unexpected_packet; tp->process_ack_packet = process_unexpected_packet; tp->process_oack_packet = process_unexpected_packet; tp->process_error_packet = process_error_packet; tp->retransmission_error_code = EIO; tp->ignore_out_of_order_packets = false; tp->blocknum_of_first_packet_of_window = INT32_MIN; tp->error = 0; return tp; } /* * Change the size of the receive and send buffer to match the options * values acknowledged by the server. */ static struct tftpStream *reallocate_stream_buffer(struct tftpStream *tp) { tp->block_size = tp->server_options.block_size; tp->packet_size = PKT_SIZE_FROM_BLK_SIZE (tp->block_size); /* * Defensive programming */ if (tp->receive_buf == tp->send_buf) { tp->send_buf = NULL; } else { free (tp->send_buf); } tp->receive_buf = realloc (tp->receive_buf, tp->packet_size); if (tp->is_for_reading) { tp->send_buf = tp->receive_buf; } else { tp->send_buf_size_in_pkts = tp->server_options.window_size; tp->send_buf = malloc ( tp->send_buf_size_in_pkts * tp->packet_size ); } if (tp->receive_buf == NULL || tp->send_buf == NULL) { sendStifle (tp, &tp->farAddress); _Tftp_Destroy (tp); return NULL; } return tp; } /* * Convert hostname to an Internet address */ static struct in_addr *get_ip_address( const char *hostname, struct in_addr *farAddress ) { struct hostent *he = gethostbyname(hostname); if (he == NULL) { return NULL; } memcpy (farAddress, he->h_addr, sizeof (*farAddress)); return farAddress; } void tftp_initialize_net_config (tftp_net_config *config) { static const tftp_net_config default_config = { .retransmissions = TFTP_RFC7440_DATA_RETRANSMISSIONS, .server_port = TFTP_DEFAULT_SERVER_PORT, .timeout = TFTP_RFC7440_TIMEOUT_MILLISECONDS, .first_timeout = PACKET_FIRST_TIMEOUT_MILLISECONDS, .options = { .block_size = TFTP_DEFAULT_BLOCK_SIZE, .window_size = TFTP_DEFAULT_WINDOW_SIZE } }; if (config != NULL) { memcpy (config, &default_config, sizeof (default_config)); } } int tftp_open( const char *hostname, const char *path, bool is_for_reading, const tftp_net_config *config, void **tftp_handle ) { struct tftpStream *tp; struct in_addr farAddress; int err; /* * Check parameters */ if (tftp_handle == NULL) { return EINVAL; } *tftp_handle = NULL; if (hostname == NULL || path == NULL) { return EINVAL; } if (config != NULL && ( config->options.window_size < TFTP_WINDOW_SIZE_MIN || config->options.block_size < TFTP_BLOCK_SIZE_MIN || config->options.block_size > TFTP_BLOCK_SIZE_MAX ) ) { return EINVAL; } /* * Create tftpStream structure */ if (get_ip_address( hostname, &farAddress ) == NULL) { return ENOENT; } tp = create_stream( config, &farAddress, is_for_reading ); if (tp == NULL) { return ENOMEM; } /* * Send RRQ or WRQ and wait for reply */ tp->prepare_packet_for_sending = prepare_request_packet_for_sending; err = communicate_with_server (tp, path); if ( err != 0 ) { _Tftp_Destroy (tp); return err; } *tftp_handle = reallocate_stream_buffer ( tp ); if( *tftp_handle == NULL ) { return ENOMEM; } return 0; } /* * Read from a TFTP stream */ ssize_t tftp_read( void *tftp_handle, void *buffer, size_t count ) { char *bp; struct tftpStream *tp = tftp_handle; int nwant; int err; if (tp == NULL || !tp->is_for_reading || buffer == NULL) return -EIO; /* * Read till user request is satisfied or EOF is reached */ bp = buffer; nwant = count; while (nwant) { if (tp->nleft) { int ncopy; if (nwant < tp->nleft) ncopy = nwant; else ncopy = tp->nleft; memcpy (bp, &tp->receive_buf->tftpDATA.data[tp->nused], ncopy); tp->nused += ncopy; tp->nleft -= ncopy; bp += ncopy; nwant -= ncopy; if (nwant == 0) break; } if (tp->at_eof) { break; } /* * Wait for the next packet */ tp->retransmission_error_code = -EIO; err = communicate_with_server(tp, NULL); if (err == tp->retransmission_error_code) { return -EIO; } /* * If communicate_with_server() returns an error, either * * an error message from the server was received or * * an error message was already sent to the server * Setting tp->at_eof true, prevents all further calls to * communicate_with_server() and suppresses the sending of * an error message to the server by tftp_close(). */ if (err != 0) { tp->at_eof = true; return -err; } } return count - nwant; } /* * Flush a write buffer and wait for acknowledgement * * This function returns only if there is at least one packet buffer free * in the tp->send_buf. This ensures that tftp_write() can store * further data for sending in this free packet buffer. * * When the end of file has been reached (i.e. tftp_close() called this * function), this function returns only after all packets * in the write buffer have been send and acknowledged (or if an error * occurred). */ static int rtems_tftp_flush (struct tftpStream *tp) { int err; if (tp->at_eof) { return 0; } do { err = communicate_with_server(tp, NULL); /* * If communicate_with_server() returns an error, either * * an error message from the server was received or * * an error message was already sent to the server * Setting tp->at_eof true, prevents all further calls to * communicate_with_server() and suppresses the sending of * an error message to the server by tftp_close(). */ if (err != 0) { tp->at_eof = true; return err; } } while( (int32_t) tp->blocknum_last_filled + 1 >= tp->blocknum_of_first_packet_of_window + tp->send_buf_size_in_pkts || /* * tp->blocknum_eof_block == tp->blocknum_last_filled * holds only true when the user invoked tftp_close(). */ (tp->blocknum_eof_block == tp->blocknum_last_filled && tp->blocknum_of_first_packet_of_window <= (int32_t) tp->blocknum_eof_block) ); return 0; } /* * Close a TFTP stream */ int tftp_close( void *tftp_handle ) { struct tftpStream *tp = tftp_handle; int e = 0; if (tp == NULL) { return 0; } if (!tp->is_for_reading) { tp->blocknum_last_filled++; tp->blocknum_eof_block = tp->blocknum_last_filled; e = rtems_tftp_flush (tp); tp->at_eof = true; } if (!tp->at_eof && !tp->firstReply) { /* * Tell the other end to stop */ rtems_interval ticksPerSecond; send_error ( tp, &tp->farAddress, TFTP_ERROR_CODE_NO_USER, "User (client) stopped reading or " "server stopped sending packets (timeout)" ); ticksPerSecond = rtems_clock_get_ticks_per_second(); rtems_task_wake_after (1 + ticksPerSecond / 10); } _Tftp_Destroy (tp); return e; } ssize_t tftp_write( void *tftp_handle, const void *buffer, size_t count ) { const char *bp; struct tftpStream *tp = tftp_handle; int nleft, nfree, ncopy; int err; union tftpPacket *send_buf; /* * Bail out if an error has occurred */ if (tp == NULL || tp->is_for_reading || tp->at_eof || buffer == NULL) { return -EIO; } /* * Write till user request is satisfied * Notice that the buffer is flushed as soon as it is filled rather * than waiting for the next write or a close. This ensures that * the flush in close writes a less than full buffer so the far * end can detect the end-of-file condition. */ bp = buffer; nleft = count; while (nleft) { nfree = tp->block_size - tp->nused; if (nleft < nfree) ncopy = nleft; else ncopy = nfree; send_buf = get_send_buffer_packet (tp, tp->blocknum_last_filled + 1); memcpy (&send_buf->tftpDATA.data[tp->nused], bp, ncopy); tp->nused += ncopy; nleft -= ncopy; bp += ncopy; if (tp->nused == tp->block_size) { tp->blocknum_last_filled++; err = rtems_tftp_flush (tp); if (err) { return -err; } tp->nused = 0; } } return count; } void _Tftp_Destroy( void *tftp_handle ) { struct tftpStream *tp = tftp_handle; if (tp == NULL) { return; } if (tp->socket >= 0) { close (tp->socket); } if (tp->receive_buf == tp->send_buf) { tp->send_buf = NULL; } free (tp->receive_buf); free (tp->send_buf); free (tp); }