svn commit: r358556 - in head: libexec/tftpd libexec/tftpd/tests usr.bin/tftp

John Baldwin jhb at FreeBSD.org
Mon Mar 2 22:19:33 UTC 2020


Author: jhb
Date: Mon Mar  2 22:19:30 2020
New Revision: 358556
URL: https://svnweb.freebsd.org/changeset/base/358556

Log:
  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.
  
  Reviewed by:	asomers
  MFC after:	2 weeks
  Sponsored by:	DARPA
  Differential Revision:	https://reviews.freebsd.org/D23836

Modified:
  head/libexec/tftpd/tests/functional.c
  head/libexec/tftpd/tftp-file.c
  head/libexec/tftpd/tftp-file.h
  head/libexec/tftpd/tftp-options.c
  head/libexec/tftpd/tftp-options.h
  head/libexec/tftpd/tftp-transfer.c
  head/libexec/tftpd/tftp-utils.c
  head/libexec/tftpd/tftp-utils.h
  head/libexec/tftpd/tftpd.8
  head/usr.bin/tftp/main.c
  head/usr.bin/tftp/tftp.1

Modified: head/libexec/tftpd/tests/functional.c
==============================================================================
--- head/libexec/tftpd/tests/functional.c	Mon Mar  2 21:19:51 2020	(r358555)
+++ head/libexec/tftpd/tests/functional.c	Mon Mar  2 22:19:30 2020	(r358556)
@@ -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,7 +1099,71 @@ 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());
 }

Modified: head/libexec/tftpd/tftp-file.c
==============================================================================
--- head/libexec/tftpd/tftp-file.c	Mon Mar  2 21:19:51 2020	(r358555)
+++ head/libexec/tftpd/tftp-file.c	Mon Mar  2 22:19:30 2020	(r358556)
@@ -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)
 {

Modified: head/libexec/tftpd/tftp-file.h
==============================================================================
--- head/libexec/tftpd/tftp-file.h	Mon Mar  2 21:19:51 2020	(r358555)
+++ head/libexec/tftpd/tftp-file.h	Mon Mar  2 22:19:30 2020	(r358556)
@@ -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);

Modified: head/libexec/tftpd/tftp-options.c
==============================================================================
--- head/libexec/tftpd/tftp-options.c	Mon Mar  2 21:19:51 2020	(r358555)
+++ head/libexec/tftpd/tftp-options.c	Mon Mar  2 22:19:30 2020	(r358556)
@@ -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 }
 };
 
@@ -271,6 +272,41 @@ option_blksize2(int peer __unused)
 	if (debug&DEBUG_OPTIONS)
 		tftp_log(LOG_DEBUG, "Setting blksize2 to '%s'",
 		    options[OPT_BLKSIZE2].o_reply);
+
+	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);
 }

Modified: head/libexec/tftpd/tftp-options.h
==============================================================================
--- head/libexec/tftpd/tftp-options.h	Mon Mar  2 21:19:51 2020	(r358555)
+++ head/libexec/tftpd/tftp-options.h	Mon Mar  2 22:19:30 2020	(r358556)
@@ -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,
 };

Modified: head/libexec/tftpd/tftp-transfer.c
==============================================================================
--- head/libexec/tftpd/tftp-transfer.c	Mon Mar  2 21:19:51 2020	(r358555)
+++ head/libexec/tftpd/tftp-transfer.c	Mon Mar  2 22:19:30 2020	(r358556)
@@ -48,6 +48,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 +61,73 @@ 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++;
+					seek_file(window[0].offset);
+					*block = window[0].block;
+					windowblock = 0;
+					goto read_block;
 				}
 
 				/* Either read failure or ERROR packet */
@@ -112,18 +137,60 @@ tftp_send(int peer, uint16_t *block, struct tftp_stats
 				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;
+					seek_file(window[0].offset);
+					*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");
+					seek_file(window[i + 1].offset);
+					*block = window[i + 1].block;
+					windowblock = 0;
+					ts->retries++;
+					goto read_block;
+				}
+
+				windowblock = 0;
 			}
 
 		}
