net: tftp: Adding support for TFTP Client.

Adding RFC1350 compliant support for TFTP Client in Zephyr. The
current implementation is minimal and only supports the ability
to get a file from the server.

Things for the future include support for putting files to
server and adding support for RFC2347.

Signed-off-by: Bilal Wasim <bilalwasim676@gmail.com>
This commit is contained in:
Bilal Wasim 2020-04-22 04:40:18 +05:00 committed by Jukka Rissanen
commit 220b664617
7 changed files with 455 additions and 0 deletions

50
include/net/tftp.h Normal file
View file

@ -0,0 +1,50 @@
/*
* Copyright (c) 2020 InnBlue
*
* SPDX-License-Identifier: Apache-2.0
*/
/** @file tftp.h
*
* @brief Zephyr TFTP Implementation
*/
#ifndef ZEPHYR_INCLUDE_NET_TFTP_H_
#define ZEPHYR_INCLUDE_NET_TFTP_H_
#include <zephyr.h>
#include <net/socket.h>
#ifdef __cplusplus
extern "C" {
#endif
struct tftpc {
u8_t *user_buf;
u32_t user_buf_size;
};
/* TFTP Client Error codes. */
#define TFTPC_SUCCESS 0
#define TFTPC_DUPLICATE_DATA -1
#define TFTPC_BUFFER_OVERFLOW -2
#define TFTPC_UNKNOWN_FAILURE -3
#define TFTPC_REMOTE_ERROR -4
#define TFTPC_RETRIES_EXHAUSTED -5
/* @brief This function gets "file" from the remote server.
*
* @param server Control Block that represents the remote server.
* @param client Client Buffer Information.
* @param remote_file Name of the remote file to get.
* @param mode TFTP Client "mode" setting
*
* @return TFTPC_SUCCESS if the operation completed successfully.
* TFTPC_BUFFER_OVERFLOW if the file is larger than the user buffer.
* TFTPC_REMOTE_ERROR if the server failed to process our request.
* TFTPC_RETRIES_EXHAUSTED if the client timed out waiting for server.
*/
int tftp_get(struct sockaddr *server, struct tftpc *client,
const char *remote_file, const char *mode);
#endif /* ZEPHYR_INCLUDE_NET_TFTP_H_ */

View file

@ -6,6 +6,7 @@ add_subdirectory_if_kconfig(lwm2m)
add_subdirectory_if_kconfig(socks)
add_subdirectory_if_kconfig(sntp)
add_subdirectory_ifdef(CONFIG_MQTT_LIB mqtt)
add_subdirectory_ifdef(CONFIG_TFTP_LIB tftp)
add_subdirectory_ifdef(CONFIG_NET_CONFIG_SETTINGS config)
add_subdirectory_ifdef(CONFIG_NET_SOCKETS sockets)
add_subdirectory_ifdef(CONFIG_TLS_CREDENTIALS tls_credentials)

View file

@ -9,6 +9,8 @@ source "subsys/net/lib/dns/Kconfig"
source "subsys/net/lib/mqtt/Kconfig"
source "subsys/net/lib/tftp/Kconfig"
source "subsys/net/lib/http/Kconfig"
source "subsys/net/lib/websocket/Kconfig"

View file

@ -0,0 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
zephyr_library()
zephyr_library_sources(
tftp_client.c
)

View file

@ -0,0 +1,40 @@
# Socket TFTP Library for Zephyr
# Copyright (c) 2020 InnBlue
# SPDX-License-Identifier: Apache-2.0
config TFTP_LIB
bool "Socket TFTP Library Support [EXPERIMENTAL]"
select NET_SOCKETS
select NET_SOCKETS_POSIX_NAMES
help
Enable the Zephyr TFTP Library
if TFTP_LIB
module=TFTP
module-dep=NET_LOG
module-str=Log level for TFTP
module-help=Enables tftp debug messages.
source "subsys/net/Kconfig.template.log_config.net"
config TFTPC_REQUEST_TIMEOUT
int "Maximum amount of time the TFTP Client will wait for a response from the server."
default 5000
help
Maximum amount of time (in msec) that the TFTP Client will wait for
data from the TFTP Server. Once this time has elasped, the TFTP Client
will assume that the Server failed and close the connection.
config TFTPC_REQUEST_RETRANSMITS
int "Number of times the TFTP Client will retransmit the request to the server."
default 5
help
Once the TFTP Client sends out a request, it will wait
TFTPC_REQUEST_TIMEOUT msecs for the data to arrive from the TFTP Server.
However, if it doesn't arrive within the given time we will re-transmit
the request to the server in hopes that the server will respond within
time to this request. This number dictates the number of times we will
do re-tx of our request before giving up and exiting.
endif # TFTP_LIB

View file

@ -0,0 +1,296 @@
/*
* Copyright (c) 2020 InnBlue
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <logging/log.h>
LOG_MODULE_REGISTER(tftp_client, LOG_LEVEL_DBG);
/* Public / Internal TFTP header file. */
#include <net/tftp.h>
#include "tftp_client.h"
/* TFTP Global Buffer. */
static u8_t tftpc_buffer[TFTPC_MAX_BUF_SIZE];
static u32_t tftpc_buffer_size;
/* TFTP Request block number property. */
static u32_t tftpc_block_no;
static u32_t tftpc_index;
/* Global mutex to protect critical resources. */
K_MUTEX_DEFINE(tftpc_lock);
/* Name: make_request
* Description: This function takes in a given list of parameters and
* returns a read request packet. This packet can be sent
* out directly to the TFTP server.
*/
static u32_t make_request(const char *remote_file, const char *mode,
u8_t request_type)
{
u32_t req_size;
const char def_mode[] = "octet";
/* Populate the read request with the provided params. Note that this
* is created per RFC1350.
*/
/* Fill in the Request Type. Also keep tabs on the request size. */
sys_put_be16(request_type, tftpc_buffer);
req_size = 2;
/* Copy in the name of the remote file - the file we need to get
* from the server. Add an upper bound to ensure no buffers overflow.
*/
strncpy(tftpc_buffer + req_size, remote_file, TFTP_MAX_FILENAME_SIZE);
req_size += strlen(remote_file);
/* Fill in 0. */
tftpc_buffer[req_size] = 0x0;
req_size++;
/* Default to "Octet" if mode not specified. */
if (mode == NULL) {
mode = def_mode;
}
/* Copy the mode of operation. */
strncpy(tftpc_buffer + req_size, mode, TFTP_MAX_MODE_SIZE);
req_size += strlen(mode);
/* Fill in 0. */
tftpc_buffer[req_size] = 0x0;
req_size++;
return req_size;
}
/* Name: send_err
* Description: This function sends an Error report to the TFTP Server.
*/
static inline int send_err(int sock, int err_code, char *err_string)
{
u32_t req_size;
LOG_ERR("Client Error. Sending code: %d(%s)", err_code, err_string);
/* Fill in the "Err" Opcode and the actual error code. */
sys_put_be16(ERROR_OPCODE, tftpc_buffer);
sys_put_be16(err_code, tftpc_buffer + 2);
req_size = 4;
/* Copy the Error String. */
strcpy(tftpc_buffer + req_size, err_string);
req_size += strlen(err_string);
/* Send Error to server. */
return send(sock, tftpc_buffer, req_size, 0);
}
/* Name: tftpc_recv_data
* Description: This function tries to get data from the TFTP Server
* (either response or data). Times out eventually.
*/
static int tftpc_recv_data(int sock)
{
int stat;
struct pollfd fds;
/* Setup FDS. */
fds.fd = sock;
fds.events = ZSOCK_POLLIN;
/* Enable poll on this socket. Wait for the specified number
* of milliseconds before exiting the call.
*/
stat = poll(&fds, 1, CONFIG_TFTPC_REQUEST_TIMEOUT);
if (stat > 0) {
/* Receive data from the TFTP Server. */
stat = recv(sock, tftpc_buffer, TFTPC_MAX_BUF_SIZE, 0);
/* Store the amount of data received in the global variable. */
tftpc_buffer_size = stat;
}
return stat;
}
/* Name: tftpc_process_resp
* Description: This function will process the data received from the
* TFTP Server (a file or part of the file) and place it in the user buffer.
* Return Value: This function will return one of the following values,
* -> TFTPC_DUPLICATE_DATA: If the block is already received by the client.
* -> TFTPC_BUFFER_OVERFLOW: If the data is more than the user provided buffer.
* -> TFTPC_DATA_RX_SUCCESS: data received but their is more to come.
* -> TFTPC_SUCCESS: Block received successfully and no more data is coming.
*/
static int tftpc_process_resp(int sock, struct tftpc *client)
{
u16_t block_no;
/* Get the block number as received in the packet. */
block_no = sys_get_be16(tftpc_buffer + 2);
if (tftpc_block_no > block_no) {
LOG_DBG("Duplicate block received: %d", block_no);
LOG_DBG("Client waiting for Block Number: %d", tftpc_block_no);
send_ack(sock, block_no);
/* Duplicate block received. */
return TFTPC_DUPLICATE_DATA;
}
/* Valid block number received. */
LOG_DBG("Block Number: %d received", tftpc_block_no);
/* Only copy block if the user buffer has enough space. */
if (RECV_DATA_SIZE() > (client->user_buf_size - tftpc_index)) {
send_err(sock, 0x3, "Buffer Overflow");
return TFTPC_BUFFER_OVERFLOW;
}
/* Perform the actual copy and update the index. */
memcpy(client->user_buf + tftpc_index,
tftpc_buffer + TFTP_HEADER_SIZE, RECV_DATA_SIZE());
tftpc_index += RECV_DATA_SIZE();
/* "block" of data received. */
send_ack(sock, block_no);
tftpc_block_no++;
/* For RFC1350, the block size will always be 512.
* -> If block_size == 512, the transfer is still in progress.
* -> If block_size < 512, we will conclude the transfer.
*/
return (RECV_DATA_SIZE() == TFTP_BLOCK_SIZE) ? TFTP_BLOCK_SIZE :
TFTPC_SUCCESS;
}
/* Name: tftp_send_request
* Description: This function sends out a request to the TFTP Server
* (Read / Write) and waits for a response. Once we get some response
* from the server, it is interpreted and ensured to be correct.
* If not, we keep on poking the server for data until we eventually
* give up.
* Return Value: This function will return the "opcode" received from
* the remote server, i.e.
* -> ERROR_OPCODE: If the remote server responded with "Error" or if
* the client was unable to send / receive anything from the server.
* -> DATA_OPCODE: If the remote server responded with "Data".
* -> ACK_OPCODE: If the remote server responded with "Ack".
*/
static int tftp_send_request(int sock, u8_t request,
const char *remote_file, const char *mode)
{
u8_t retx_cnt = 0;
u32_t req_size;
/* Create TFTP Request. */
req_size = make_request(remote_file, mode, request);
send_req:
/* Send this request to the server. */
if (send(sock, tftpc_buffer, req_size, 0)) {
if (tftpc_recv_data(sock)) {
return sys_get_be16(tftpc_buffer);
}
}
/* No of times we had to re-tx this "request". */
retx_cnt++;
/* Log this out. */
LOG_DBG("Unable to get data from the TFTP Server.");
LOG_DBG("no_of_retransmists = %d", retx_cnt);
/* Are we retransmitting? */
if (retx_cnt < TFTP_REQ_RETX) {
LOG_DBG("Are we re-transmitting: YES");
goto send_req;
}
LOG_DBG("Are we re-transmitting: NO");
return ERROR_OPCODE;
}
/* Name: tftp_get
* Description: This function gets "file" from the remote server.
*/
int tftp_get(struct sockaddr *server, struct tftpc *client,
const char *remote_file, const char *mode)
{
s32_t stat = TFTPC_UNKNOWN_FAILURE;
u8_t retx_cnt = 0;
s32_t sock;
/* Re-init the global "block number" variable. */
tftpc_block_no = 1;
tftpc_index = 0;
/* Create Socket for TFTP (IPv4 / UDP) */
sock = socket(server->sa_family, SOCK_DGRAM, IPPROTO_UDP);
if (sock < 0) {
LOG_ERR("Failed to create UDP socket (IPv4): %d", errno);
return -errno;
}
/* Connect with the address. */
stat = connect(sock, server, sizeof(struct sockaddr_in6));
if (stat < 0) {
LOG_ERR("Cannot connect to UDP remote (IPv4): %d", errno);
return -errno;
}
/* Obtain Global Lock before accessing critical resources. */
k_mutex_lock(&tftpc_lock, K_FOREVER);
/* Send out the request to the TFTP Server. */
stat = tftp_send_request(sock, READ_REQUEST, remote_file, mode);
if (stat == ERROR_OPCODE) {
LOG_ERR("Server responded with error.");
return TFTPC_REMOTE_ERROR;
}
process_resp:
/* Process server response. */
stat = tftpc_process_resp(sock, client);
if (stat == TFTPC_BUFFER_OVERFLOW ||
stat == TFTPC_SUCCESS) {
goto req_done;
}
tftpc_recv:
/* More data is available - Read (or we read a duplicate). */
stat = tftpc_recv_data(sock);
if (stat <= 0) {
/* No response from server. */
LOG_DBG("Server response timeout.");
/* Retries exhausted ? */
if (++retx_cnt >= TFTP_REQ_RETX) {
LOG_ERR("No more retransmits available. Exiting");
return TFTPC_RETRIES_EXHAUSTED;
}
/* Start Retransmission. */
LOG_DBG("Starting Re-transmission.");
send_ack(sock, tftpc_block_no);
goto tftpc_recv;
}
/* Received response from the server. */
LOG_DBG("Received data of size: %d", stat);
goto process_resp;
req_done:
/* Clean up. */
k_mutex_unlock(&tftpc_lock);
close(sock);
return stat;
}

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2020 InnBlue
*
* SPDX-License-Identifier: Apache-2.0
*/
/* Generic header files required by the TFTPC Module. */
#include <zephyr.h>
#include <errno.h>
#include <net/socket.h>
/* Defines for creating static arrays for TFTP communication. */
#define TFTP_HEADER_SIZE 4
#define TFTP_BLOCK_SIZE 512
#define TFTP_MAX_MODE_SIZE 8
#define TFTP_REQ_RETX CONFIG_TFTPC_REQUEST_RETRANSMITS
/* Maximum amount of data that can be received in a single go ! */
#define TFTPC_MAX_BUF_SIZE (TFTP_BLOCK_SIZE + TFTP_HEADER_SIZE)
/* Maximum filename size allowed by the TFTP Client. This is used as
* an upper bound in the "make_request" function to ensure that there
* are no buffer overflows. The complete "tftpc_buffer" has the size of
* "TFTPC_MAX_BUF_SIZE" which is used when creating a request. From this
* total size, we need 2 bytes for request info, 2 dummy NULL bytes and
* "TFTP_MAX_MODE_SIZE" for mode info. Everything else can be used for
* filename.
*/
#define TFTP_MAX_FILENAME_SIZE (TFTPC_MAX_BUF_SIZE - TFTP_MAX_MODE_SIZE - 4)
/* TFTP Opcodes. */
#define READ_REQUEST 0x1
#define WRITE_REQUEST 0x2
#define DATA_OPCODE 0x3
#define ACK_OPCODE 0x4
#define ERROR_OPCODE 0x5
#define RECV_DATA_SIZE() (tftpc_buffer_size - TFTP_HEADER_SIZE)
/* Name: send_ack
* Description: This function sends an Ack to the TFTP
* Server (in response to the data sent by the
* Server).
*/
static inline int send_ack(int sock, int block)
{
u8_t tmp[4];
LOG_INF("Client acking Block Number: %d", block);
/* Fill in the "Ack" Opcode and the block no. */
sys_put_be16(ACK_OPCODE, tmp);
sys_put_be16(block, tmp + 2);
/* Lets send this request buffer out. Size of request
* buffer is 4 bytes.
*/
return send(sock, tmp, 4, 0);
}