[PATCH v5 17/20] lwip: tftp: add support of blksize option to client

Jerome Forissier
2024-07-25T13:01:09Z

The TFTP protocol uses a default block size of 512 bytes. This value is
sub-optimal for ethernet devices, which have a MTU (Maximum Transmission
Unit) of 1500 bytes. When taking into acount the overhead of the IP and
UDP layers, this leaves 1468 bytes for the TFTP payload.

This patch introduces a new function: tftp_client_set_blksize() which
may be used to change the block size from the default. It has to be
called after tftp_client_init() and before tftp_get(). If the server
does not support the option, the client will still accept to receive
512-byte blocks.

Signed-off-by: Jerome Forissier <jerome.forissier@linaro.org>
---
lib/lwip/lwip/src/apps/tftp/tftp.c | 94 +++++++++++++++++--
.../lwip/src/include/lwip/apps/tftp_client.h | 1 +
2 files changed, 89 insertions(+), 6 deletions(-)

diff --git a/lib/lwip/lwip/src/apps/tftp/tftp.c b/lib/lwip/lwip/src/apps/tftp/tftp.c
index ddfdbfc0c1b..e08e9665124 100644
--- a/lib/lwip/lwip/src/apps/tftp/tftp.c
+++ b/lib/lwip/lwip/src/apps/tftp/tftp.c
@@ -57,7 +57,7 @@
#include "lwip/timeouts.h"
#include "lwip/debug.h"

-#define TFTP_MAX_PAYLOAD_SIZE 512
+#define TFTP_DEFAULT_BLOCK_SIZE 512
#define TFTP_HEADER_LENGTH 4

#define TFTP_RRQ 1
@@ -65,6 +65,7 @@
#define TFTP_DATA 3
#define TFTP_ACK 4
#define TFTP_ERROR 5
+#define TFTP_OACK 6

