Index: head/libexec/tftpd/tests/functional.c =================================================================== --- head/libexec/tftpd/tests/functional.c +++ head/libexec/tftpd/tests/functional.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #include @@ -89,6 +90,13 @@ 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 @@ } +/* + * 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 @@ */ #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 @@ */ #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 @@ } /* + * 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 @@ } /* + * 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 @@ } /* + * 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 @@ 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 @@ 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 @@ 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()); } Index: head/libexec/tftpd/tftp-file.h =================================================================== --- head/libexec/tftpd/tftp-file.h +++ head/libexec/tftpd/tftp-file.h @@ -36,4 +36,7 @@ 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); Index: head/libexec/tftpd/tftp-file.c =================================================================== --- head/libexec/tftpd/tftp-file.c +++ head/libexec/tftpd/tftp-file.c @@ -214,6 +214,20 @@ 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) { Index: head/libexec/tftpd/tftp-options.h =================================================================== --- head/libexec/tftpd/tftp-options.h +++ head/libexec/tftpd/tftp-options.h @@ -42,6 +42,7 @@ 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 @@ OPT_BLKSIZE, OPT_BLKSIZE2, OPT_ROLLOVER, + OPT_WINDOWSIZE, }; Index: head/libexec/tftpd/tftp-options.c =================================================================== --- head/libexec/tftpd/tftp-options.c +++ head/libexec/tftpd/tftp-options.c @@ -56,6 +56,7 @@ { "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 @@ 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); } Index: head/libexec/tftpd/tftp-transfer.c =================================================================== --- head/libexec/tftpd/tftp-transfer.c +++ head/libexec/tftpd/tftp-transfer.c @@ -48,6 +48,12 @@ #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 @@ 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 @@ 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 @@ 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 @@ 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 @@ "Timeout #%d on DATA block %d", retry, *block); send_ack(peer, oldblock); + windowblock = 0; continue; } @@ -247,19 +320,42 @@ 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 @@ 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 @@ continue; } + if (debug&DEBUG_SIMPLE) + tftp_log(LOG_DEBUG, "Sent ACK for %d", *block); + windowblock = 0; break; } gettimeofday(&(ts->tstop), NULL); Index: head/libexec/tftpd/tftp-utils.h =================================================================== --- head/libexec/tftpd/tftp-utils.h +++ head/libexec/tftpd/tftp-utils.h @@ -46,6 +46,11 @@ #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 @@ extern uint16_t segsize; extern uint16_t pktsize; +extern uint16_t windowsize; extern int acting_as_client; Index: head/libexec/tftpd/tftp-utils.c =================================================================== --- head/libexec/tftpd/tftp-utils.c +++ head/libexec/tftpd/tftp-utils.c @@ -51,6 +51,7 @@ int maxtimeouts = MAX_TIMEOUTS; uint16_t segsize = SEGSIZE; uint16_t pktsize = SEGSIZE + 4; +uint16_t windowsize = WINDOWSIZE; int acting_as_client; Index: head/libexec/tftpd/tftpd.8 =================================================================== --- head/libexec/tftpd/tftpd.8 +++ head/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 @@ .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 @@ 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 Index: head/usr.bin/tftp/main.c =================================================================== --- head/usr.bin/tftp/main.c +++ head/usr.bin/tftp/main.c @@ -114,6 +114,7 @@ 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 @@ "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 @@ 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); } Index: head/usr.bin/tftp/tftp.1 =================================================================== --- head/usr.bin/tftp/tftp.1 +++ head/usr.bin/tftp/tftp.1 @@ -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 @@ .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 @@ .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