@@ -161,31 +228,35 @@ tftp_receive(int peer, uint16_t *block, struct tftp_st
     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 +287,8 @@ tftp_receive(int peer, uint16_t *block, struct tftp_st
 		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 +304,7 @@ tftp_receive(int peer, uint16_t *block, struct tftp_st
 					    "Timeout #%d on DATA block %d",
 					    retry, *block);
 					send_ack(peer, oldblock);
+					windowblock = 0;
 					continue;
 				}
 
@@ -247,19 +320,42 @@ tftp_receive(int peer, uint16_t *block, struct tftp_st
 				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,
 				    "Expected DATA block, got %s block",
@@ -282,7 +378,11 @@ tftp_receive(int peer, uint16_t *block, struct tftp_st
 			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 +401,9 @@ send_ack:
 				continue;
 			}
 
+			if (debug&DEBUG_SIMPLE)
+				tftp_log(LOG_DEBUG, "Sent ACK for %d", *block);
+			windowblock = 0;
 			break;
 		}
 		gettimeofday(&(ts->tstop), NULL);

Modified: head/libexec/tftpd/tftp-utils.c
==============================================================================
--- head/libexec/tftpd/tftp-utils.c	Mon Mar  2 21:19:51 2020	(r358555)
+++ head/libexec/tftpd/tftp-utils.c	Mon Mar  2 22:19:30 2020	(r358556)
@@ -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;
 

Modified: head/libexec/tftpd/tftp-utils.h
==============================================================================
--- head/libexec/tftpd/tftp-utils.h	Mon Mar  2 21:19:51 2020	(r358555)
+++ head/libexec/tftpd/tftp-utils.h	Mon Mar  2 22:19:30 2020	(r358556)
@@ -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,
 
 extern uint16_t	segsize;
 extern uint16_t	pktsize;
+extern uint16_t	windowsize;
 
 extern int	acting_as_client;
 

Modified: head/libexec/tftpd/tftpd.8
==============================================================================
--- head/libexec/tftpd/tftpd.8	Mon Mar  2 21:19:51 2020	(r358555)
+++ head/libexec/tftpd/tftpd.8	Mon Mar  2 22:19:30 2020	(r358556)
@@ -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 at FreeBSD.org> performed a major 
 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

Modified: head/usr.bin/tftp/main.c
==============================================================================
--- head/usr.bin/tftp/main.c	Mon Mar  2 21:19:51 2020	(r358555)
+++ head/usr.bin/tftp/main.c	Mon Mar  2 22:19:30 2020	(r358556)
@@ -114,6 +114,7 @@ static void	setblocksize2(int, char **);
 static void	setoptions(int, char **);
 static void	setrollover(int, char **);
 static void	setpacketdrop(int, char **);
+static void	setwindowsize(int, char **);
 
 static void command(bool, EditLine *, History *, HistEvent *) __dead2;
 static const char *command_prompt(void);
@@ -158,6 +159,7 @@ static struct cmd cmdtab[] = {
 	  "enable or disable RFC2347 style options" },
 	{ "help",	help,		"print help information"	},
 	{ "packetdrop",	setpacketdrop,	"artificial packetloss feature"	},
+	{ "windowsize",	setwindowsize,	"set windowsize[*]"		},
 	{ "?",		help,		"print help information"	},
 	{ NULL,		NULL,		NULL				}
 };
@@ -1068,4 +1070,28 @@ setpacketdrop(int argc, char *argv[])
 
 	printf("Randomly %d in 100 packets will be dropped\n",
 	    packetdroppercentage);
+}
+
+static void
+setwindowsize(int argc, char *argv[])
+{
+
+	if (!options_rfc_enabled)
+		printf("RFC2347 style options are not enabled "
+		    "(but proceeding anyway)\n");
+
+	if (argc != 1) {
+		int size = atoi(argv[1]);
+
+		if (size < WINDOWSIZE_MIN || size > WINDOWSIZE_MAX) {
+			printf("Windowsize should be between %d and %d "
+			    "blocks.\n", WINDOWSIZE_MIN, WINDOWSIZE_MAX);
+			return;
+		} else {
+			asprintf(&options[OPT_WINDOWSIZE].o_request, "%d",
+			    size);
+		}
+	}
+	printf("Windowsize is now %s blocks.\n",
+	    options[OPT_WINDOWSIZE].o_request);
 }

Modified: head/usr.bin/tftp/tftp.1
==============================================================================
--- head/usr.bin/tftp/tftp.1	Mon Mar  2 21:19:51 2020	(r358555)
+++ head/usr.bin/tftp/tftp.1	Mon Mar  2 22:19:30 2020	(r358556)
@@ -28,7 +28,7 @@
 .\"     @(#)tftp.1	8.2 (Berkeley) 4/18/94
 .\" $FreeBSD$
 .\"
-.Dd Aug 22, 2018
+.Dd March 2, 2020
 .Dt TFTP 1
 .Os
 .Sh NAME
@@ -216,6 +216,14 @@ Toggle packet tracing.
 .Pp
 .It Cm verbose
 Toggle verbose mode.
+.Pp
+.It Cm windowsize Op Ar size
+Sets the TFTP windowsize option in TFTP Read Request or Write Request packets to
+.Op Ar size
+blocks as specified in RFC 7440.
+Valid values are between 1 and 65535.
+If no windowsize is specified,
+then the default windowsize of 1 block will be used.
 .El
 .Sh SEE ALSO
 .Xr tftpd 8
@@ -235,6 +243,9 @@ The following RFC's are supported:
 .Re
 .Rs
 .%T RFC 3617: Uniform Resource Identifier (URI) Scheme and Applicability Statement for the Trivial File Transfer Protocol (TFTP)
+.Re
+.Rs
+.%T RFC 7440: TFTP Windowsize Option
 .Re
 .Pp
 The non-standard


More information about the svn-src-all mailing list