summaryrefslogtreecommitdiff
path: root/libexec
diff options
context:
space:
mode:
authorJohn Baldwin <jhb@FreeBSD.org>2020-11-03 20:43:01 +0000
committerJohn Baldwin <jhb@FreeBSD.org>2020-11-03 20:43:01 +0000
commit7d1eb99a6a6da0ac8f8e6f97691ae1ee1269bb27 (patch)
treeee1244fbcd2320a06246c2c8a1df8b33ac580a71 /libexec
parentd9b1d1487bb59d45b673af5ce8d44944e6d7e0c8 (diff)
downloadsrc-test2-7d1eb99a6a6da0ac8f8e6f97691ae1ee1269bb27.tar.gz
src-test2-7d1eb99a6a6da0ac8f8e6f97691ae1ee1269bb27.zip
MFC 358556,360167: Add support for the TFTP windowsize option.
358556: Add support for the TFTP windowsize option described in RFC 7440. The windowsize option permits multiple blocks to be transmitted before the receiver sends an ACK improving throughput for larger files. 360167: Abort transfer if fseeko() fails.
Notes
Notes: svn path=/stable/12/; revision=367305
Diffstat (limited to 'libexec')
-rw-r--r--libexec/tftpd/tests/functional.c202
-rw-r--r--libexec/tftpd/tftp-file.c14
-rw-r--r--libexec/tftpd/tftp-file.h3
-rw-r--r--libexec/tftpd/tftp-options.c36
-rw-r--r--libexec/tftpd/tftp-options.h2
-rw-r--r--libexec/tftpd/tftp-transfer.c219
-rw-r--r--libexec/tftpd/tftp-utils.c1
-rw-r--r--libexec/tftpd/tftp-utils.h6
-rw-r--r--libexec/tftpd/tftpd.88
9 files changed, 442 insertions, 49 deletions
diff --git a/libexec/tftpd/tests/functional.c b/libexec/tftpd/tests/functional.c
index 58fd4cfc696b..8c35daf5cd65 100644
--- a/libexec/tftpd/tests/functional.c
+++ b/libexec/tftpd/tests/functional.c
@@ -38,6 +38,7 @@ __FBSDID("$FreeBSD$");
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
+#include <stdalign.h>
#include <stdio.h>
#include <unistd.h>
@@ -89,6 +90,13 @@ recv_ack(uint16_t blocknum)
RECV(hdr, NULL, 0);
}
+static void
+recv_oack(const char *options, size_t options_len)
+{
+ char hdr[] = {0, 6};
+ RECV(hdr, options, options_len);
+}
+
/*
* Receive a data packet from tftpd
* @param blocknum Expected block number to be received
@@ -159,6 +167,11 @@ send_ack(uint16_t blocknum)
}
+/*
+ * build an option string
+ */
+#define OPTION_STR(name, value) name "\000" value "\000"
+
/*
* send a read request to tftpd.
* @param filename filename as a string, absolute or relative
@@ -166,6 +179,11 @@ send_ack(uint16_t blocknum)
*/
#define SEND_RRQ(filename, mode) SEND_STR("\0\001" filename "\0" mode "\0")
+/*
+ * send a read request with options
+ */
+#define SEND_RRQ_OPT(filename, mode, options) SEND_STR("\0\001" filename "\0" mode "\000" options)
+
/*
* send a write request to tftpd.
* @param filename filename as a string, absolute or relative
@@ -173,6 +191,11 @@ send_ack(uint16_t blocknum)
*/
#define SEND_WRQ(filename, mode) SEND_STR("\0\002" filename "\0" mode "\0")
+/*
+ * send a write request with options
+ */
+#define SEND_WRQ_OPT(filename, mode, options) SEND_STR("\0\002" filename "\0" mode "\000" options)
+
/* Define a test case, for both IPv4 and IPv6 */
#define TFTPD_TC_DEFINE(name, head, ...) \
static void \
@@ -573,6 +596,32 @@ TFTPD_TC_DEFINE(rrq_medium,)
}
/*
+ * Read a medium file with a window size of 2.
+ */
+TFTPD_TC_DEFINE(rrq_medium_window,)
+{
+ int fd;
+ size_t i;
+ uint32_t contents[192];
+ char options[] = OPTION_STR("windowsize", "2");
+
+ for (i = 0; i < nitems(contents); i++)
+ contents[i] = i;
+
+ fd = open("medium.txt", O_RDWR | O_CREAT, 0644);
+ ATF_REQUIRE(fd >= 0);
+ write_all(fd, contents, sizeof(contents));
+ close(fd);
+
+ SEND_RRQ_OPT("medium.txt", "octet", OPTION_STR("windowsize", "2"));
+ recv_oack(options, sizeof(options) - 1);
+ send_ack(0);
+ recv_data(1, (const char*)&contents[0], 512);
+ recv_data(2, (const char*)&contents[128], 256);
+ send_ack(2);
+}
+
+/*
* Read a file in netascii format
*/
TFTPD_TC_DEFINE(rrq_netascii,)
@@ -652,6 +701,59 @@ TFTPD_TC_DEFINE(rrq_small,)
}
/*
+ * Read a file following the example in RFC 7440.
+ */
+TFTPD_TC_DEFINE(rrq_window_rfc7440,)
+{
+ int fd;
+ size_t i;
+ char options[] = OPTION_STR("windowsize", "4");
+ alignas(uint32_t) char contents[13 * 512 - 4];
+ uint32_t *u32p;
+
+ u32p = (uint32_t *)contents;
+ for (i = 0; i < sizeof(contents) / sizeof(uint32_t); i++)
+ u32p[i] = i;
+
+ fd = open("rfc7440.txt", O_RDWR | O_CREAT, 0644);
+ ATF_REQUIRE(fd >= 0);
+ write_all(fd, contents, sizeof(contents));
+ close(fd);
+
+ SEND_RRQ_OPT("rfc7440.txt", "octet", OPTION_STR("windowsize", "4"));
+ recv_oack(options, sizeof(options) - 1);
+ send_ack(0);
+ recv_data(1, &contents[0 * 512], 512);
+ recv_data(2, &contents[1 * 512], 512);
+ recv_data(3, &contents[2 * 512], 512);
+ recv_data(4, &contents[3 * 512], 512);
+ send_ack(4);
+ recv_data(5, &contents[4 * 512], 512);
+ recv_data(6, &contents[5 * 512], 512);
+ recv_data(7, &contents[6 * 512], 512);
+ recv_data(8, &contents[7 * 512], 512);
+
+ /* ACK 5 as if 6-8 were dropped. */
+ send_ack(5);
+ recv_data(6, &contents[5 * 512], 512);
+ recv_data(7, &contents[6 * 512], 512);
+ recv_data(8, &contents[7 * 512], 512);
+ recv_data(9, &contents[8 * 512], 512);
+ send_ack(9);
+ recv_data(10, &contents[9 * 512], 512);
+ recv_data(11, &contents[10 * 512], 512);
+ recv_data(12, &contents[11 * 512], 512);
+ recv_data(13, &contents[12 * 512], 508);
+
+ /* Drop ACK and after timeout receive 10-13. */
+ recv_data(10, &contents[9 * 512], 512);
+ recv_data(11, &contents[10 * 512], 512);
+ recv_data(12, &contents[11 * 512], 512);
+ recv_data(13, &contents[12 * 512], 508);
+ send_ack(13);
+}
+
+/*
* Try to transfer a file with an unknown mode.
*/
TFTPD_TC_DEFINE(unknown_modes,)
@@ -872,6 +974,38 @@ TFTPD_TC_DEFINE(wrq_medium,)
}
/*
+ * Write a medium file with a window size of 2.
+ */
+TFTPD_TC_DEFINE(wrq_medium_window,)
+{
+ int fd;
+ size_t i;
+ ssize_t r;
+ uint32_t contents[192];
+ char buffer[1024];
+ char options[] = OPTION_STR("windowsize", "2");
+
+ for (i = 0; i < nitems(contents); i++)
+ contents[i] = i;
+
+ fd = open("medium.txt", O_RDWR | O_CREAT, 0666);
+ ATF_REQUIRE(fd >= 0);
+ close(fd);
+
+ SEND_WRQ_OPT("medium.txt", "octet", OPTION_STR("windowsize", "2"));
+ recv_oack(options, sizeof(options) - 1);
+ send_data(1, (const char*)&contents[0], 512);
+ send_data(2, (const char*)&contents[128], 256);
+ recv_ack(2);
+
+ fd = open("medium.txt", O_RDONLY);
+ ATF_REQUIRE(fd >= 0);
+ r = read(fd, buffer, sizeof(buffer));
+ close(fd);
+ require_bufeq((const char*)contents, 768, buffer, r);
+}
+
+/*
* Write a file in netascii format
*/
TFTPD_TC_DEFINE(wrq_netascii,)
@@ -965,6 +1099,70 @@ TFTPD_TC_DEFINE(wrq_truncate,)
ATF_REQUIRE_EQ(sb.st_size, 0);
}
+/*
+ * Write a file following the example in RFC 7440.
+ */
+TFTPD_TC_DEFINE(wrq_window_rfc7440,)
+{
+ int fd;
+ size_t i;
+ ssize_t r;
+ char options[] = OPTION_STR("windowsize", "4");
+ alignas(uint32_t) char contents[13 * 512 - 4];
+ char buffer[sizeof(contents)];
+ uint32_t *u32p;
+
+ u32p = (uint32_t *)contents;
+ for (i = 0; i < sizeof(contents) / sizeof(uint32_t); i++)
+ u32p[i] = i;
+
+ fd = open("rfc7440.txt", O_RDWR | O_CREAT, 0666);
+ ATF_REQUIRE(fd >= 0);
+ close(fd);
+
+ SEND_WRQ_OPT("rfc7440.txt", "octet", OPTION_STR("windowsize", "4"));
+ recv_oack(options, sizeof(options) - 1);
+ send_data(1, &contents[0 * 512], 512);
+ send_data(2, &contents[1 * 512], 512);
+ send_data(3, &contents[2 * 512], 512);
+ send_data(4, &contents[3 * 512], 512);
+ recv_ack(4);
+ send_data(5, &contents[4 * 512], 512);
+
+ /* Drop 6-8. */
+ recv_ack(5);
+ send_data(6, &contents[5 * 512], 512);
+ send_data(7, &contents[6 * 512], 512);
+ send_data(8, &contents[7 * 512], 512);
+ send_data(9, &contents[8 * 512], 512);
+ recv_ack(9);
+
+ /* Drop 11. */
+ send_data(10, &contents[9 * 512], 512);
+ send_data(12, &contents[11 * 512], 512);
+
+ /*
+ * We can't send 13 here as tftpd has probably already seen 12
+ * and sent the ACK of 10 if running locally. While it would
+ * recover by sending another ACK of 10, our state machine
+ * would be out of sync.
+ */
+
+ /* Ignore ACK for 10 and resend 10-13. */
+ recv_ack(10);
+ send_data(10, &contents[9 * 512], 512);
+ send_data(11, &contents[10 * 512], 512);
+ send_data(12, &contents[11 * 512], 512);
+ send_data(13, &contents[12 * 512], 508);
+ recv_ack(13);
+
+ fd = open("rfc7440.txt", O_RDONLY);
+ ATF_REQUIRE(fd >= 0);
+ r = read(fd, buffer, sizeof(buffer));
+ close(fd);
+ require_bufeq(contents, sizeof(contents), buffer, r);
+}
+
/*
* Main
@@ -981,10 +1179,12 @@ ATF_TP_ADD_TCS(tp)
TFTPD_TC_ADD(tp, rrq_eaccess);
TFTPD_TC_ADD(tp, rrq_empty);
TFTPD_TC_ADD(tp, rrq_medium);
+ TFTPD_TC_ADD(tp, rrq_medium_window);
TFTPD_TC_ADD(tp, rrq_netascii);
TFTPD_TC_ADD(tp, rrq_nonexistent);
TFTPD_TC_ADD(tp, rrq_path_max);
TFTPD_TC_ADD(tp, rrq_small);
+ TFTPD_TC_ADD(tp, rrq_window_rfc7440);
TFTPD_TC_ADD(tp, unknown_modes);
TFTPD_TC_ADD(tp, unknown_opcode);
TFTPD_TC_ADD(tp, w_flag);
@@ -994,10 +1194,12 @@ ATF_TP_ADD_TCS(tp)
TFTPD_TC_ADD(tp, wrq_eaccess);
TFTPD_TC_ADD(tp, wrq_eaccess_world_readable);
TFTPD_TC_ADD(tp, wrq_medium);
+ TFTPD_TC_ADD(tp, wrq_medium_window);
TFTPD_TC_ADD(tp, wrq_netascii);
TFTPD_TC_ADD(tp, wrq_nonexistent);
TFTPD_TC_ADD(tp, wrq_small);
TFTPD_TC_ADD(tp, wrq_truncate);
+ TFTPD_TC_ADD(tp, wrq_window_rfc7440);
return (atf_no_error());
}
diff --git a/libexec/tftpd/tftp-file.c b/libexec/tftpd/tftp-file.c
index a8e7bcf9228b..4ca075836f8d 100644
--- a/libexec/tftpd/tftp-file.c
+++ b/libexec/tftpd/tftp-file.c
@@ -214,6 +214,20 @@ write_close(void)
return 0;
}
+off_t
+tell_file(void)
+{
+
+ return ftello(file);
+}
+
+int
+seek_file(off_t offset)
+{
+
+ return fseeko(file, offset, SEEK_SET);
+}
+
int
read_init(int fd, FILE *f, const char *mode)
{
diff --git a/libexec/tftpd/tftp-file.h b/libexec/tftpd/tftp-file.h
index f1f6397a92f5..c9ce5e33b723 100644
--- a/libexec/tftpd/tftp-file.h
+++ b/libexec/tftpd/tftp-file.h
@@ -36,4 +36,7 @@ int read_init(int fd, FILE *f, const char *mode);
size_t read_file(char *buffer, int count);
int read_close(void);
+int seek_file(off_t offset);
+off_t tell_file(void);
+
int synchnet(int peer);
diff --git a/libexec/tftpd/tftp-options.c b/libexec/tftpd/tftp-options.c
index 78bdcfae7d33..10fb112654fd 100644
--- a/libexec/tftpd/tftp-options.c
+++ b/libexec/tftpd/tftp-options.c
@@ -56,6 +56,7 @@ struct options options[] = {
{ "blksize", NULL, NULL, option_blksize, 1 },
{ "blksize2", NULL, NULL, option_blksize2, 0 },
{ "rollover", NULL, NULL, option_rollover, 0 },
+ { "windowsize", NULL, NULL, option_windowsize, 1 },
{ NULL, NULL, NULL, NULL, 0 }
};
@@ -275,6 +276,41 @@ option_blksize2(int peer __unused)
return (0);
}
+int
+option_windowsize(int peer)
+{
+ int size;
+
+ if (options[OPT_WINDOWSIZE].o_request == NULL)
+ return (0);
+
+ size = atoi(options[OPT_WINDOWSIZE].o_request);
+ if (size < WINDOWSIZE_MIN || size > WINDOWSIZE_MAX) {
+ if (acting_as_client) {
+ tftp_log(LOG_ERR,
+ "Invalid windowsize (%d blocks), aborting",
+ size);
+ send_error(peer, EBADOP);
+ return (1);
+ } else {
+ tftp_log(LOG_WARNING,
+ "Invalid windowsize (%d blocks), ignoring request",
+ size);
+ return (0);
+ }
+ }
+
+ /* XXX: Should force a windowsize of 1 for non-seekable files. */
+ asprintf(&options[OPT_WINDOWSIZE].o_reply, "%d", size);
+ windowsize = size;
+
+ if (debug&DEBUG_OPTIONS)
+ tftp_log(LOG_DEBUG, "Setting windowsize to '%s'",
+ options[OPT_WINDOWSIZE].o_reply);
+
+ return (0);
+}
+
/*
* Append the available options to the header
*/
diff --git a/libexec/tftpd/tftp-options.h b/libexec/tftpd/tftp-options.h
index 8768d1ceb3d4..713cbb12e439 100644
--- a/libexec/tftpd/tftp-options.h
+++ b/libexec/tftpd/tftp-options.h
@@ -42,6 +42,7 @@ int option_timeout(int peer);
int option_blksize(int peer);
int option_blksize2(int peer);
int option_rollover(int peer);
+int option_windowsize(int peer);
extern int options_extra_enabled;
extern int options_rfc_enabled;
@@ -61,4 +62,5 @@ enum opt_enum {
OPT_BLKSIZE,
OPT_BLKSIZE2,
OPT_ROLLOVER,
+ OPT_WINDOWSIZE,
};
diff --git a/libexec/tftpd/tftp-transfer.c b/libexec/tftpd/tftp-transfer.c
index c2c9e0793354..1d449ea09778 100644
--- a/libexec/tftpd/tftp-transfer.c
+++ b/libexec/tftpd/tftp-transfer.c
@@ -40,6 +40,7 @@ __FBSDID("$FreeBSD$");
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#include <syslog.h>
#include "tftp-file.h"
@@ -48,6 +49,12 @@ __FBSDID("$FreeBSD$");
#include "tftp-options.h"
#include "tftp-transfer.h"
+struct block_data {
+ off_t offset;
+ uint16_t block;
+ int size;
+};
+
/*
* Send a file via the TFTP data session.
*/
@@ -55,54 +62,79 @@ void
tftp_send(int peer, uint16_t *block, struct tftp_stats *ts)
{
struct tftphdr *rp;
- int size, n_data, n_ack, try;
- uint16_t oldblock;
+ int size, n_data, n_ack, sendtry, acktry;
+ u_int i, j;
+ uint16_t oldblock, windowblock;
char sendbuffer[MAXPKTSIZE];
char recvbuffer[MAXPKTSIZE];
+ struct block_data window[WINDOWSIZE_MAX];
rp = (struct tftphdr *)recvbuffer;
*block = 1;
ts->amount = 0;
+ windowblock = 0;
+ acktry = 0;
do {
+read_block:
if (debug&DEBUG_SIMPLE)
- tftp_log(LOG_DEBUG, "Sending block %d", *block);
+ tftp_log(LOG_DEBUG, "Sending block %d (window block %d)",
+ *block, windowblock);
+ window[windowblock].offset = tell_file();
+ window[windowblock].block = *block;
size = read_file(sendbuffer, segsize);
if (size < 0) {
tftp_log(LOG_ERR, "read_file returned %d", size);
send_error(peer, errno + 100);
goto abort;
}
+ window[windowblock].size = size;
+ windowblock++;
- for (try = 0; ; try++) {
+ for (sendtry = 0; ; sendtry++) {
n_data = send_data(peer, *block, sendbuffer, size);
- if (n_data > 0) {
- if (try == maxtimeouts) {
- tftp_log(LOG_ERR,
- "Cannot send DATA packet #%d, "
- "giving up", *block);
- return;
- }
+ if (n_data == 0)
+ break;
+
+ if (sendtry == maxtimeouts) {
tftp_log(LOG_ERR,
- "Cannot send DATA packet #%d, trying again",
- *block);
- continue;
+ "Cannot send DATA packet #%d, "
+ "giving up", *block);
+ return;
}
+ tftp_log(LOG_ERR,
+ "Cannot send DATA packet #%d, trying again",
+ *block);
+ }
+ /* Only check for ACK for last block in window. */
+ if (windowblock == windowsize || size != segsize) {
n_ack = receive_packet(peer, recvbuffer,
MAXPKTSIZE, NULL, timeoutpacket);
if (n_ack < 0) {
if (n_ack == RP_TIMEOUT) {
- if (try == maxtimeouts) {
+ if (acktry == maxtimeouts) {
tftp_log(LOG_ERR,
"Timeout #%d send ACK %d "
- "giving up", try, *block);
+ "giving up", acktry, *block);
return;
}
tftp_log(LOG_WARNING,
"Timeout #%d on ACK %d",
- try, *block);
- continue;
+ acktry, *block);
+
+ acktry++;
+ ts->retries++;
+ if (seek_file(window[0].offset) != 0) {
+ tftp_log(LOG_ERR,
+ "seek_file failed: %s",
+ strerror(errno));
+ send_error(peer, errno + 100);
+ goto abort;
+ }
+ *block = window[0].block;
+ windowblock = 0;
+ goto read_block;
}
/* Either read failure or ERROR packet */
@@ -112,18 +144,73 @@ tftp_send(int peer, uint16_t *block, struct tftp_stats *ts)
goto abort;
}
if (rp->th_opcode == ACK) {
- ts->blocks++;
- if (rp->th_block == *block) {
- ts->amount += size;
- break;
+ /*
+ * Look for the ACKed block in our open
+ * window.
+ */
+ for (i = 0; i < windowblock; i++) {
+ if (rp->th_block == window[i].block)
+ break;
}
- /* Re-synchronize with the other side */
- (void) synchnet(peer);
- if (rp->th_block == (*block - 1)) {
+ if (i == windowblock) {
+ /* Did not recognize ACK. */
+ if (debug&DEBUG_SIMPLE)
+ tftp_log(LOG_DEBUG,
+ "ACK %d out of window",
+ rp->th_block);
+
+ /* Re-synchronize with the other side */
+ (void) synchnet(peer);
+
+ /* Resend the current window. */
ts->retries++;
- continue;
+ if (seek_file(window[0].offset) != 0) {
+ tftp_log(LOG_ERR,
+ "seek_file failed: %s",
+ strerror(errno));
+ send_error(peer, errno + 100);
+ goto abort;
+ }
+ *block = window[0].block;
+ windowblock = 0;
+ goto read_block;
+ }
+
+ /* ACKed at least some data. */
+ acktry = 0;
+ for (j = 0; j <= i; j++) {
+ if (debug&DEBUG_SIMPLE)
+ tftp_log(LOG_DEBUG,
+ "ACKed block %d",
+ window[j].block);
+ ts->blocks++;
+ ts->amount += window[j].size;
+ }
+
+ /*
+ * Partial ACK. Rewind state to first
+ * un-ACKed block.
+ */
+ if (i + 1 != windowblock) {
+ if (debug&DEBUG_SIMPLE)
+ tftp_log(LOG_DEBUG,
+ "Partial ACK");
+ if (seek_file(window[i + 1].offset) !=
+ 0) {
+ tftp_log(LOG_ERR,
+ "seek_file failed: %s",
+ strerror(errno));
+ send_error(peer, errno + 100);
+ goto abort;
+ }
+ *block = window[i + 1].block;
+ windowblock = 0;
+ ts->retries++;
+ goto read_block;
}
+
+ windowblock = 0;
}
}
@@ -161,31 +248,35 @@ tftp_receive(int peer, uint16_t *block, struct tftp_stats *ts,
struct tftphdr *firstblock, size_t fb_size)
{
struct tftphdr *rp;
- uint16_t oldblock;
- int n_data, n_ack, writesize, i, retry;
+ uint16_t oldblock, windowstart;
+ int n_data, n_ack, writesize, i, retry, windowblock;
char recvbuffer[MAXPKTSIZE];
ts->amount = 0;
+ windowblock = 0;
if (firstblock != NULL) {
writesize = write_file(firstblock->th_data, fb_size);
ts->amount += writesize;
- for (i = 0; ; i++) {
- n_ack = send_ack(peer, *block);
- if (n_ack > 0) {
- if (i == maxtimeouts) {
+ windowblock++;
+ if (windowsize == 1 || fb_size != segsize) {
+ for (i = 0; ; i++) {
+ n_ack = send_ack(peer, *block);
+ if (n_ack > 0) {
+ if (i == maxtimeouts) {
+ tftp_log(LOG_ERR,
+ "Cannot send ACK packet #%d, "
+ "giving up", *block);
+ return;
+ }
tftp_log(LOG_ERR,
- "Cannot send ACK packet #%d, "
- "giving up", *block);
- return;
+ "Cannot send ACK packet #%d, trying again",
+ *block);
+ continue;
}
- tftp_log(LOG_ERR,
- "Cannot send ACK packet #%d, trying again",
- *block);
- continue;
- }
- break;
+ break;
+ }
}
if (fb_size != segsize) {
@@ -216,7 +307,8 @@ tftp_receive(int peer, uint16_t *block, struct tftp_stats *ts,
for (retry = 0; ; retry++) {
if (debug&DEBUG_SIMPLE)
tftp_log(LOG_DEBUG,
- "Receiving DATA block %d", *block);
+ "Receiving DATA block %d (window block %d)",
+ *block, windowblock);
n_data = receive_packet(peer, recvbuffer,
MAXPKTSIZE, NULL, timeoutpacket);
@@ -232,6 +324,7 @@ tftp_receive(int peer, uint16_t *block, struct tftp_stats *ts,
"Timeout #%d on DATA block %d",
retry, *block);
send_ack(peer, oldblock);
+ windowblock = 0;
continue;
}
@@ -247,18 +340,41 @@ tftp_receive(int peer, uint16_t *block, struct tftp_stats *ts,
if (rp->th_block == *block)
break;
+ /*
+ * Ignore duplicate blocks within the
+ * window.
+ *
+ * This does not handle duplicate
+ * blocks during a rollover as
+ * gracefully, but that should still
+ * recover eventually.
+ */
+ if (*block > windowsize)
+ windowstart = *block - windowsize;
+ else
+ windowstart = 0;
+ if (rp->th_block > windowstart &&
+ rp->th_block < *block) {
+ if (debug&DEBUG_SIMPLE)
+ tftp_log(LOG_DEBUG,
+ "Ignoring duplicate DATA block %d",
+ rp->th_block);
+ windowblock++;
+ retry = 0;
+ continue;
+ }
+
tftp_log(LOG_WARNING,
"Expected DATA block %d, got block %d",
*block, rp->th_block);
/* Re-synchronize with the other side */
(void) synchnet(peer);
- if (rp->th_block == (*block-1)) {
- tftp_log(LOG_INFO, "Trying to sync");
- *block = oldblock;
- ts->retries++;
- goto send_ack; /* rexmit */
- }
+
+ tftp_log(LOG_INFO, "Trying to sync");
+ *block = oldblock;
+ ts->retries++;
+ goto send_ack; /* rexmit */
} else {
tftp_log(LOG_WARNING,
@@ -282,7 +398,11 @@ tftp_receive(int peer, uint16_t *block, struct tftp_stats *ts,
if (n_data != segsize)
write_close();
}
+ windowblock++;
+ /* Only send ACKs for the last block in the window. */
+ if (windowblock < windowsize && n_data == segsize)
+ continue;
send_ack:
for (i = 0; ; i++) {
n_ack = send_ack(peer, *block);
@@ -301,6 +421,9 @@ send_ack:
continue;
}
+ if (debug&DEBUG_SIMPLE)
+ tftp_log(LOG_DEBUG, "Sent ACK for %d", *block);
+ windowblock = 0;
break;
}
gettimeofday(&(ts->tstop), NULL);
diff --git a/libexec/tftpd/tftp-utils.c b/libexec/tftpd/tftp-utils.c
index 2111e35bb722..d706639d51e8 100644
--- a/libexec/tftpd/tftp-utils.c
+++ b/libexec/tftpd/tftp-utils.c
@@ -51,6 +51,7 @@ int timeoutnetwork = MAX_TIMEOUTS * TIMEOUT;
int maxtimeouts = MAX_TIMEOUTS;
uint16_t segsize = SEGSIZE;
uint16_t pktsize = SEGSIZE + 4;
+uint16_t windowsize = WINDOWSIZE;
int acting_as_client;
diff --git a/libexec/tftpd/tftp-utils.h b/libexec/tftpd/tftp-utils.h
index 479faa8175a7..0d251874f1fc 100644
--- a/libexec/tftpd/tftp-utils.h
+++ b/libexec/tftpd/tftp-utils.h
@@ -46,6 +46,11 @@ __FBSDID("$FreeBSD$");
#define TIMEOUT_MAX 255 /* Maximum timeout value */
#define MIN_TIMEOUTS 3
+/* For the windowsize option */
+#define WINDOWSIZE 1
+#define WINDOWSIZE_MIN 1
+#define WINDOWSIZE_MAX 65535
+
extern int timeoutpacket;
extern int timeoutnetwork;
extern int maxtimeouts;
@@ -53,6 +58,7 @@ int settimeouts(int timeoutpacket, int timeoutnetwork, int maxtimeouts);
extern uint16_t segsize;
extern uint16_t pktsize;
+extern uint16_t windowsize;
extern int acting_as_client;
diff --git a/libexec/tftpd/tftpd.8 b/libexec/tftpd/tftpd.8
index 0071264ef338..e4f5ab94a2fe 100644
--- a/libexec/tftpd/tftpd.8
+++ b/libexec/tftpd/tftpd.8
@@ -28,7 +28,7 @@
.\" @(#)tftpd.8 8.1 (Berkeley) 6/4/93
.\" $FreeBSD$
.\"
-.Dd June 22, 2011
+.Dd March 2, 2020
.Dt TFTPD 8
.Os
.Sh NAME
@@ -245,6 +245,9 @@ The following RFC's are supported:
.Rs
.%T RFC 2349: TFTP Timeout Interval and Transfer Size Options
.Re
+.Rs
+.%T RFC 7440: TFTP Windowsize Option
+.Re
.Pp
The non-standard
.Cm rollover
@@ -291,6 +294,9 @@ Edwin Groothuis <edwin@FreeBSD.org> performed a major rewrite of the
and
.Xr tftp 1
code to support RFC2348.
+.Pp
+Support for the windowsize option (RFC7440) was introduced in
+.Fx 13.0 .
.Sh NOTES
Files larger than 33,553,919 octets (65535 blocks, last one <512
octets) cannot be correctly transferred without client and server