enum tftp_error {
TFTP_ERROR_FILE_NOT_FOUND = 1,
@@ -88,9 +89,11 @@ struct tftp_state {
int timer;
int last_pkt;
u16_t blknum;
+ u16_t blksize;
u8_t retries;
u8_t mode_write;
u8_t tftp_mode;
+ bool wait_oack;
};

static struct tftp_state tftp_state;
@@ -137,10 +140,24 @@ send_request(const ip_addr_t *addr, u16_t port, u16_t opcode, const char* fname,
{
size_t fname_length = strlen(fname)+1;
size_t mode_length = strlen(mode)+1;
- struct pbuf* p = init_packet(opcode, 0, fname_length + mode_length - 2);
+ size_t blksize_length = 0;
+ struct pbuf* p;
char* payload;
err_t ret;

+ if (tftp_state.blksize) {
+ blksize_length = 14; /* maximum (blksize is a u16_t): 'blksize\0XXXXX\0' */
+ if (tftp_state.blksize < 10000)
+ blksize_length--;
+ if (tftp_state.blksize < 1000)
+ blksize_length--;
+ if (tftp_state.blksize < 100)
+ blksize_length--;
+ if (tftp_state.blksize < 10)
+ blksize_length--;
+ }
+
+ p = init_packet(opcode, 0, fname_length + mode_length + blksize_length - 2);
if (p == NULL) {
return ERR_MEM;
}
@@ -148,7 +165,10 @@ send_request(const ip_addr_t *addr, u16_t port, u16_t opcode, const char* fname,
payload = (char*) p->payload;
MEMCPY(payload+2, fname, fname_length);
MEMCPY(payload+2+fname_length, mode, mode_length);
+ if (tftp_state.blksize)
+ sprintf(payload+2+fname_length+mode_length, "blksize%c%d%c", 0, tftp_state.blksize, 0);

+ tftp_state.wait_oack = true;
ret = udp_sendto(tftp_state.upcb, p, addr, port);
pbuf_free(p);
return ret;
@@ -221,14 +241,14 @@ send_data(const ip_addr_t *addr, u16_t port)
pbuf_free(tftp_state.last_data);
}

- tftp_state.last_data = init_packet(TFTP_DATA, tftp_state.blknum, TFTP_MAX_PAYLOAD_SIZE);
+ tftp_state.last_data = init_packet(TFTP_DATA, tftp_state.blknum, TFTP_DEFAULT_BLOCK_SIZE);
if (tftp_state.last_data == NULL) {
return;
}

payload = (u16_t *) tftp_state.last_data->payload;

- ret = tftp_state.ctx->read(tftp_state.handle, &payload[2], TFTP_MAX_PAYLOAD_SIZE);
+ ret = tftp_state.ctx->read(tftp_state.handle, &payload[2], TFTP_DEFAULT_BLOCK_SIZE);
if (ret < 0) {
send_error(addr, port, TFTP_ERROR_ACCESS_VIOLATION, "Error occurred while reading the file.");
close_handle();
@@ -239,6 +259,28 @@ send_data(const ip_addr_t *addr, u16_t port)
resend_data(addr, port);
}

+static u16_t payload_size(void)
+{
+ if (tftp_state.blksize)
+ return tftp_state.blksize;
+ return TFTP_DEFAULT_BLOCK_SIZE;
+}
+
+static const char *
+find_option(struct pbuf *p, const char *option)
+{
+ int i;
+ u16_t optlen = strlen(option);
+ const char *b = p->payload;
+
+ for (i = 0; i + optlen + 1 < p->len; i++) {
+ if (lwip_strnstr(b + i, option, optlen))
+ return b + i + optlen + 2;
+ }
+
+ return NULL;
+}
+
static void
tftp_recv(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr, u16_t port)
{
@@ -338,6 +380,15 @@ tftp_recv(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr
}

blknum = lwip_ntohs(sbuf[1]);
+ if (tftp_state.blksize && tftp_state.wait_oack) {
+ /*
+ * Data received while we are expecting an OACK for our blksize option.
+ * This means the server doesn't support it, let's switch back to the
+ * default block size.
+ */
+ tftp_state.blksize = 0;
+ tftp_state.wait_oack = false;
+ }
if (blknum == tftp_state.blknum) {
pbuf_remove_header(p, TFTP_HEADER_LENGTH);

@@ -349,7 +400,7 @@ tftp_recv(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr
send_ack(addr, port, blknum);
}

- if (p->tot_len < TFTP_MAX_PAYLOAD_SIZE) {
+ if (p->tot_len < payload_size()) {
close_handle();
} else {
tftp_state.blknum++;
@@ -386,7 +437,7 @@ tftp_recv(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr
lastpkt = 0;

if (tftp_state.last_data != NULL) {
- lastpkt = tftp_state.last_data->tot_len != (TFTP_MAX_PAYLOAD_SIZE + TFTP_HEADER_LENGTH);
+ lastpkt = tftp_state.last_data->tot_len != (TFTP_DEFAULT_BLOCK_SIZE + TFTP_HEADER_LENGTH);
}

if (!lastpkt) {
@@ -405,6 +456,25 @@ tftp_recv(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr
close_handle();
}
break;
+ case PP_HTONS(TFTP_OACK): {
+ const char *optval = find_option(p, "blksize");
+ u16_t srv_blksize = 0;
+ tftp_state.wait_oack = false;
+ if (optval) {
+ if (!tftp_state.blksize) {
+ /* We did not request this option */
+ send_error(addr, port, TFTP_ERROR_ILLEGAL_OPERATION, "blksize unexpected");
+ }
+ srv_blksize = atoi(optval);
+ if (srv_blksize <= 0 || srv_blksize > tftp_state.blksize) {
+ send_error(addr, port, TFTP_ERROR_ILLEGAL_OPERATION, "Invalid blksize");
+ }
+ LWIP_DEBUGF(TFTP_DEBUG | LWIP_DBG_STATE, ("tftp: accepting blksize=%d\n", srv_blksize));
+ tftp_state.blksize = srv_blksize;
+ }
+ send_ack(addr, port, 0);
+ break;
+ }
default:
send_error(addr, port, TFTP_ERROR_ILLEGAL_OPERATION, "Unknown operation");
break;
@@ -493,6 +563,18 @@ tftp_init_client(const struct tftp_context *ctx)
return tftp_init_common(LWIP_TFTP_MODE_CLIENT, ctx);
}

+/** @ingroup tftp
+ * Set the block size to be used by the TFTP client. The server may choose to
+ * accept a lower value.
+ * @param blksize Block size in bytes
+ */
+void
+tftp_client_set_blksize(u16_t blksize)
+{
+ if (blksize != TFTP_DEFAULT_BLOCK_SIZE)
+ tftp_state.blksize = blksize;
+}
+
/** @ingroup tftp
* Deinitialize ("turn off") TFTP client/server.
*/
diff --git a/lib/lwip/lwip/src/include/lwip/apps/tftp_client.h b/lib/lwip/lwip/src/include/lwip/apps/tftp_client.h
index 24dbda6a8c9..e1e21d06b67 100644
--- a/lib/lwip/lwip/src/include/lwip/apps/tftp_client.h
+++ b/lib/lwip/lwip/src/include/lwip/apps/tftp_client.h
@@ -44,6 +44,7 @@ enum tftp_transfer_mode {
};

err_t tftp_init_client(const struct tftp_context* ctx);
+void tftp_client_set_blksize(u16_t blksize);
err_t tftp_get(void* handle, const ip_addr_t *addr, u16_t port, const char* fname, enum tftp_transfer_mode mode);
err_t tftp_put(void* handle, const ip_addr_t *addr, u16_t port, const char* fname, enum tftp_transfer_mode mode);

--
2.40.1