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

Mitchell Horne mhorne at freebsd.org
Thu Mar 5 18:09:44 UTC 2020


On Mon, Mar 2, 2020 at 5:19 PM John Baldwin <jhb at freebsd.org> wrote:
>
> 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];

Hey John,

This alignas directive seems to break builds with GCC.


Mitchell

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