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
This commit is contained in:
John Baldwin 2020-03-02 22:19:30 +00:00
parent a969e975c9
commit fdf929ff91
11 changed files with 462 additions and 52 deletions

View File

@ -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 \
@ -572,6 +595,32 @@ TFTPD_TC_DEFINE(rrq_medium,)
send_ack(2);
}
/*
* 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
*/
@ -651,6 +700,59 @@ TFTPD_TC_DEFINE(rrq_small,)
send_ack(1);
}
/*
* 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.
*/
@ -871,6 +973,38 @@ TFTPD_TC_DEFINE(wrq_medium,)
require_bufeq((const char*)contents, 768, buffer, r);
}
/*
* 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
*/
@ -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());
}

View File

@ -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)
{

View File

@ -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);

View File

@ -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
*/

View File

@ -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,
};

View File

@ -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;
}
tftp_log(LOG_ERR,
"Cannot send DATA packet #%d, trying again",
*block);
continue;
}
if (n_data == 0)
break;
if (sendtry == maxtimeouts) {
tftp_log(LOG_ERR,
"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 *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;
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_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 +287,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 +304,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 +320,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 +378,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 +401,9 @@ tftp_receive(int peer, uint16_t *block, struct tftp_stats *ts,
continue;
}
if (debug&DEBUG_SIMPLE)
tftp_log(LOG_DEBUG, "Sent ACK for %d", *block);
windowblock = 0;
break;
}
gettimeofday(&(ts->tstop), NULL);

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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 }
};
@ -1069,3 +1071,27 @@ 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);
}

View File

@ -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
@ -236,6 +244,9 @@ The following RFC's are supported:
.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
.Cm rollover