1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/sys/net/gnrc/application_layer/tftp/gnrc_tftp.c
Martine Lenders 60c26648fe
Merge pull request #11766 from nmeum/pr/gnrc_tftp_min_len
gnrc_tftp: Add minimum packet length check
2019-07-16 14:39:48 +02:00

1191 lines
37 KiB
C

/*
* Copyright (C) 2015 Nick van IJzendoorn <nijzendoorn@engineering-spirit.nl>
*
* This file is subject to the terms and conditions of the GNU Lesser General
* Public License v2.1. See the file LICENSE in the top level directory for
* more details.
*/
/**
* @ingroup net_gnrc_tftp
* @{
*
* @file
*
* @author Nick van IJzendoorn <nijzendoorn@engineering-spirit.nl>
*/
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include "net/gnrc/tftp.h"
#include "net/gnrc/netapi.h"
#include "net/gnrc/netif.h"
#include "net/gnrc/netreg.h"
#include "net/gnrc/udp.h"
#include "net/gnrc/ipv6.h"
#include "random.h"
#define ENABLE_DEBUG (0)
#include "debug.h"
#if ENABLE_DEBUG
/* For PRIu16 etc. */
#include <inttypes.h>
#endif
#if (GNRC_NETIF_NUMOF > 1)
/* TODO: change API to make link-local address communitcation with
* multiple network interfaces */
#warning "gnrc_tftp does not work reliably with link-local addresses and >1 network interfaces."
#endif
static kernel_pid_t _tftp_kernel_pid;
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define CT_HTONS(x) ((uint16_t)(( \
(((uint16_t)(x)) >> 8) & 0x00FF) | \
((((uint16_t)(x)) << 8) & 0xFF00)))
#else
#define CT_HTONS(x) ((uint16_t)x)
#endif
#define MIN(a, b) ((a) > (b) ? (b) : (a))
#define ARRAY_LEN(x) (sizeof(x) / sizeof(x[0]))
#define TFTP_TIMEOUT_MSG 0x4000
#define TFTP_STOP_SERVER_MSG 0x4001
#define TFTP_MIN_PACKET_LEN 4
#define TFTP_DEFAULT_DATA_SIZE (GNRC_TFTP_MAX_TRANSFER_UNIT \
+ sizeof(tftp_packet_data_t))
/**
* @brief TFTP mode help support
*/
#define MODE(mode) { # mode, sizeof(# mode) }
typedef struct {
char *name;
uint8_t len;
} tftp_opt_t;
/**
* @brief TFTP opcodes
* @{
*/
typedef uint16_t tftp_opcodes_t;
#define TO_RRQ CT_HTONS(1) /**< Read Request */
#define TO_WRQ CT_HTONS(2) /**< Write Request */
#define TO_DATA CT_HTONS(3) /**< Data */
#define TO_ACK CT_HTONS(4) /**< Acknowledgment */
#define TO_ERROR CT_HTONS(5) /**< Error */
#define TO_OACK CT_HTONS(6) /**< Option ACK */
/**
* @}
*/
/**
* @brief TFTP Error Codes
* @{
*/
typedef uint16_t tftp_err_codes_t;
#define TE_UN_DEF CT_HTONS(0) /**< Not defined, see error message */
#define TE_NO_FILE CT_HTONS(1) /**< File not found */
#define TE_ACCESS CT_HTONS(2) /**< Access violation */
#define TE_DFULL CT_HTONS(3) /**< Disk full or allocation exceeded */
#define TE_ILLOPT CT_HTONS(4) /**< Illegal TFTP operation */
#define TE_UNKOWN_ID CT_HTONS(5) /**< Unknown transfer ID */
#define TE_EXISTS CT_HTONS(6) /**< File already exists */
#define TE_UNKOWN_USR CT_HTONS(7) /**< No such user */
/**
* @}
*/
/* ordered as @see tftp_mode_t */
tftp_opt_t _tftp_modes[] = {
[TTM_ASCII] = MODE(netascii),
[TTM_OCTET] = MODE(octet),
[TTM_MAIL] = MODE(mail),
};
/**
* @brief TFTP Options
*/
typedef enum {
TOPT_BLKSIZE,
TOPT_TIMEOUT,
TOPT_TSIZE,
} tftp_options_t;
/* ordered as @see tftp_options_t */
tftp_opt_t _tftp_options[] = {
[TOPT_BLKSIZE] = MODE(blksize),
[TOPT_TIMEOUT] = MODE(timeout),
[TOPT_TSIZE] = MODE(tsize),
};
/**
* @brief The TFTP state
*/
typedef enum {
TS_APP_FAILED = -3,
TS_DUP = -2,
TS_FAILED = -1,
TS_BUSY = 0,
TS_FINISHED = 1
} tftp_state;
/**
* @brief The type of the context used
*/
typedef enum {
CT_SERVER,
CT_CLIENT
} tftp_context_type;
/**
* @brief The TFTP context for the current transfer
*/
typedef struct {
char file_name[GNRC_TFTP_MAX_FILENAME_LEN];
tftp_mode_t mode;
tftp_opcodes_t op;
ipv6_addr_t peer;
xtimer_t timer;
msg_t timer_msg;
uint32_t timeout;
uint16_t dst_port;
uint16_t src_port;
tftp_context_type ct;
tftp_start_cb_t start_cb;
tftp_data_cb_t data_cb;
tftp_stop_cb_t stop_cb;
gnrc_netreg_entry_t entry;
/* transfer parameters */
uint16_t block_nr;
uint16_t block_size;
size_t transfer_size;
uint32_t block_timeout;
uint32_t retries;
bool use_options;
bool enable_options;
bool write_finished;
} tftp_context_t;
/**
* @brief The default TFTP header
*/
typedef struct __attribute__((packed)) {
tftp_opcodes_t opc;
uint8_t data[];
} tftp_header_t;
/**
* @brief The TFTP data packet
*/
typedef struct __attribute__((packed)) {
tftp_opcodes_t opc;
network_uint16_t block_nr;
uint8_t data[];
} tftp_packet_data_t;
/**
* @brief The TFTP error packet
*/
typedef struct __attribute__((packed)) {
tftp_opcodes_t opc;
tftp_err_codes_t err_code;
char err_msg[];
} tftp_packet_error_t;
/* get the TFTP opcode */
static inline tftp_opcodes_t _tftp_parse_type(uint8_t *buf)
{
return ((tftp_header_t *)buf)->opc;
}
/* initialize the context to it's default state */
static int _tftp_init_ctxt(ipv6_addr_t *addr, const char *file_name,
tftp_opcodes_t op, tftp_mode_t mode, tftp_context_type type,
tftp_start_cb_t start, tftp_stop_cb_t stop,
tftp_data_cb_t data, bool enable_options, tftp_context_t *ctxt);
/* set the default TFTP options */
static void _tftp_set_default_options(tftp_context_t *ctxt);
/* set the TFTP options to use */
static int _tftp_set_opts(tftp_context_t *ctxt, size_t blksize, uint32_t timeout, size_t total_size);
/* this function registers the UDP port and won't return till the TFTP transfer is finished */
static int _tftp_do_client_transfer(tftp_context_t *ctxt);
/* the state process of the TFTP transfer */
static tftp_state _tftp_state_processes(tftp_context_t *ctxt, msg_t *m);
/* send an start request if we run in client mode */
static tftp_state _tftp_send_start(tftp_context_t *ctxt, gnrc_pktsnip_t *buf);
/* send data or and ack depending if we are reading or writing */
static tftp_state _tftp_send_dack(tftp_context_t *ctxt, gnrc_pktsnip_t *buf, tftp_opcodes_t op);
/* send and TFTP error to the client */
static tftp_state _tftp_send_error(tftp_context_t *ctxt, gnrc_pktsnip_t *buf, tftp_err_codes_t err, const char *err_msg);
/* this function sends the actual packet */
static tftp_state _tftp_send(gnrc_pktsnip_t *buf, tftp_context_t *ctxt, size_t len);
/* decode the default TFTP start packet */
static int _tftp_decode_start(tftp_context_t *ctxt, gnrc_pktsnip_t *inpkt, gnrc_pktsnip_t *outbuf);
/* decode the TFTP option extensions */
static int _tftp_decode_options(tftp_context_t *ctxt, gnrc_pktsnip_t *buf, uint32_t start);
/* decode the received ACK packet */
static bool _tftp_validate_ack(tftp_context_t *ctxt, uint8_t *buf);
/* processes the received data packet and calls the callback defined by the user */
static int _tftp_process_data(tftp_context_t *ctxt, gnrc_pktsnip_t *buf);
/* decode the received error packet and calls the callback defined by the user */
static int _tftp_decode_error(uint8_t *buf, tftp_err_codes_t *err, const char * *err_msg);
/* TFTP super loop server */
static int _tftp_server(tftp_context_t *ctxt);
/* get the maximum allowed transfer unit to avoid 6Lo fragmentation */
static uint16_t _tftp_get_maximum_block_size(void)
{
uint16_t tmp;
gnrc_netif_t *netif = gnrc_netif_iter(NULL);
if ((netif != NULL) && gnrc_netapi_get(netif->pid, NETOPT_MAX_PDU_SIZE,
0, &tmp, sizeof(uint16_t)) >= 0) {
/* TODO calculate proper block size */
return tmp - sizeof(udp_hdr_t) - sizeof(ipv6_hdr_t) - 10;
}
return GNRC_TFTP_MAX_TRANSFER_UNIT;
}
int gnrc_tftp_client_read(ipv6_addr_t *addr, const char *file_name, tftp_mode_t mode,
tftp_data_cb_t data_cb, tftp_start_cb_t start_cb, tftp_stop_cb_t stop_cb, bool use_option_extensions)
{
tftp_context_t ctxt;
assert(data_cb);
assert(start_cb);
assert(stop_cb);
/* prepare the context */
if (_tftp_init_ctxt(addr, file_name, TO_RRQ, mode, CT_CLIENT, start_cb, stop_cb, data_cb, use_option_extensions, &ctxt) != TS_FINISHED) {
return -EINVAL;
}
/* set the transfer options */
uint16_t mtu = _tftp_get_maximum_block_size();
if (!use_option_extensions ||
_tftp_set_opts(&ctxt, mtu, GNRC_TFTP_DEFAULT_TIMEOUT, 0) != TS_FINISHED) {
_tftp_set_default_options(&ctxt);
if (use_option_extensions) {
return -EINVAL;
}
}
/* start the process */
int ret = _tftp_do_client_transfer(&ctxt);
/* remove possibly stale timer */
xtimer_remove(&(ctxt.timer));
return ret;
}
int gnrc_tftp_client_write(ipv6_addr_t *addr, const char *file_name, tftp_mode_t mode,
tftp_data_cb_t data_cb, size_t total_size, tftp_stop_cb_t stop_cb, bool use_option_extensions)
{
tftp_context_t ctxt;
assert(data_cb);
assert(stop_cb);
/* prepare the context */
if (_tftp_init_ctxt(addr, file_name, TO_WRQ, mode, CT_CLIENT, NULL, stop_cb, data_cb, use_option_extensions, &ctxt) < 0) {
return -EINVAL;
}
/* set the transfer options */
uint16_t mtu = _tftp_get_maximum_block_size();
if (!use_option_extensions ||
_tftp_set_opts(&ctxt, mtu, GNRC_TFTP_DEFAULT_TIMEOUT, total_size) != TS_FINISHED) {
_tftp_set_default_options(&ctxt);
if (use_option_extensions) {
return -EINVAL;
}
}
/* start the process */
int ret = _tftp_do_client_transfer(&ctxt);
/* remove possibly stale timer */
xtimer_remove(&(ctxt.timer));
return ret;
}
int _tftp_init_ctxt(ipv6_addr_t *addr, const char *file_name,
tftp_opcodes_t op, tftp_mode_t mode, tftp_context_type type,
tftp_start_cb_t start, tftp_stop_cb_t stop,
tftp_data_cb_t data, bool enable_options, tftp_context_t *ctxt)
{
if (!addr) {
return TS_FAILED;
}
memset(ctxt, 0, sizeof(*ctxt));
/* set the default context parameters */
ctxt->op = op;
ctxt->ct = type;
ctxt->data_cb = data;
ctxt->start_cb = start;
ctxt->stop_cb = stop;
memcpy(&(ctxt->peer), addr, sizeof(ctxt->peer));
ctxt->mode = mode;
if (file_name) {
strncpy(ctxt->file_name, file_name, GNRC_TFTP_MAX_FILENAME_LEN);
}
ctxt->file_name[GNRC_TFTP_MAX_FILENAME_LEN - 1] = 0;
ctxt->dst_port = GNRC_TFTP_DEFAULT_DST_PORT;
ctxt->enable_options = enable_options;
/* transport layer parameters */
ctxt->block_size = GNRC_TFTP_MAX_TRANSFER_UNIT;
ctxt->block_timeout = GNRC_TFTP_DEFAULT_TIMEOUT;
ctxt->write_finished = false;
/* generate a random source UDP source port */
do {
ctxt->src_port = (random_uint32() & 0xff) + GNRC_TFTP_DEFAULT_SRC_PORT;
} while (gnrc_netreg_lookup(GNRC_NETTYPE_UDP, ctxt->src_port));
return TS_FINISHED;
}
void _tftp_set_default_options(tftp_context_t *ctxt)
{
ctxt->block_size = GNRC_TFTP_MAX_TRANSFER_UNIT;
ctxt->timeout = GNRC_TFTP_DEFAULT_TIMEOUT;
ctxt->block_timeout = GNRC_TFTP_DEFAULT_TIMEOUT;
ctxt->transfer_size = 0;
ctxt->use_options = false;
}
int _tftp_set_opts(tftp_context_t *ctxt, size_t blksize, uint32_t timeout, size_t total_size)
{
if (blksize > GNRC_TFTP_MAX_TRANSFER_UNIT || !timeout) {
return TS_FAILED;
}
ctxt->block_size = blksize;
ctxt->timeout = timeout;
ctxt->block_timeout = timeout;
ctxt->transfer_size = total_size;
ctxt->use_options = true;
return TS_FINISHED;
}
int gnrc_tftp_server(tftp_data_cb_t data_cb, tftp_start_cb_t start_cb, tftp_stop_cb_t stop_cb, bool use_options)
{
/* check if there is only one TFTP server running */
if (_tftp_kernel_pid != KERNEL_PID_UNDEF) {
DEBUG("tftp: only one TFTP server allowed\n");
return -1;
}
/* context will be initialized when a connection is established */
tftp_context_t ctxt = {
.dst_port = GNRC_TFTP_DEFAULT_DST_PORT,
.src_port = GNRC_TFTP_DEFAULT_DST_PORT,
.data_cb = data_cb,
.start_cb = start_cb,
.stop_cb = stop_cb,
.enable_options = use_options,
};
/* validate our arguments */
assert(data_cb);
assert(start_cb);
assert(stop_cb);
/* save our kernel PID */
_tftp_kernel_pid = thread_getpid();
/* start the server */
int ret = _tftp_server(&ctxt);
/* remove possibly stale timer */
xtimer_remove(&(ctxt.timer));
/* reset the kernel PID */
_tftp_kernel_pid = KERNEL_PID_UNDEF;
return ret;
}
int gnrc_tftp_server_stop(void)
{
/* check if there is a server running */
if (_tftp_kernel_pid == KERNEL_PID_UNDEF) {
DEBUG("tftp: no TFTP server running\n");
return -1;
}
/* prepare the stop message */
msg_t m = {
thread_getpid(),
TFTP_STOP_SERVER_MSG,
{ NULL }
};
/* send the stop message */
msg_send(&m, _tftp_kernel_pid);
return 0;
}
int _tftp_server(tftp_context_t *ctxt)
{
msg_t msg;
bool active = true;
gnrc_netreg_entry_t entry = GNRC_NETREG_ENTRY_INIT_PID(GNRC_TFTP_DEFAULT_DST_PORT,
sched_active_pid);
while (active) {
int ret = TS_BUSY;
bool got_client = false;
/* register the servers main listening port */
if (gnrc_netreg_register(GNRC_NETTYPE_UDP, &entry)) {
DEBUG("tftp: error starting server.\n");
return TS_FAILED;
}
/* main processing loop */
while (ret == TS_BUSY) {
/* wait for a message */
msg_receive(&msg);
/* check if the server stop message has been received */
if (msg.type == TFTP_STOP_SERVER_MSG) {
active = false;
ret = TS_FAILED;
break;
}
else {
/* continue normal server opration */
DEBUG("tftp: message incoming\n");
ret = _tftp_state_processes(ctxt, &msg);
/* release packet if we received one */
if (msg.type == GNRC_NETAPI_MSG_TYPE_RCV) {
gnrc_pktbuf_release(msg.content.ptr);
}
}
/* if we just accepted a client, disable the server main listening port */
if (!got_client) {
gnrc_netreg_unregister(GNRC_NETTYPE_UDP, &entry);
if (ret == TS_BUSY) {
got_client = true;
DEBUG("tftp: connection established\n");
}
}
}
/* remove any stall timers */
xtimer_remove(&(ctxt->timer));
/* if the server transfer has finished, unregister the client dst port */
gnrc_netreg_unregister(GNRC_NETTYPE_UDP, &(ctxt->entry));
DEBUG("tftp: connection terminated\n");
}
/* unregister our UDP listener on this thread */
gnrc_netreg_unregister(GNRC_NETTYPE_UDP, &entry);
return 0;
}
int _tftp_do_client_transfer(tftp_context_t *ctxt)
{
msg_t msg;
tftp_state ret = TS_BUSY;
/* register our DNS response listener */
gnrc_netreg_entry_t entry = GNRC_NETREG_ENTRY_INIT_PID(ctxt->src_port,
sched_active_pid);
if (gnrc_netreg_register(GNRC_NETTYPE_UDP, &entry)) {
DEBUG("tftp: error starting server.\n");
return TS_FAILED;
}
/* try to start the TFTP transfer */
ret = _tftp_state_processes(ctxt, NULL);
if (ret != TS_BUSY) {
DEBUG("tftp: transfer failed\n");
/* if the start failed return */
return ret;
}
/* main processing loop */
while (ret == TS_BUSY) {
/* wait for a message */
msg_receive(&msg);
DEBUG("tftp: message received\n");
ret = _tftp_state_processes(ctxt, &msg);
/* release packet if we received one */
if (msg.type == GNRC_NETAPI_MSG_TYPE_RCV) {
gnrc_pktbuf_release(msg.content.ptr);
}
}
/* unregister our UDP listener on this thread */
gnrc_netreg_unregister(GNRC_NETTYPE_UDP, &entry);
return ret;
}
tftp_state _tftp_state_processes(tftp_context_t *ctxt, msg_t *m)
{
gnrc_pktsnip_t *outbuf = gnrc_pktbuf_add(NULL, NULL, TFTP_DEFAULT_DATA_SIZE,
GNRC_NETTYPE_UNDEF);
/* check if this is an client start */
if (!m) {
DEBUG("tftp: starting transaction as client\n");
return _tftp_send_start(ctxt, outbuf);
}
else if (m->type == TFTP_TIMEOUT_MSG) {
DEBUG("tftp: timeout occured\n");
if (++(ctxt->retries) > GNRC_TFTP_MAX_RETRIES) {
/* transfer failed due to lost peer */
DEBUG("tftp: peer lost\n");
gnrc_pktbuf_release(outbuf);
return TS_FAILED;
}
/* increase the timeout for congestion control */
ctxt->block_timeout <<= 1;
/* the send message timed out, re-sending */
if (ctxt->dst_port == GNRC_TFTP_DEFAULT_DST_PORT) {
DEBUG("tftp: sending timed out, re-sending\n");
/* we are still negotiating resent, start */
return _tftp_send_start(ctxt, outbuf);
}
else {
DEBUG("tftp: last data or ack packet lost, resending\n");
/* we are sending / receiving data */
/* if we are reading resent the ACK, if writing the DATA */
return _tftp_send_dack(ctxt, outbuf, (ctxt->op == TO_RRQ) ? TO_ACK : TO_DATA);
}
}
else if (m->type != GNRC_NETAPI_MSG_TYPE_RCV) {
DEBUG("tftp: unknown message\n");
gnrc_pktbuf_release(outbuf);
return TS_BUSY;
}
gnrc_pktsnip_t *pkt = m->content.ptr;
if (pkt->size < TFTP_MIN_PACKET_LEN) {
DEBUG("tftp: packet is too short\n");
gnrc_pktbuf_release(outbuf);
return TS_FAILED;
}
gnrc_pktsnip_t *tmp;
tmp = gnrc_pktsnip_search_type(pkt, GNRC_NETTYPE_UDP);
udp_hdr_t *udp = (udp_hdr_t *)tmp->data;
tmp = gnrc_pktsnip_search_type(pkt, GNRC_NETTYPE_IPV6);
ipv6_hdr_t *ip = (ipv6_hdr_t *)tmp->data;
uint8_t *data = (uint8_t *)pkt->data;
xtimer_remove(&(ctxt->timer));
switch (_tftp_parse_type(data)) {
case TO_RRQ:
case TO_WRQ: {
if (byteorder_ntohs(udp->dst_port) != GNRC_TFTP_DEFAULT_DST_PORT) {
/* not a valid start packet */
DEBUG("tftp: incoming packet on port %d dropped\n", byteorder_ntohs(udp->dst_port));
gnrc_pktbuf_release(outbuf);
return TS_FAILED;
}
/* reinitialize the context with the current client */
tftp_opcodes_t op = _tftp_parse_type(data);
_tftp_init_ctxt(&(ip->src), NULL, op, TTM_ASCII, CT_SERVER,
ctxt->start_cb, ctxt->stop_cb, ctxt->data_cb,
ctxt->enable_options, ctxt);
/* get the context of the client */
ctxt->dst_port = byteorder_ntohs(udp->src_port);
DEBUG("tftp: client's port is %" PRIu16 "\n", ctxt->dst_port);
int offset = _tftp_decode_start(ctxt, pkt, outbuf);
DEBUG("tftp: offset after decode start = %i\n", offset);
if (offset < 0) {
DEBUG("tftp: there is no data?\n");
gnrc_pktbuf_release(outbuf);
return TS_FAILED;
}
/* register a listener for the UDP port */
gnrc_netreg_entry_init_pid(&(ctxt->entry), ctxt->src_port,
sched_active_pid);
gnrc_netreg_register(GNRC_NETTYPE_UDP, &(ctxt->entry));
/* try to decode the options */
tftp_state state;
tftp_opcodes_t opcode;
if (ctxt->enable_options &&
_tftp_decode_options(ctxt, pkt, offset) > offset) {
DEBUG("tftp: send option ACK\n");
/* the client send the TFTP options */
opcode = TO_OACK;
}
else {
DEBUG("tftp: send normal ACK\n");
/* the client didn't send options, use ACK and set defaults */
_tftp_set_default_options(ctxt);
/* send the first data block */
if (ctxt->op == TO_RRQ) {
++(ctxt->block_nr);
opcode = TO_DATA;
}
else {
opcode = TO_ACK;
}
}
/* validate if the application accepts the action, mode, filename and transfer_size */
tftp_action_t action = (ctxt->op == TO_RRQ) ? TFTP_READ : TFTP_WRITE;
if (!ctxt->start_cb(action, ctxt->mode, ctxt->file_name, &(ctxt->transfer_size))) {
_tftp_send_error(ctxt, outbuf, TE_ACCESS, "Blocked by user application");
DEBUG("tftp: callback not able to handle mode\n");
return TS_FAILED;
}
/* the client send the TFTP options */
state = _tftp_send_dack(ctxt, outbuf, opcode);
/* check if the client negotiation was successful */
if (state != TS_BUSY) {
DEBUG("tftp: not able to send ACK\n");
}
return state;
} break;
case TO_DATA: {
/* try to process the data */
int proc = _tftp_process_data(ctxt, pkt);
if (proc == TS_APP_FAILED || proc == TS_FAILED) {
DEBUG("tftp: data not accepted\n");
/* the data is not accepted return */
/* we are maybe releasing twice XXX*/
gnrc_pktbuf_release(outbuf);
return proc;
}
if (proc == TS_DUP) {
DEBUG("tftp: duplicated data received, acking...\n");
ctxt->dst_port = byteorder_ntohs(udp->src_port);
DEBUG("tftp: client's port is %" PRIu16 "\n", ctxt->dst_port);
_tftp_send_dack(ctxt, outbuf, TO_ACK);
return TS_BUSY;
}
/* check if this is the first block */
if (!ctxt->block_nr
&& ctxt->dst_port == GNRC_TFTP_DEFAULT_DST_PORT
&& !ctxt->use_options) {
/* no OACK received, restore default TFTP parameters */
_tftp_set_default_options(ctxt);
DEBUG("tftp: restore default TFTP parameters\n");
/* switch the destination port to the src port of the server */
ctxt->dst_port = byteorder_ntohs(udp->src_port);
}
/* wait for the next data block */
DEBUG("tftp: wait for the next data block\n");
++(ctxt->block_nr);
_tftp_send_dack(ctxt, outbuf, TO_ACK);
/* check if the data transfer has finished */
if (proc < (int)ctxt->block_size) {
DEBUG("tftp: transfer finished\n");
if (ctxt->stop_cb) {
ctxt->stop_cb(TFTP_SUCCESS, NULL);
}
return TS_FINISHED;
}
return TS_BUSY;
}
break;
case TO_ACK: {
/* validate if this is the ACK we are waiting for */
if (!_tftp_validate_ack(ctxt, data)) {
/* invalid packet ACK, drop */
gnrc_pktbuf_release(outbuf);
return TS_BUSY;
}
/* check if the write action is finished */
if (ctxt->write_finished) {
gnrc_pktbuf_release(outbuf);
if (ctxt->stop_cb) {
ctxt->stop_cb(TFTP_SUCCESS, NULL);
}
return TS_FINISHED;
}
/* check if this is the first ACK */
if (!ctxt->block_nr && ctxt->dst_port != byteorder_ntohs(udp->src_port)) {
/* no OACK received restore default TFTP parameters */
_tftp_set_default_options(ctxt);
/* switch the destination port to the src port of the server */
ctxt->dst_port = byteorder_ntohs(udp->src_port);
}
/* send the next data block */
++(ctxt->block_nr);
return _tftp_send_dack(ctxt, outbuf, TO_DATA);
} break;
case TO_ERROR: {
tftp_err_codes_t err;
const char *err_msg;
/* decode the received error */
_tftp_decode_error(data, &err, &err_msg);
/* inform the user application about the error */
if (ctxt->stop_cb) {
ctxt->stop_cb(TFTP_PEER_ERROR, err_msg);
}
DEBUG("tftp: ERROR: %s\n", err_msg);
gnrc_pktbuf_release(outbuf);
return TS_FAILED;
} break;
case TO_OACK: {
/* only allow one OACK to be received */
if (ctxt->dst_port != byteorder_ntohs(udp->src_port)) {
DEBUG("tftp: TO_OACK received\n");
/* decode the options */
_tftp_decode_options(ctxt, pkt, 0);
/* take the new source port */
ctxt->dst_port = byteorder_ntohs(udp->src_port);
/* we must send block one to finish the negotiation in send mode */
if (ctxt->op == TO_WRQ) {
++(ctxt->block_nr);
}
}
else {
DEBUG("tftp: dropping double TO_OACK\n");
}
return _tftp_send_dack(ctxt, outbuf, (ctxt->op == TO_WRQ) ? TO_DATA : TO_ACK);
} break;
}
gnrc_pktbuf_release(outbuf);
return TS_FAILED;
}
size_t _tftp_add_option(uint8_t *dst, tftp_opt_t *opt, uint32_t value)
{
size_t offset;
/* set the option name */
memcpy(dst, opt->name, opt->len);
offset = opt->len;
/* set the option value */
offset += sprintf((char *)(dst + opt->len), "%" PRIu32, value);
/* finish option value */
*(dst + offset) = 0;
return ++offset;
}
uint32_t _tftp_append_options(tftp_context_t *ctxt, tftp_header_t *hdr, uint32_t offset)
{
offset += _tftp_add_option(hdr->data + offset, _tftp_options + TOPT_BLKSIZE, ctxt->block_size);
offset += _tftp_add_option(hdr->data + offset, _tftp_options + TOPT_TIMEOUT, (ctxt->timeout / US_PER_SEC));
/**
* Only set the transfer option if we are sending.
* Or when we are reading in bin mode.
*/
if ((ctxt->ct == CT_SERVER && ctxt->op == TO_RRQ) ||
(ctxt->ct == CT_CLIENT && ctxt->op == TO_WRQ) ||
ctxt->mode == TTM_OCTET) {
offset += _tftp_add_option(hdr->data + offset, _tftp_options + TOPT_TSIZE, ctxt->transfer_size);
}
return offset;
}
tftp_state _tftp_send_start(tftp_context_t *ctxt, gnrc_pktsnip_t *buf)
{
/* get required values */
int len = strlen(ctxt->file_name) + 1; /* we also want the \0 char */
tftp_opt_t *m = _tftp_modes + ctxt->mode;
/* start filling the header */
tftp_header_t *hdr = (tftp_header_t *)(buf->data);
hdr->opc = ctxt->op;
memcpy(hdr->data, ctxt->file_name, len);
memcpy(hdr->data + len, m->name, m->len);
/* fill the options */
uint32_t offset = (len + m->len);
if (ctxt->use_options) {
offset = _tftp_append_options(ctxt, hdr, offset);
}
/* send the data */
return _tftp_send(buf, ctxt, offset + sizeof(tftp_header_t));
}
tftp_state _tftp_send_dack(tftp_context_t *ctxt, gnrc_pktsnip_t *buf, tftp_opcodes_t op)
{
size_t len = 0;
assert(op == TO_DATA || op == TO_ACK || op == TO_OACK);
/* fill the packet */
tftp_packet_data_t *pkt = (tftp_packet_data_t *)(buf->data);
pkt->block_nr = byteorder_htons(ctxt->block_nr);
pkt->opc = op;
if (op == TO_DATA) {
DEBUG("tftp: getting data from callback\n");
/* get the required data from the user */
len = ctxt->data_cb(ctxt->block_size * (ctxt->block_nr - 1), pkt->data, ctxt->block_size);
/* check if we are finished on ACK receive */
ctxt->write_finished = (len < ctxt->block_size);
/* enable timeout */
ctxt->block_timeout = ctxt->timeout;
}
else if (op == TO_OACK) {
/* append the options */
len = _tftp_append_options(ctxt, (tftp_header_t *)pkt, 0);
/* disable timeout*/
ctxt->block_timeout = 0;
}
else if (op == TO_ACK) {
/* disable timeout*/
ctxt->block_timeout = 0;
}
/* send the data */
return _tftp_send(buf, ctxt, sizeof(tftp_packet_data_t) + len);
}
tftp_state _tftp_send_error(tftp_context_t *ctxt, gnrc_pktsnip_t *buf, tftp_err_codes_t err, const char *err_msg)
{
int strl = err_msg ? strlen(err_msg) + 1 : 0;
(void) ctxt;
/* fill the packet */
tftp_packet_error_t *pkt = (tftp_packet_error_t *)(buf->data);
pkt->opc = TO_ERROR;
pkt->err_code = err;
memcpy(pkt->err_msg, err_msg, strl);
/* don't set a timeout on the error */
ctxt->block_timeout = 0;
/* return the size of the packet */
_tftp_send(buf, ctxt, sizeof(tftp_packet_error_t) + strl);
return TS_FAILED;
}
tftp_state _tftp_send(gnrc_pktsnip_t *buf, tftp_context_t *ctxt, size_t len)
{
network_uint16_t src_port, dst_port;
gnrc_pktsnip_t *udp, *ip;
assert(len <= TFTP_DEFAULT_DATA_SIZE);
/* down-size the packet to it's used size */
if (len > TFTP_DEFAULT_DATA_SIZE) {
DEBUG("tftp: can't reallocate to bigger packet, buffer overflowed\n");
gnrc_pktbuf_release(buf);
if (ctxt->stop_cb) {
ctxt->stop_cb(TFTP_INTERN_ERROR, "buffer overflowed");
}
return TS_FAILED;
}
else if (gnrc_pktbuf_realloc_data(buf, len) != 0) {
assert(false);
DEBUG("tftp: failed to reallocate data snippet\n");
gnrc_pktbuf_release(buf);
/* inform the user that we can't reallocate */
if (ctxt->stop_cb) {
ctxt->stop_cb(TFTP_INTERN_ERROR, "no reallocate");
}
return TS_FAILED;
}
/* allocate UDP header, set source port := destination port */
src_port.u16 = ctxt->src_port;
dst_port.u16 = ctxt->dst_port;
udp = gnrc_udp_hdr_build(buf, src_port.u16, dst_port.u16);
if (udp == NULL) {
DEBUG("tftp: error unable to allocate UDP header\n");
gnrc_pktbuf_release(buf);
if (ctxt->stop_cb) {
ctxt->stop_cb(TFTP_INTERN_ERROR, "no udp allocate");
}
return TS_FAILED;
}
/* allocate IPv6 header */
ip = gnrc_ipv6_hdr_build(udp, NULL, &(ctxt->peer));
if (ip == NULL) {
DEBUG("tftp: error unable to allocate IPv6 header\n");
gnrc_pktbuf_release(udp);
if (ctxt->stop_cb) {
ctxt->stop_cb(TFTP_INTERN_ERROR, "no ip allocate");
}
return TS_FAILED;
}
if (ipv6_addr_is_link_local(&(ctxt->peer))) {
gnrc_pktsnip_t *netif_hdr = gnrc_netif_hdr_build(NULL, 0, NULL, 0);
if (netif_hdr == NULL) {
DEBUG("tftp: error unable to allocate IPv6 header\n");
gnrc_pktbuf_release(ip);
if (ctxt->stop_cb) {
ctxt->stop_cb(TFTP_INTERN_ERROR, "no netif_hdr allocate");
}
return TS_FAILED;
}
((gnrc_netif_hdr_t *)netif_hdr->data)->if_pid = gnrc_netif_iter(NULL)->pid;
LL_PREPEND(ip, netif_hdr);
}
/* send packet */
if (gnrc_netapi_dispatch_send(GNRC_NETTYPE_UDP, GNRC_NETREG_DEMUX_CTX_ALL,
ip) == 0) {
/* if send failed inform the user */
DEBUG("tftp: error unable to locate UDP thread\n");
gnrc_pktbuf_release(ip);
if (ctxt->stop_cb) {
ctxt->stop_cb(TFTP_INTERN_ERROR, "no dispatch send");
}
return TS_FAILED;
}
/* only set timeout if enabled for this block */
if (ctxt->block_timeout) {
ctxt->timer_msg.type = TFTP_TIMEOUT_MSG;
xtimer_set_msg(&(ctxt->timer), ctxt->block_timeout, &(ctxt->timer_msg), thread_getpid());
DEBUG("tftp: set timeout %" PRIu32 " ms\n", ctxt->block_timeout / US_PER_MS);
}
return TS_BUSY;
}
bool _tftp_validate_ack(tftp_context_t *ctxt, uint8_t *buf)
{
tftp_packet_data_t *pkt = (tftp_packet_data_t *) buf;
return ctxt->block_nr == byteorder_ntohs(pkt->block_nr);
}
int _tftp_decode_start(tftp_context_t *ctxt, gnrc_pktsnip_t *inpkt, gnrc_pktsnip_t *outbuf)
{
/* decode the packet */
tftp_header_t *hdr = (tftp_header_t *)inpkt->data;
/* get the file name and copy terminating byte */
size_t fnlen = strlen((char *)hdr->data) + 1;
if (fnlen >= GNRC_TFTP_MAX_FILENAME_LEN) {
_tftp_send_error(ctxt, outbuf, TE_ILLOPT, "Filename to long");
if (ctxt->stop_cb) {
ctxt->stop_cb(TFTP_INTERN_ERROR, "Filename to long");
}
return TS_FAILED;
}
memcpy(ctxt->file_name, hdr->data, fnlen);
/* Get mode string by advancing pointer */
char *str_mode = (char *)hdr->data + fnlen;
DEBUG("tftp: incoming request '%s', mode: %s\n", ctxt->file_name, str_mode);
/* decode the TFTP transfer mode */
for (uint32_t idx = 0; idx < ARRAY_LEN(_tftp_modes); ++idx) {
if (_tftp_modes[idx].len > (inpkt->size - sizeof(*hdr) - fnlen)) {
continue;
}
if (memcmp(_tftp_modes[idx].name, str_mode, _tftp_modes[idx].len) == 0) {
ctxt->mode = (tftp_mode_t)idx;
return (str_mode + _tftp_modes[idx].len) - (char *)hdr->data;
}
}
return -EINVAL;
}
int _tftp_decode_options(tftp_context_t *ctxt, gnrc_pktsnip_t *buf, uint32_t start)
{
tftp_header_t *pkt = (tftp_header_t *)buf->data;
size_t offset = start;
DEBUG("tftp: decode options\n");
DEBUG("tftp: buffer size = %lu\n", (unsigned long)buf->size);
while ((offset + sizeof(uint16_t)) < (buf->size)) {
DEBUG("tftp: offset = %lu\n", (unsigned long)offset);
/* get the option name */
const char *name = (const char *)(pkt->data + offset);
offset += strlen(name) + 1;
/* get the value name */
const char *value = (const char *)(pkt->data + offset);
offset += strlen(value) + 1;
/* check what option we are parsing */
for (uint32_t idx = 0; idx < ARRAY_LEN(_tftp_options); ++idx) {
if (memcmp(name, _tftp_options[idx].name, _tftp_options[idx].len) == 0) {
/* set the option value of the known options */
switch (idx) {
case TOPT_BLKSIZE:
ctxt->block_size = atoi(value);
DEBUG("tftp: got option TOPT_BLKSIZE = %" PRIu16 "\n", ctxt->block_size);
break;
case TOPT_TSIZE:
ctxt->transfer_size = atoi(value);
DEBUG("tftp: got option TOPT_TSIZE = %" PRIu32 "\n", (uint32_t)ctxt->transfer_size);
if (ctxt->start_cb && ctxt->ct == CT_CLIENT) {
ctxt->start_cb(TFTP_READ, ctxt->mode, ctxt->file_name, &(ctxt->transfer_size));
}
break;
case TOPT_TIMEOUT:
ctxt->timeout = atoi(value) * US_PER_SEC;
DEBUG("tftp: option TOPT_TIMEOUT = %" PRIu32 " ms\n", ctxt->timeout / US_PER_MS);
break;
}
break;
}
}
}
DEBUG("tftp: return %lu\n", (unsigned long)offset);
return offset;
}
int _tftp_process_data(tftp_context_t *ctxt, gnrc_pktsnip_t *buf)
{
tftp_packet_data_t *pkt = (tftp_packet_data_t *) buf->data;
DEBUG("tftp: processing data\n");
uint16_t block_nr = byteorder_ntohs(pkt->block_nr);
/* check if this is the packet we are waiting for */
if (block_nr > (ctxt->block_nr + 1)) {
DEBUG("tftp: incorrect block_nr %d received from server, expected %d\n",
block_nr, (ctxt->block_nr + 1));
return TS_FAILED;
}
if (block_nr < (ctxt->block_nr + 1)) {
DEBUG("tftp: not the packet we were waiting for, expected %d, received %d\n",
(uint16_t)(ctxt->block_nr + 1), block_nr);
return TS_DUP;
}
/* send the user data trough to the user application */
if (ctxt->data_cb(ctxt->block_nr * ctxt->block_size, pkt->data,
buf->size - sizeof(tftp_packet_data_t)) < 0) {
DEBUG("tftp: error in data callback\n");
return TS_APP_FAILED;
}
/* return the number of data bytes received */
return buf->size - sizeof(tftp_packet_data_t);
}
int _tftp_decode_error(uint8_t *buf, tftp_err_codes_t *err, const char * *err_msg)
{
tftp_packet_error_t *pkt = (tftp_packet_error_t *) buf;
/* return the error code and message */
*err = pkt->err_code;
*err_msg = pkt->err_msg;
return sizeof(tftp_packet_error_t) + strlen(pkt->err_msg) + 1;
}
/**
* @}
*/