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:
parent
91c20e313d
commit
220b664617
7 changed files with 455 additions and 0 deletions
50
include/net/tftp.h
Normal file
50
include/net/tftp.h
Normal 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_ */
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
7
subsys/net/lib/tftp/CMakeLists.txt
Normal file
7
subsys/net/lib/tftp/CMakeLists.txt
Normal file
|
@ -0,0 +1,7 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
zephyr_library()
|
||||
|
||||
zephyr_library_sources(
|
||||
tftp_client.c
|
||||
)
|
40
subsys/net/lib/tftp/Kconfig
Normal file
40
subsys/net/lib/tftp/Kconfig
Normal 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
|
296
subsys/net/lib/tftp/tftp_client.c
Normal file
296
subsys/net/lib/tftp/tftp_client.c
Normal 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;
|
||||
}
|
59
subsys/net/lib/tftp/tftp_client.h
Normal file
59
subsys/net/lib/tftp/tftp_client.h
Normal 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);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue