Blob Blame History Raw
commit e46782908e7026f27ef92de52e47ec3720116125
Author: Jan Synacek <jsynacek@redhat.com>
Date:   Tue Nov 7 08:26:54 2017 +0100

    implement rfc7440

diff --git a/common/Makefile b/common/Makefile
index a825213..d1e97b5 100644
--- a/common/Makefile
+++ b/common/Makefile
@@ -4,7 +4,7 @@ VERSION = $(shell cat ../version)
 -include ../MCONFIG
 include ../MRULES
 
-OBJS = tftpsubs.$(O)
+OBJS = tftpsubs.$(O) common.$(O)
 LIB  = libcommon.a
 
 all: $(LIB)
@@ -14,7 +14,7 @@ $(LIB): $(OBJS)
 	$(AR) $(LIB) $(OBJS)
 	$(RANLIB) $(LIB)
 
-$(OBJS): tftpsubs.h
+$(OBJS): tftpsubs.h common.h
 
 install:
 
diff --git a/common/common.c b/common/common.c
new file mode 100644
index 0000000..a4e2fef
--- /dev/null
+++ b/common/common.c
@@ -0,0 +1,433 @@
+/*
+ * Copyright (c) 2017 Jan Synáček
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <poll.h>
+#include <stdarg.h>
+#include <syslog.h>
+#include "common.h"
+
+static char *pktbuf;
+static int verbose;
+
+const int SYNC_TIMEOUT = 50; /* ms */
+
+static void die(const char *fmt, ...)
+{
+    va_list ap;
+
+    va_start(ap, fmt);
+    fprintf(stderr, "fatal: ");
+    vfprintf(stderr, fmt, ap);
+    printf("\n");
+    va_end(ap);
+    exit(1);
+}
+
+int format_error(struct tftphdr *tp, char *error)
+{
+    int r = 0;
+
+    if (error)
+        r = snprintf(error, ERROR_MAXLEN, "Error code %d: %s", ntohs(tp->th_code), tp->th_msg);
+    return r;
+}
+
+void die_on_error(struct tftphdr *tp)
+{
+    char error[ERROR_MAXLEN];
+
+    snprintf(error, ERROR_MAXLEN, "Error code %d: %s", ntohs(tp->th_code), tp->th_msg);
+    fprintf(stderr, "%s\n", error);
+    exit(1);
+}
+
+void send_error(int sockfd, union sock_addr *to, const char *msg)
+{
+    char buf[516];
+    struct tftphdr *out = (struct tftphdr *)buf;
+    int len;
+
+    out->th_opcode = htons(ERROR);
+    out->th_code = htons(EUNDEF);
+
+    len = strlen(msg) + 1;
+    memset(buf, 0, 516);
+    memcpy(out->th_msg, msg, len > 511 ? 511 : len);
+    len += 4;
+
+    if (to) {
+        if (sendto(sockfd, out, len, 0, &to->sa, SOCKLEN(to)) != len)
+            die("send_error: sendto: %s", strerror(-errno));
+    } else {
+        if (send(sockfd, out, len, 0) != len)
+            die("send_error: send: %s", strerror(-errno));
+    }
+}
+
+static void _send_ack(int sockfd, union sock_addr *to, unsigned short block, int check_errors)
+{
+    struct tftphdr out;
+    out.th_opcode = htons(ACK);
+    out.th_block  = htons(block);
+
+    if (to) {
+        if (sendto(sockfd, &out, 4, 0, &to->sa, SOCKLEN(to)) != 4 && check_errors)
+            die("send_ack: sendto: %m");
+    } else {
+        if (send(sockfd, &out, 4, 0) != 4 && check_errors)
+            die("send_ack: send: %m");
+    }
+}
+
+void send_ack(int sockfd, union sock_addr *to, unsigned short block)
+{
+    _send_ack(sockfd, to, block, 1);
+}
+
+
+static size_t read_data(FILE *fp,
+                        int blocksize,
+                        unsigned short block,
+                        int seek,
+                        struct tftphdr *out)
+{
+    out->th_opcode = htons(DATA);
+    out->th_block  = htons(block);
+    if (seek)
+        (void) fseek(fp, seek, SEEK_CUR);
+    return fread(pktbuf + 4, 1, blocksize, fp);
+}
+
+static size_t write_data(FILE *fp, char *buf, size_t count, int convert)
+{
+    char wbuf[count];
+    size_t i = 0, cnt = 0;
+    static int was_cr = 0;
+
+    /* TODO: jsynacek: I don't think any conversion should take place...
+     * RFC 1350 says: "A host which receives netascii mode data must translate
+     * the data to its own format."
+     * That basically means nothing. What does "own format" even mean?
+     * The original implementation translated \r\n to \n and skipped \0 bytes,
+     * which aren't even legal in the netascii format.
+     * However, I believe that the file should remain as is before and after
+     * the transfer.
+     */
+    convert = 0;
+
+    if (convert == 0)
+        return fwrite(buf, 1, count, fp);
+
+    /* Working conversion. Leave it as dead code for now. */
+    if (was_cr && buf[0] == '\n') {
+        wbuf[cnt++] = '\n';
+        i = 1;
+        (void) fseek(fp, -1, SEEK_CUR);
+    }
+
+    while(i < count) {
+        if (buf[i] == '\r' && buf[i + 1] == '\n') {
+            wbuf[cnt++] = '\n';
+            ++i;
+        } else if (buf[i] == '\0') {
+            /* Skip it */
+        } else {
+            wbuf[cnt++] = buf[i];
+        }
+        ++i;
+    }
+    /* Preserve state between data chunks */
+    was_cr = buf[i - 1] == '\r';
+
+    if (fwrite(wbuf, 1, cnt, fp) == 0)
+        return 0;
+
+    return count;
+}
+
+void set_verbose(int v)
+{
+    verbose = v;
+}
+
+int recv_with_timeout(int s, void *in, int len, int timeout)
+{
+    return recvfrom_flags_with_timeout(s, in, len, NULL, timeout, 0);
+}
+
+int recvfrom_with_timeout(int s,
+                          void *in,
+                          size_t len,
+                          union sock_addr *from,
+                          int timeout)
+{
+    return recvfrom_flags_with_timeout(s, in, len, from, timeout, 0);
+}
+
+int recvfrom_flags_with_timeout(int s,
+                                void *in,
+                                size_t len,
+                                union sock_addr *from,
+                                int timeout,
+                                int flags)
+{
+    socklen_t fromlen = sizeof(from);
+    struct pollfd pfd;
+    int r;
+
+    pfd.fd = s;
+    pfd.events = POLLIN;
+    pfd.revents = 0;
+
+    r = poll(&pfd, 1, timeout);
+    if (r > 0) {
+        if (from) {
+            r = recvfrom(s, in, len, flags, &from->sa, &fromlen);
+            if (r < 0)
+                die("recvfrom_flags_with_timeout: recvfrom: %m");
+        } else {
+            r = recv(s, in, len, flags);
+            if (r < 0)
+                die("recvfrom_flags_with_timeout: recv: %m");
+        }
+    }
+
+    return r;
+}
+
+int receiver(int sockfd,
+             union sock_addr *server,
+             int blocksize,
+             int windowsize,
+             int timeout,
+             FILE *fp,
+             unsigned long *received,
+             char *error)
+{
+    struct tftphdr *tp;
+    unsigned short tp_opcode, tp_block;
+    unsigned short block = 1;
+    unsigned long amount = 0;
+    int pktsize = blocksize + 4;
+    int window = 1;
+    size_t size;
+    int retries;
+    int n, r = 0;
+
+    pktbuf = calloc(pktsize, 1);
+    if (!pktbuf)
+        die("Out of memory!");
+    tp = (struct tftphdr *)pktbuf;
+
+    retries = RETRIES;
+    do {
+        n = recvfrom_with_timeout(sockfd, tp, pktsize, server, timeout);
+        if (n == 0) {
+            if (--retries <= 0) {
+                r = E_TIMED_OUT;
+                goto abort;
+            }
+            continue;
+        }
+        retries = RETRIES;
+
+        tp_opcode = ntohs(tp->th_opcode);
+        tp_block  = ntohs(tp->th_block);
+
+        if (tp_opcode == DATA) {
+            if (tp_block == block) {
+                size = write_data(fp, tp->th_data, n - 4, 0);
+                if (size == 0 && ferror(fp)) {
+                    send_error(sockfd, server, "Failed to write data");
+                    r = E_FAILED_TO_WRITE;
+                    goto abort;
+                }
+                amount += size;
+
+                if (window++ >= windowsize || size != blocksize) {
+                    send_ack(sockfd, server, block);
+                    window = 1;
+                }
+
+                ++block;
+            } else {
+                do {
+                    n = recvfrom_with_timeout(sockfd, pktbuf, pktsize, server, SYNC_TIMEOUT);
+                } while (n > 0);
+                if (windowsize > 0) {
+                    send_ack(sockfd, server, block - 1);
+                }
+                window = 1;
+                n = 0;
+            }
+        } else if (tp_opcode == ERROR) {
+            format_error(tp, error);
+            r = E_RECEIVED_ERROR;
+            goto abort;
+        } else {
+            r = E_UNEXPECTED_PACKET;
+            goto abort;
+        }
+    } while (size == blocksize || n == 0);
+
+    /* Last ack can get lost, let's try and resend it twice
+     * to make it more likely that the ack gets to the sender.
+     */
+    --block;
+    for (n = 0; n < 2; ++n) {
+        usleep(SYNC_TIMEOUT * 1000);
+        _send_ack(sockfd, server, block, 0);
+    }
+
+    if (received)
+        *received = amount;
+abort:
+    free(pktbuf);
+    return r;
+}
+
+int sender(int sockfd,
+           union sock_addr *server,
+           int blocksize,
+           int windowsize,
+           int timeout,
+           int rollover,
+           FILE *fp,
+           unsigned long *sent)
+{
+    struct tftphdr *tp;
+    unsigned short tp_opcode, tp_block;
+    unsigned short block = 1;
+    unsigned long amount = 0;
+    int l_timeout = timeout;
+    int pktsize = blocksize + 4;
+    int window = 1;
+    int seek = 0;
+    int retries;
+    size_t size;
+    int done = 0;
+    int n, r = 0;
+
+    pktbuf = calloc(pktsize, 1);
+    if (!pktbuf)
+        die("Out of memory!");
+    tp = (struct tftphdr *)pktbuf;
+
+    retries = RETRIES;
+    do {
+        size = read_data(fp, blocksize, block, seek, tp);
+        if (size == 0 && ferror(fp)) {
+            send_error(sockfd, server, "Error while reading the file");
+            r = E_FAILED_TO_READ;
+            goto abort;
+        }
+        amount += size;
+        seek = 0;
+
+        if (server)
+            n = sendto(sockfd, tp, size + 4, 0, &server->sa, SOCKLEN(server));
+        else
+            n = send(sockfd, tp, size + 4, 0);
+        if (n != size + 4) {
+            syslog(LOG_WARNING, "tftpd: send: %m");
+            r = E_SYSTEM_ERROR;
+            goto abort;
+        }
+
+        if (window++ < windowsize) {
+            /* Even if the entire window is not sent, the server should check for incoming packets
+             * and react to out of order ACKs, or ERRORs.
+             */
+            l_timeout = 0;
+        } else {
+            l_timeout = timeout;
+            window = 1;
+        }
+        done = size != blocksize;
+        if (done) {
+            l_timeout = timeout;
+            window = 1;
+        }
+
+        n = recvfrom_with_timeout(sockfd, pktbuf, pktsize, server, l_timeout);
+        if (n < 0) {
+            syslog(LOG_WARNING, "tftpd: recv: %m");
+            r = E_SYSTEM_ERROR;
+            goto abort;
+        } else if (l_timeout > 0 && n == 0) {
+            seek = -size;
+            if (--retries <= 0) {
+                r = E_TIMED_OUT;
+                goto abort;
+            }
+            done = 0;
+            continue;
+        }
+
+        if (++block == 0)
+            block = rollover;
+
+        tp_opcode = ntohs(tp->th_opcode);
+        tp_block  = ntohs(tp->th_block);
+
+        if (tp_opcode == ACK) {
+            if (tp_block != (unsigned short)(block - 1)) {
+                int offset = tp_block;
+
+                done = 0;
+                do {
+                    n = recvfrom_with_timeout(sockfd, pktbuf, pktsize, server, SYNC_TIMEOUT);
+                } while (n > 0);
+                /* This is a bit of a hack. Mismatched packets that are "over the edge" of the unsigned short
+                 * have to be handled and a correct seek has to be issued. In theory, overflowing block number
+                 * should not even be supported, as it is impossible to distinguish correctly if more than 2^16
+                 * packets are sent, but the first ones are received later than the last ones.
+                 */
+                if (tp_block > 65000 && tp_block > (unsigned short)(block - 1))
+                    offset -= 65535;
+                seek = (offset - block + 2) * blocksize - size;
+            }
+            block = tp_block + 1;
+            retries = RETRIES;
+        } else if (tp_opcode == ERROR) {
+            r = E_RECEIVED_ERROR;
+            goto abort;
+        }
+    } while (!done);
+
+    if (sent)
+        *sent = amount;
+abort:
+    free(pktbuf);
+    return r;
+}
diff --git a/common/common.h b/common/common.h
new file mode 100644
index 0000000..31c1b7c
--- /dev/null
+++ b/common/common.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2017 Jan Synáček
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *	This product includes software developed by the University of
+ *	California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _COMMON_H
+#define _COMMON_H
+
+#include <netinet/in.h>
+#include "config.h"
+
+#define TIMEOUT 1000 /* ms */
+#define RETRIES 6
+
+#define E_TIMED_OUT -1
+#define E_RECEIVED_ERROR -2
+#define E_UNEXPECTED_PACKET -3
+#define E_FAILED_TO_READ -4
+#define E_FAILED_TO_WRITE -5
+#define E_SYSTEM_ERROR -6
+#define ERROR_MAXLEN 511
+
+union sock_addr {
+    struct sockaddr     sa;
+    struct sockaddr_in  si;
+#ifdef HAVE_IPV6
+    struct sockaddr_in6 s6;
+#endif
+};
+
+#define SOCKLEN(sock) \
+    (((union sock_addr*)sock)->sa.sa_family == AF_INET ? \
+    (sizeof(struct sockaddr_in)) : \
+    (sizeof(union sock_addr)))
+
+const char *opcode_to_str(unsigned short opcode);
+int str_equal(const char *s1, const char *s2);
+void set_verbose(int v);
+
+int format_error(struct tftphdr *tp, char *error);
+void die_on_error(struct tftphdr *tp);
+void send_error(int sockfd, union sock_addr *to, const char *msg);
+void send_ack(int sockfd, union sock_addr *to, unsigned short block);
+int recv_with_timeout(int s, void *in, int len, int timeout);
+int recvfrom_with_timeout(int s, void *in, size_t len, union sock_addr *from, int timeout);
+int recvfrom_flags_with_timeout(int s,
+                                void *in,
+                                size_t len,
+                                union sock_addr *from,
+                                int timeout,
+                                int flags);
+int receiver(int sockfd,
+             union sock_addr *server,
+             int blocksize,
+             int windowsize,
+             int timeout,
+             FILE *fp,
+             unsigned long *received,
+             char *error);
+
+int sender(int sockfd,
+           union sock_addr *server,
+           int blocksize,
+           int windowsize,
+           int timeout,
+           int rollover,
+           FILE *fp,
+           unsigned long *sent);
+#endif
diff --git a/common/tftpsubs.c b/common/tftpsubs.c
index 8c999f6..6033e9b 100644
--- a/common/tftpsubs.c
+++ b/common/tftpsubs.c
@@ -404,3 +404,23 @@ char *strip_address(char *addr)
     return addr;
 }
 #endif
+
+
+int str_equal(const char *s1, const char *s2)
+{
+    return !strcmp(s1, s2);
+}
+
+
+const char *opcode_to_str(unsigned short opcode)
+{
+    switch(opcode) {
+        case RRQ:   return "RRQ";
+        case WRQ:   return "WRQ";
+        case DATA:  return "DATA";
+        case ACK:   return "ACK";
+        case ERROR: return "ERROR";
+        case OACK:  return "OACK";
+    }
+    return "UNKNOWN";
+}
diff --git a/common/tftpsubs.h b/common/tftpsubs.h
index b3a3bf3..311b141 100644
--- a/common/tftpsubs.h
+++ b/common/tftpsubs.h
@@ -38,21 +38,9 @@
 #ifndef TFTPSUBS_H
 #define TFTPSUBS_H
 
+#include "common.h"
 #include "config.h"
 
-union sock_addr {
-    struct sockaddr     sa;
-    struct sockaddr_in  si;
-#ifdef HAVE_IPV6
-    struct sockaddr_in6 s6;
-#endif
-};
-
-#define SOCKLEN(sock) \
-    (((union sock_addr*)sock)->sa.sa_family == AF_INET ? \
-    (sizeof(struct sockaddr_in)) : \
-    (sizeof(union sock_addr)))
-
 #ifdef HAVE_IPV6
 #define SOCKPORT(sock) \
     (((union sock_addr*)sock)->sa.sa_family == AF_INET ? \
diff --git a/tftp/extern.h b/tftp/extern.h
index 78474fc..dd0523f 100644
--- a/tftp/extern.h
+++ b/tftp/extern.h
@@ -34,7 +34,7 @@
 #ifndef RECVFILE_H
 #define RECVFILE_H
 
-void tftp_recvfile(int, const char *, const char *);
-void tftp_sendfile(int, const char *, const char *);
+void tftp_recvfile(int, const char *, const char *, int);
+void tftp_sendfile(int, const char *, const char *, int);
 
 #endif
diff --git a/tftp/main.c b/tftp/main.c
index fcf5a25..5a7d3a6 100644
--- a/tftp/main.c
+++ b/tftp/main.c
@@ -49,7 +49,7 @@
 
 #include "extern.h"
 
-#define	TIMEOUT		5       /* secs between rexmt's */
+#define	RTIMEOUT	5       /* secs between rexmt's */
 #define	LBUFLEN		200     /* size of input buffer */
 
 struct modes {
@@ -82,7 +82,7 @@ int ai_fam_sock = AF_INET;
 union sock_addr peeraddr;
 int f = -1;
 u_short port;
-int trace;
+int trace_opt;
 int verbose;
 int literal;
 int connected;
@@ -104,6 +104,7 @@ struct servent *sp;
 int portrange = 0;
 unsigned int portrange_from = 0;
 unsigned int portrange_to = 0;
+int windowsize = -1;
 
 void get(int, char **);
 void help(int, char **);
@@ -191,14 +192,14 @@ char *xstrdup(const char *);
 
 const char *program;
 
-static inline void usage(int errcode)
+static void usage(int errcode)
 {
     fprintf(stderr,
 #ifdef HAVE_IPV6
-            "Usage: %s [-4][-6][-v][-V][-l][-m mode] [-R port:port] "
+            "Usage: %s [-4][-6][-v][-V][-l][-m mode][-w size] [-R port:port] "
 			"[host [port]] [-c command]\n",
 #else
-            "Usage: %s [-v][-V][-l][-m mode] [-R port:port] "
+            "Usage: %s [-v][-V][-l][-m mode][-w size] [-R port:port] "
 			"[host [port]] [-c command]\n",
 #endif
             program);
@@ -279,6 +280,15 @@ int main(int argc, char *argv[])
                     }
                     portrange = 1;
                     break;
+                case 'w':
+                    if (++arg >= argc)
+                        usage(EX_USAGE);
+                    windowsize = atoi(argv[arg]);
+                    if (windowsize <= 0 || windowsize > 64) {
+                        fprintf(stderr, "Bad window size: %s (1-64)\n", argv[arg]);
+                        exit(EX_USAGE);
+                    }
+                    break;
                 case 'h':
                 default:
                     usage(*optx == 'h' ? 0 : EX_USAGE);
@@ -593,7 +603,7 @@ void put(int argc, char *argv[])
             printf("putting %s to %s:%s [%s]\n",
                    cp, hostname, targ, mode->m_mode);
         sa_set_port(&peeraddr, port);
-        tftp_sendfile(fd, targ, mode->m_mode);
+        tftp_sendfile(fd, targ, mode->m_mode, windowsize);
         return;
     }
     /* this assumes the target is a directory */
@@ -625,7 +635,7 @@ void put(int argc, char *argv[])
             printf("putting %s to %s:%s [%s]\n",
                    argv[n], hostname, remote_pth, mode->m_mode);
         sa_set_port(&peeraddr, port);
-        tftp_sendfile(fd, remote_pth, mode->m_mode);
+        tftp_sendfile(fd, remote_pth, mode->m_mode, windowsize);
     }
 }
 
@@ -693,7 +703,7 @@ void get(int argc, char *argv[])
                 printf("getting from %s:%s to %s [%s]\n",
                        hostname, src, cp, mode->m_mode);
             sa_set_port(&peeraddr, port);
-            tftp_recvfile(fd, src, mode->m_mode);
+            tftp_recvfile(fd, src, mode->m_mode, windowsize);
             break;
         }
         cp = tail(src);         /* new .. jdg */
@@ -708,7 +718,7 @@ void get(int argc, char *argv[])
             printf("getting from %s:%s to %s [%s]\n",
                    hostname, src, cp, mode->m_mode);
         sa_set_port(&peeraddr, port);
-        tftp_recvfile(fd, src, mode->m_mode);
+        tftp_recvfile(fd, src, mode->m_mode, windowsize);
     }
 }
 
@@ -718,7 +728,7 @@ static void getusage(char *s)
     printf("       %s file file ... file if connected\n", s);
 }
 
-int rexmtval = TIMEOUT;
+int rexmtval = RTIMEOUT;
 
 void setrexmt(int argc, char *argv[])
 {
@@ -741,7 +751,7 @@ void setrexmt(int argc, char *argv[])
         rexmtval = t;
 }
 
-int maxtimeout = 5 * TIMEOUT;
+int maxtimeout = 5 * RTIMEOUT;
 
 void settimeout(int argc, char *argv[])
 {
@@ -781,7 +791,7 @@ void status(int argc, char *argv[])
     else
         printf("Not connected.\n");
     printf("Mode: %s Verbose: %s Tracing: %s Literal: %s\n", mode->m_mode,
-           verbose ? "on" : "off", trace ? "on" : "off",
+           verbose ? "on" : "off", trace_opt ? "on" : "off",
            literal ? "on" : "off");
     printf("Rexmt-interval: %d seconds, Max-timeout: %d seconds\n",
            rexmtval, maxtimeout);
@@ -969,8 +979,8 @@ void settrace(int argc, char *argv[])
     (void)argc;
     (void)argv;                 /* Quiet unused warning */
 
-    trace = !trace;
-    printf("Packet tracing %s.\n", trace ? "on" : "off");
+    trace_opt = !trace_opt;
+    printf("Packet tracing %s.\n", trace_opt ? "on" : "off");
 }
 
 void setverbose(int argc, char *argv[])
diff --git a/tftp/tftp.1.in b/tftp/tftp.1.in
index b41f7b5..1779a43 100644
--- a/tftp/tftp.1.in
+++ b/tftp/tftp.1.in
@@ -82,6 +82,9 @@ Default to verbose mode.
 .B \-V
 Print the version number and configuration to standard output, then
 exit gracefully.
+.TP
+.B \-w\fP \fIwindow-size\fP
+Set the "windowsize" TFTP option (RFC 7440) to the specified value.
 .SH COMMANDS
 Once
 .B tftp
diff --git a/tftp/tftp.c b/tftp/tftp.c
index 9d15022..58ea597 100644
--- a/tftp/tftp.c
+++ b/tftp/tftp.c
@@ -31,355 +31,307 @@
  * SUCH DAMAGE.
  */
 
+#include <poll.h>
+#include <stdarg.h>
 #include "common/tftpsubs.h"
-
-/*
- * TFTP User Program -- Protocol Machines
- */
 #include "extern.h"
 
+/* TODO: This 'peeraddr' global should be removed. */
 extern union sock_addr peeraddr; /* filled in by main */
 extern int f;                    /* the opened socket */
-extern int trace;
+extern int trace_opt;
 extern int verbose;
-extern int rexmtval;
-extern int maxtimeout;
-
+/* TODO: Adjust when blocksize is implemented. */
 #define PKTSIZE    SEGSIZE+4
-char ackbuf[PKTSIZE];
-int timeout;
-sigjmp_buf toplevel;
-sigjmp_buf timeoutbuf;
+static char pktbuf[PKTSIZE];
 
-static void nak(int, const char *);
-static int makerequest(int, const char *, struct tftphdr *, const char *);
 static void printstats(const char *, unsigned long);
 static void startclock(void);
 static void stopclock(void);
-static void timer(int);
-static void tpacket(const char *, struct tftphdr *, int);
+static void timed_out(void)
+{
+    printf("client: timed out");
+    exit(1);
+}
+
+static void die(const char *fmt, ...)
+{
+    va_list ap;
+
+    va_start(ap, fmt);
+    fprintf(stderr, "fatal: client: ");
+    vfprintf(stderr, fmt, ap);
+    printf("\n");
+    va_end(ap);
+    exit(1);
+}
+
+static size_t make_request(unsigned short opcode,
+                           const char *name,
+                           const char *mode,
+                           int blocksize,
+                           int windowsize,
+                           struct tftphdr *out)
+{
+    char *cp, buf[16];
+    size_t len;
+
+    cp = (char *)&(out->th_stuff);
+
+    out->th_opcode = htons(opcode);
+
+    len = strlen(name) + 1;
+    memcpy(cp, name, len);
+    cp += len;
+
+    len = strlen(mode) + 1;
+    memcpy(cp, mode, len);
+    cp += len;
+
+    /* Don't include options with default values. */
+
+    /* TODO: TBI in a separate patch. */
+    (void) blocksize;
+    /*if (blocksize != SEGSIZE) {
+        len = strlen("blksize") + 1;
+        memcpy(cp, "blksize", len);
+        cp += len;
+        if (snprintf(buf, 16, "%u", blocksize) < 0)
+            die("out of memory");
+        len = strlen(buf) + 1;
+        memcpy(cp, buf, len);
+        cp += len;
+    }*/
+
+    if (windowsize > 0) {
+        len = strlen("windowsize") + 1;
+        memcpy(cp, "windowsize", len);
+        cp += len;
+        if (snprintf(buf, 16, "%u", windowsize) < 0)
+            die("out of memory");
+        len = strlen(buf) + 1;
+        memcpy(cp, buf, len);
+        cp += len;
+    }
+
+    return (cp - (char *)out);
+}
+
+static void send_request(int sock,
+                         union sock_addr *to,
+                         short request,
+                         const char *name,
+                         const char *mode,
+                         unsigned blocksize,
+                         unsigned windowsize)
+{
+    struct tftphdr *out;
+    size_t size;
+
+    out = (struct tftphdr *)pktbuf;
+    size = make_request(request, name, mode, blocksize, windowsize, out);
+
+    if (sendto(sock, out, size, 0, &to->sa, SOCKLEN(to)) != (unsigned)size)
+        die("send_request: sendto: %m");
+}
+
+static int wait_for_oack(int sock, union sock_addr *from, char **options, int *optlen)
+{
+    unsigned short in_opcode;
+    struct tftphdr *in;
+    int r;
+
+    r = recvfrom_with_timeout(sock, pktbuf, sizeof(pktbuf), from, TIMEOUT);
+    if (r == 0)
+        return r;
+
+    in = (struct tftphdr *)pktbuf;
+    in_opcode = ntohs(in->th_opcode);
+
+    if (in_opcode == ERROR)
+        die_on_error(in);
+    if (in_opcode != OACK)
+        return -in_opcode;
+
+    *options = pktbuf + 2;
+    *optlen = r - 2;
+
+    return 1;
+}
 
 /*
  * Send the requested file.
  */
-void tftp_sendfile(int fd, const char *name, const char *mode)
+void tftp_sendfile(int fd, const char *name, const char *mode, int windowsize)
 {
-    struct tftphdr *ap;         /* data and ack packets */
-    struct tftphdr *dp;
-    int n;
-    volatile int is_request;
-    volatile u_short block;
-    volatile int size, convert;
-    volatile off_t amount;
-    union sock_addr from;
-    socklen_t fromlen;
-    FILE *file;
-    u_short ap_opcode, ap_block;
-
-    startclock();               /* start stat's clock */
-    dp = r_init();              /* reset fillbuf/read-ahead code */
-    ap = (struct tftphdr *)ackbuf;
-    convert = !strcmp(mode, "netascii");
-    file = fdopen(fd, convert ? "rt" : "rb");
-    block = 0;
-    is_request = 1;             /* First packet is the actual WRQ */
-    amount = 0;
-
-    bsd_signal(SIGALRM, timer);
+    union sock_addr server = peeraddr;
+    unsigned long amount = 0;
+    int blocksize = SEGSIZE;
+    char *options;
+    int optlen;
+    int retries;
+    FILE *fp;
+    int n, r;
+
+    set_verbose(trace_opt + verbose);
+
+    startclock();
+    send_request(f, &server, WRQ, name, mode, blocksize, windowsize);
+
+    /* If no windowsize was specified on the command line,
+     * don't bother with options.
+     * When blocksize is supported, this should actually only be called
+     * if no options were sent in the RRQ.
+     */
+    if (windowsize < 0)
+        goto no_options;
+    retries = RETRIES;
     do {
-        if (is_request) {
-            size = makerequest(WRQ, name, dp, mode) - 4;
-        } else {
-            /*      size = read(fd, dp->th_data, SEGSIZE);   */
-            size = readit(file, &dp, convert);
-            if (size < 0) {
-                nak(errno + 100, NULL);
-                break;
-            }
-            dp->th_opcode = htons((u_short) DATA);
-            dp->th_block = htons((u_short) block);
+        r = wait_for_oack(f, &server, &options, &optlen);
+        if (r < 0) {
+            return;
         }
-        timeout = 0;
-        (void)sigsetjmp(timeoutbuf, 1);
-
-        if (trace)
-            tpacket("sent", dp, size + 4);
-        n = sendto(f, dp, size + 4, 0,
-                   &peeraddr.sa, SOCKLEN(&peeraddr));
-        if (n != size + 4) {
-            perror("tftp: sendto");
-            goto abort;
-        }
-        read_ahead(file, convert);
-        for (;;) {
-            alarm(rexmtval);
-            do {
-                fromlen = sizeof(from);
-                n = recvfrom(f, ackbuf, sizeof(ackbuf), 0,
-                             &from.sa, &fromlen);
-            } while (n <= 0);
-            alarm(0);
-            if (n < 0) {
-                perror("tftp: recvfrom");
-                goto abort;
-            }
-            sa_set_port(&peeraddr, SOCKPORT(&from));  /* added */
-            if (trace)
-                tpacket("received", ap, n);
-            /* should verify packet came from server */
-            ap_opcode = ntohs((u_short) ap->th_opcode);
-            ap_block = ntohs((u_short) ap->th_block);
-            if (ap_opcode == ERROR) {
-                printf("Error code %d: %s\n", ap_block, ap->th_msg);
-                goto abort;
-            }
-            if (ap_opcode == ACK) {
-                int j;
 
-                if (ap_block == block) {
-                    break;
+        /* Parse returned options. */
+        n = 0;
+        if (r != 0) {
+            char *opt, *val;
+            int got_ws = 0;
+            int v;
+
+            while (n < optlen) {
+                opt = options + n;
+                n += strlen(opt) + 1;
+                val = options + n;
+                if (str_equal(opt, "windowsize") && windowsize != 1) {
+                    v = atoi(val);
+                    if (v != windowsize)
+                        printf("client: server negotiated different windowsize: %d", v);
+                    /* Assumes v > 0, it probably shouldn't. */
+                    windowsize = v;
+                    got_ws = 1;
                 }
-                /* On an error, try to synchronize
-                 * both sides.
-                 */
-                j = synchnet(f);
-                if (j && trace) {
-                    printf("discarded %d packets\n", j);
-                }
-                /*
-                 * RFC1129/RFC1350: We MUST NOT re-send the DATA
-                 * packet in response to an invalid ACK.  Doing so
-                 * would cause the Sorcerer's Apprentice bug.
-                 */
+                n += strlen(val) + 1;
+            }
+
+            if (got_ws == 0 && windowsize != 1) {
+                windowsize = 1;
+                printf("client: server didn't negotiate windowsize, continuing with windowsize=1");
             }
         }
-        if (!is_request)
-            amount += size;
-        is_request = 0;
-        block++;
-    } while (size == SEGSIZE || block == 1);
-  abort:
-    fclose(file);
+    } while (r == 0 && --retries > 0);
+    if (retries <= 0)
+        timed_out();
+
+no_options:
+    if (windowsize < 0) {
+        struct tftphdr *tp = (struct tftphdr *)pktbuf;
+
+        retries = RETRIES;
+        do {
+            r = recvfrom_with_timeout(f, pktbuf, PKTSIZE, &server, TIMEOUT);
+            if (r == 0) {
+                /* Timed out. */
+                continue;
+            }
+            if (ntohs(tp->th_opcode) == ACK && ntohs(tp->th_block) == 0) {
+                break;
+            } else if (ntohs(tp->th_opcode) == ERROR) {
+                die_on_error(tp);
+            }
+        } while (r == 0 && --retries > 0);
+        if (retries <= 0)
+            timed_out();
+    }
+    fp = fdopen(fd, "r");
+    r = sender(f, &server, blocksize, windowsize, TIMEOUT, 0, fp, &amount);
+    if (r < 0)
+        exit(1);
+
     stopclock();
     if (amount > 0)
         printstats("Sent", amount);
 }
 
+
 /*
  * Receive a file.
  */
-void tftp_recvfile(int fd, const char *name, const char *mode)
+void tftp_recvfile(int fd, const char *name, const char *mode, int windowsize)
 {
-    struct tftphdr *ap;
-    struct tftphdr *dp;
-    int n;
-    volatile u_short block;
-    volatile int size, firsttrip;
-    volatile unsigned long amount;
-    union sock_addr from;
-    socklen_t fromlen;
-    FILE *file;
-    volatile int convert;       /* true if converting crlf -> lf */
-    u_short dp_opcode, dp_block;
+    union sock_addr server = peeraddr;
+    unsigned long amount = 0;
+    int blocksize = SEGSIZE;
+    char *options, error[ERROR_MAXLEN];
+    int optlen;
+    int retries;
+    FILE *fp;
+    int n, r;
+
+    set_verbose(trace_opt + verbose);
 
     startclock();
-    dp = w_init();
-    ap = (struct tftphdr *)ackbuf;
-    convert = !strcmp(mode, "netascii");
-    file = fdopen(fd, convert ? "wt" : "wb");
-    block = 1;
-    firsttrip = 1;
-    amount = 0;
-
-    bsd_signal(SIGALRM, timer);
+
+    send_request(f, &server, RRQ, name, mode, blocksize, windowsize);
+    /* If no windowsize was specified on the command line,
+     * don't bother with options.
+     * When blocksize is supported, this should actually only be called
+     * if no options were sent in the RRQ.
+     */
+    if (windowsize < 0)
+        goto no_options;
+    retries = RETRIES;
     do {
-        if (firsttrip) {
-            size = makerequest(RRQ, name, ap, mode);
-            firsttrip = 0;
-        } else {
-            ap->th_opcode = htons((u_short) ACK);
-            ap->th_block = htons((u_short) block);
-            size = 4;
-            block++;
-        }
-        timeout = 0;
-        (void)sigsetjmp(timeoutbuf, 1);
-      send_ack:
-        if (trace)
-            tpacket("sent", ap, size);
-        if (sendto(f, ackbuf, size, 0, &peeraddr.sa,
-                   SOCKLEN(&peeraddr)) != size) {
-            alarm(0);
-            perror("tftp: sendto");
-            goto abort;
+        r = wait_for_oack(f, &server, &options, &optlen);
+        if (r < 0) {
+            return;
         }
-        write_behind(file, convert);
-        for (;;) {
-            alarm(rexmtval);
-            do {
-                fromlen = sizeof(from);
-                n = recvfrom(f, dp, PKTSIZE, 0,
-                             &from.sa, &fromlen);
-            } while (n <= 0);
-            alarm(0);
-            if (n < 0) {
-                perror("tftp: recvfrom");
-                goto abort;
-            }
-            sa_set_port(&peeraddr, SOCKPORT(&from));  /* added */
-            if (trace)
-                tpacket("received", dp, n);
-            /* should verify client address */
-            dp_opcode = ntohs((u_short) dp->th_opcode);
-            dp_block = ntohs((u_short) dp->th_block);
-            if (dp_opcode == ERROR) {
-                printf("Error code %d: %s\n", dp_block, dp->th_msg);
-                goto abort;
-            }
-            if (dp_opcode == DATA) {
-                int j;
 
-                if (dp_block == block) {
-                    break;      /* have next packet */
-                }
-                /* On an error, try to synchronize
-                 * both sides.
-                 */
-                j = synchnet(f);
-                if (j && trace) {
-                    printf("discarded %d packets\n", j);
-                }
-                if (dp_block == (block - 1)) {
-                    goto send_ack;      /* resend ack */
+        /* Parse returned options. */
+        n = 0;
+        if (r != 0) {
+            char *opt, *val;
+            int got_ws = 0;
+            int v;
+
+            while (n < optlen) {
+                opt = options + n;
+                n += strlen(opt) + 1;
+                val = options + n;
+                if (str_equal(opt, "windowsize") && windowsize != 1) {
+                    v = atoi(val);
+                    if (v != windowsize)
+                        printf("client: server negotiated different windowsize: %d", v);
+                    /* Assumes v > 0, it probably shouldn't. */
+                    windowsize = v;
+                    got_ws = 1;
                 }
+                n += strlen(val) + 1;
+            }
+
+            if (got_ws == 0 && windowsize != 1) {
+                windowsize = 1;
+                printf("client: server didn't negotiate windowsize, continuing with windowsize=1");
             }
         }
-        /*      size = write(fd, dp->th_data, n - 4); */
-        size = writeit(file, &dp, n - 4, convert);
-        if (size < 0) {
-            nak(errno + 100, NULL);
-            break;
-        }
-        amount += size;
-    } while (size == SEGSIZE);
-  abort:                       /* ok to ack, since user */
-    ap->th_opcode = htons((u_short) ACK);       /* has seen err msg */
-    ap->th_block = htons((u_short) block);
-    (void)sendto(f, ackbuf, 4, 0, (struct sockaddr *)&peeraddr,
-                 SOCKLEN(&peeraddr));
-    write_behind(file, convert);        /* flush last buffer */
-    fclose(file);
+    } while (r == 0 && --retries > 0);
+    if (retries <= 0)
+        timed_out();
+
+    send_ack(f, &server, 0);
+
+no_options:
+    fp = fdopen(fd, "w");
+    r = receiver(f, &server, blocksize, windowsize, TIMEOUT, fp, &amount, error);
+    if (r < 0) {
+        fprintf(stderr, "%s\n", error);
+        exit(1);
+    }
+
     stopclock();
     if (amount > 0)
         printstats("Received", amount);
-}
-
-static int
-makerequest(int request, const char *name,
-            struct tftphdr *tp, const char *mode)
-{
-    char *cp;
-    size_t len;
-
-    tp->th_opcode = htons((u_short) request);
-    cp = (char *)&(tp->th_stuff);
-    len = strlen(name) + 1;
-    memcpy(cp, name, len);
-    cp += len;
-    len = strlen(mode) + 1;
-    memcpy(cp, mode, len);
-    cp += len;
-    return (cp - (char *)tp);
-}
-
-static const char *const errmsgs[] = {
-    "Undefined error code",     /* 0 - EUNDEF */
-    "File not found",           /* 1 - ENOTFOUND */
-    "Access denied",            /* 2 - EACCESS */
-    "Disk full or allocation exceeded", /* 3 - ENOSPACE */
-    "Illegal TFTP operation",   /* 4 - EBADOP */
-    "Unknown transfer ID",      /* 5 - EBADID */
-    "File already exists",      /* 6 - EEXISTS */
-    "No such user",             /* 7 - ENOUSER */
-    "Failure to negotiate RFC2347 options"      /* 8 - EOPTNEG */
-};
-
-#define ERR_CNT (sizeof(errmsgs)/sizeof(const char *))
-
-/*
- * Send a nak packet (error message).
- * Error code passed in is one of the
- * standard TFTP codes, or a UNIX errno
- * offset by 100.
- */
-static void nak(int error, const char *msg)
-{
-    struct tftphdr *tp;
-    int length;
-
-    tp = (struct tftphdr *)ackbuf;
-    tp->th_opcode = htons((u_short) ERROR);
-    tp->th_code = htons((u_short) error);
-
-    if (error >= 100) {
-        /* This is a Unix errno+100 */
-        if (!msg)
-            msg = strerror(error - 100);
-        error = EUNDEF;
-    } else {
-        if ((unsigned)error >= ERR_CNT)
-            error = EUNDEF;
-
-        if (!msg)
-            msg = errmsgs[error];
-    }
-
-    tp->th_code = htons((u_short) error);
-
-    length = strlen(msg) + 1;
-    memcpy(tp->th_msg, msg, length);
-    length += 4;                /* Add space for header */
-
-    if (trace)
-        tpacket("sent", tp, length);
-    if (sendto(f, ackbuf, length, 0, &peeraddr.sa,
-               SOCKLEN(&peeraddr)) != length)
-        perror("nak");
-}
-
-static void tpacket(const char *s, struct tftphdr *tp, int n)
-{
-    static const char *opcodes[] =
-        { "#0", "RRQ", "WRQ", "DATA", "ACK", "ERROR", "OACK" };
-    char *cp, *file;
-    u_short op = ntohs((u_short) tp->th_opcode);
-
-    if (op < RRQ || op > ERROR)
-        printf("%s opcode=%x ", s, op);
-    else
-        printf("%s %s ", s, opcodes[op]);
-    switch (op) {
-
-    case RRQ:
-    case WRQ:
-        n -= 2;
-        file = cp = (char *)&(tp->th_stuff);
-        cp = strchr(cp, '\0');
-        printf("<file=%s, mode=%s>\n", file, cp + 1);
-        break;
-
-    case DATA:
-        printf("<block=%d, %d bytes>\n", ntohs(tp->th_block), n - 4);
-        break;
-
-    case ACK:
-        printf("<block=%d>\n", ntohs(tp->th_block));
-        break;
-
-    case ERROR:
-        printf("<code=%d, msg=%s>\n", ntohs(tp->th_code), tp->th_msg);
-        break;
-    }
+    fclose(fp);
 }
 
 struct timeval tstart;
@@ -404,23 +356,9 @@ static void printstats(const char *direction, unsigned long amount)
         (tstart.tv_sec + (tstart.tv_usec / 1000000.0));
     if (verbose) {
         printf("%s %lu bytes in %.1f seconds", direction, amount, delta);
+        /* TODO: Change the statistics in a separate patch (bits???)! */
         printf(" [%.0f bit/s]", (amount * 8.) / delta);
         putchar('\n');
     }
 }
 
-static void timer(int sig)
-{
-    int save_errno = errno;
-
-    (void)sig;                  /* Shut up unused warning */
-
-    timeout += rexmtval;
-    if (timeout >= maxtimeout) {
-        printf("Transfer timed out.\n");
-        errno = save_errno;
-        siglongjmp(toplevel, -1);
-    }
-    errno = save_errno;
-    siglongjmp(timeoutbuf, 1);
-}
diff --git a/tftpd/tftpd.8.in b/tftpd/tftpd.8.in
index 321b8c6..46384d6 100644
--- a/tftpd/tftpd.8.in
+++ b/tftpd/tftpd.8.in
@@ -227,6 +227,11 @@ Set the time before the server retransmits a packet, in microseconds.
 \fBrollover\fP (nonstandard)
 Set the block number to resume at after a block number rollover.  The
 default and recommended value is zero.
+.TP
+\fBwindowsize\fP (RFC 7440)
+Set the windowsize to a number of blocks that should be sent before
+expecting an ack. The default is 1, which means the same functionality
+as if windowsize wasn't used. Maximum is 64.
 .PP
 The
 .B \-\-refuse
@@ -408,6 +413,9 @@ RFC 2348,
 .br
 RFC 2349,
 .IR "TFTP Timeout Interval and Transfer Size Options" .
+.br
+RFC 7440,
+.IR "TFTP Windowsize Option" .
 .SH "AUTHOR"
 This version of
 .B tftpd
diff --git a/tftpd/tftpd.c b/tftpd/tftpd.c
index b7a5a95..98adbc1 100644
--- a/tftpd/tftpd.c
+++ b/tftpd/tftpd.c
@@ -48,6 +48,8 @@
 #include <pwd.h>
 #include <limits.h>
 #include <syslog.h>
+#include <poll.h>
+#include <stdarg.h>
 
 #include "common/tftpsubs.h"
 #include "recvfrom.h"
@@ -72,23 +74,16 @@ static int ai_fam = AF_UNSPEC;
 static int ai_fam = AF_INET;
 #endif
 
-#define	TIMEOUT 1000000         /* Default timeout (us) */
-#define TRIES   6               /* Number of attempts to send each packet */
-#define TIMEOUT_LIMIT ((1 << TRIES)-1)
-
 const char *__progname;
 static int peer;
-static unsigned long timeout  = TIMEOUT;        /* Current timeout value */
-static unsigned long rexmtval = TIMEOUT;       /* Basic timeout value */
-static unsigned long maxtimeout = TIMEOUT_LIMIT * TIMEOUT;
-static int timeout_quit = 0;
-static sigjmp_buf timeoutbuf;
 static uint16_t rollover_val = 0;
+static int windowsize = 1;
 
 #define	PKTSIZE	MAX_SEGSIZE+4
 static char buf[PKTSIZE];
-static char ackbuf[PKTSIZE];
+static char pktbuf[PKTSIZE];
 static unsigned int max_blksize = MAX_SEGSIZE;
+#define MAX_WINDOWSIZE 64
 
 static char tmpbuf[INET6_ADDRSTRLEN], *tmp_p;
 
@@ -113,8 +108,7 @@ static struct rule *rewrite_rules = NULL;
 #endif
 
 int tftp(struct tftphdr *, int);
-static void nak(int, const char *);
-static void timer(int);
+static void nak(int error, const char *msg);
 static void do_opt(const char *, const char *, char **);
 
 static int set_blksize(uintmax_t *);
@@ -123,6 +117,9 @@ static int set_tsize(uintmax_t *);
 static int set_timeout(uintmax_t *);
 static int set_utimeout(uintmax_t *);
 static int set_rollover(uintmax_t *);
+static int set_windowsize(uintmax_t *);
+
+int g_timeout = 1000; /* ms */
 
 struct options {
     const char *o_opt;
@@ -134,6 +131,7 @@ struct options {
     {"timeout",  set_timeout},
     {"utimeout", set_utimeout},
     {"rollover", set_rollover},
+    {"windowsize", set_windowsize},
     {NULL, NULL}
 };
 
@@ -152,16 +150,6 @@ static void handle_exit(int sig)
     exit_signal = sig;
 }
 
-/* Handle timeout signal or timeout event */
-void timer(int sig)
-{
-    (void)sig;                  /* Suppress unused warning */
-    timeout <<= 1;
-    if (timeout >= maxtimeout || timeout_quit)
-        exit(0);
-    siglongjmp(timeoutbuf, 1);
-}
-
 #ifdef WITH_REGEX
 static struct rule *read_remap_rules(const char *file)
 {
@@ -229,64 +217,6 @@ static void pmtu_discovery_off(int fd)
 #endif
 }
 
-/*
- * Receive packet with synchronous timeout; timeout is adjusted
- * to account for time spent waiting.
- */
-static int recv_time(int s, void *rbuf, int len, unsigned int flags,
-                     unsigned long *timeout_us_p)
-{
-    fd_set fdset;
-    struct timeval tmv, t0, t1;
-    int rv, err;
-    unsigned long timeout_us = *timeout_us_p;
-    unsigned long timeout_left, dt;
-
-    gettimeofday(&t0, NULL);
-    timeout_left = timeout_us;
-
-    for (;;) {
-        FD_ZERO(&fdset);
-        FD_SET(s, &fdset);
-
-        do {
-            tmv.tv_sec = timeout_left / 1000000;
-            tmv.tv_usec = timeout_left % 1000000;
-
-            rv = select(s + 1, &fdset, NULL, NULL, &tmv);
-            err = errno;
-
-            gettimeofday(&t1, NULL);
-
-            dt = (t1.tv_sec - t0.tv_sec) * 1000000 +
-		 (t1.tv_usec - t0.tv_usec);
-            *timeout_us_p = timeout_left =
-                (dt >= timeout_us) ? 1 : (timeout_us - dt);
-        } while (rv == -1 && err == EINTR);
-
-        if (rv == 0) {
-            timer(0);           /* Should not return */
-            return -1;
-        }
-
-        set_socket_nonblock(s, 1);
-        rv = recv(s, rbuf, len, flags);
-        err = errno;
-        set_socket_nonblock(s, 0);
-
-        if (rv < 0) {
-            if (E_WOULD_BLOCK(err) || err == EINTR) {
-                continue;       /* Once again, with feeling... */
-            } else {
-                errno = err;
-                return rv;
-            }
-        } else {
-            return rv;
-        }
-    }
-}
-
 static int split_port(char **ap, char **pp)
 {
     char *a, *p;
@@ -325,7 +255,7 @@ static int split_port(char **ap, char **pp)
 enum long_only_options {
     OPT_VERBOSITY	= 256,
 };
-    
+
 static struct option long_options[] = {
     { "ipv4",        0, NULL, '4' },
     { "ipv6",        0, NULL, '6' },
@@ -389,7 +319,7 @@ int main(int argc, char **argv)
     char envtz[10];
     my_time = time(NULL);
     p_tm = localtime(&my_time);
-    snprintf(envtz, sizeof(envtz) - 1, "UTC%+d", (p_tm->tm_gmtoff * -1)/3600);
+    snprintf(envtz, sizeof(envtz) - 1, "UTC%+ld", (p_tm->tm_gmtoff * -1)/3600);
     setenv("TZ", envtz, 0);
 
     /* basename() is way too much of a pain from a portability standpoint */
@@ -455,8 +385,7 @@ int main(int argc, char **argv)
                     syslog(LOG_ERR, "Bad timeout value: %s", optarg);
                     exit(EX_USAGE);
                 }
-                rexmtval = timeout = tov;
-                maxtimeout = rexmtval * TIMEOUT_LIMIT;
+                g_timeout = tov;
             }
             break;
         case 'R':
@@ -1090,9 +1019,9 @@ int tftp(struct tftphdr *tp, int size)
     u_short tp_opcode = ntohs(tp->th_opcode);
 
     char *val = NULL, *opt = NULL;
-    char *ap = ackbuf + 2;
+    char *ap = pktbuf + 2;
 
-    ((struct tftphdr *)ackbuf)->th_opcode = htons(OACK);
+    ((struct tftphdr *)pktbuf)->th_opcode = htons(OACK);
 
     origfilename = cp = (char *)&(tp->th_stuff);
     argn = 0;
@@ -1176,11 +1105,11 @@ int tftp(struct tftphdr *tp, int size)
         exit(0);
     }
 
-    if (ap != (ackbuf + 2)) {
+    if (ap != (pktbuf + 2)) {
         if (tp_opcode == WRQ)
-            (*pf->f_recv) (pf, (struct tftphdr *)ackbuf, ap - ackbuf);
+            (*pf->f_recv) (pf, (struct tftphdr *)pktbuf, ap - pktbuf);
         else
-            (*pf->f_send) (pf, (struct tftphdr *)ackbuf, ap - ackbuf, origfilename);
+            (*pf->f_send) (pf, (struct tftphdr *)pktbuf, ap - pktbuf, origfilename);
     } else {
         if (tp_opcode == WRQ)
             (*pf->f_recv) (pf, NULL, 0);
@@ -1248,7 +1177,7 @@ static int set_blksize2(uintmax_t *vp)
 static int set_rollover(uintmax_t *vp)
 {
     uintmax_t ro = *vp;
-    
+
     if (ro > 65535)
 	return 0;
 
@@ -1287,8 +1216,7 @@ static int set_timeout(uintmax_t *vp)
     if (to < 1 || to > 255)
         return 0;
 
-    rexmtval = timeout = to * 1000000UL;
-    maxtimeout = rexmtval * TIMEOUT_LIMIT;
+    g_timeout = to * 1000;
 
     return 1;
 }
@@ -1301,8 +1229,20 @@ static int set_utimeout(uintmax_t *vp)
     if (to < 10000UL || to > 255000000UL)
         return 0;
 
-    rexmtval = timeout = to;
-    maxtimeout = rexmtval * TIMEOUT_LIMIT;
+    g_timeout = to / 1000;
+
+    return 1;
+}
+
+/*
+ * Set window size (c.f. RFC7440)
+ */
+static int set_windowsize(uintmax_t *vp)
+{
+    if (*vp < 1 || *vp > MAX_WINDOWSIZE)
+        return 0;
+
+    windowsize = *vp;
 
     return 1;
 }
@@ -1343,11 +1283,11 @@ static void do_opt(const char *opt, const char *val, char **ap)
 		optlen = strlen(opt);
 		retlen = sprintf(retbuf, "%"PRIuMAX, v);
 
-                if (p + optlen + retlen + 2 >= ackbuf + sizeof(ackbuf)) {
+                if (p + optlen + retlen + 2 >= pktbuf + sizeof(pktbuf)) {
                     nak(EOPTNEG, "Insufficient space for options");
                     exit(0);
                 }
-		
+
 		memcpy(p, opt, optlen+1);
 		p += optlen+1;
 		memcpy(p, retbuf, retlen+1);
@@ -1568,104 +1508,63 @@ static int validate_access(char *filename, int mode,
  */
 static void tftp_sendfile(const struct formats *pf, struct tftphdr *oap, int oacklen, char *filename)
 {
-    struct tftphdr *dp;
-    struct tftphdr *ap;         /* ack packet */
-    static u_short block = 1;   /* Static to avoid longjmp funnies */
-    u_short ap_opcode, ap_block;
-    unsigned long r_timeout;
-    int size, n;
-
+    struct tftphdr *tp = (struct tftphdr *)pktbuf;
+    unsigned short tp_opcode, tp_block;
+    int retries = RETRIES;
+    int timed_out = 0;
+    int n, r = 0;
+    (void) pf;
+
+    set_verbose(verbosity);
     if (oap) {
-        timeout = rexmtval;
-        (void)sigsetjmp(timeoutbuf, 1);
-      oack:
-        r_timeout = timeout;
-        if (send(peer, oap, oacklen, 0) != oacklen) {
-            syslog(LOG_WARNING, "tftpd: oack: %m\n");
-            goto abort;
-        }
-        for (;;) {
-            n = recv_time(peer, ackbuf, sizeof(ackbuf), 0, &r_timeout);
-            if (n < 0) {
-                syslog(LOG_WARNING, "tftpd: read: %m\n");
-                goto abort;
-            }
-            ap = (struct tftphdr *)ackbuf;
-            ap_opcode = ntohs((u_short) ap->th_opcode);
-            ap_block = ntohs((u_short) ap->th_block);
-
-            if (ap_opcode == ERROR) {
-                syslog(LOG_WARNING,
-                       "tftp: client does not accept options\n");
+        do {
+            if (send(peer, oap, oacklen, 0) != oacklen) {
+                syslog(LOG_WARNING, "tftpd: oack: %m\n");
                 goto abort;
             }
-            if (ap_opcode == ACK) {
-                if (ap_block == 0)
-                    break;
-                /* Resynchronize with the other side */
-                (void)synchnet(peer);
-                goto oack;
-            }
-        }
-    }
 
-    dp = r_init();
-    do {
-        size = readit(file, &dp, pf->f_convert);
-        if (size < 0) {
-            nak(errno + 100, NULL);
-            goto abort;
-        }
-        dp->th_opcode = htons((u_short) DATA);
-        dp->th_block = htons((u_short) block);
-        timeout = rexmtval;
-        (void)sigsetjmp(timeoutbuf, 1);
-
-        r_timeout = timeout;
-        if (send(peer, dp, size + 4, 0) != size + 4) {
-            syslog(LOG_WARNING, "tftpd: write: %m");
-            goto abort;
-        }
-        read_ahead(file, pf->f_convert);
-        for (;;) {
-            n = recv_time(peer, ackbuf, sizeof(ackbuf), 0, &r_timeout);
+            n = recv_with_timeout(peer, pktbuf, sizeof(pktbuf), g_timeout);
             if (n < 0) {
-                syslog(LOG_WARNING, "tftpd: read(ack): %m");
+                syslog(LOG_WARNING, "tftpd: recv: %m");
                 goto abort;
+            } else if (n == 0) {
+                if (--retries <= 0) {
+                    timed_out = 1;
+                    goto abort;
+                }
+                continue;
             }
-            ap = (struct tftphdr *)ackbuf;
-            ap_opcode = ntohs((u_short) ap->th_opcode);
-            ap_block = ntohs((u_short) ap->th_block);
 
-            if (ap_opcode == ERROR)
-                goto abort;
+            tp_opcode = ntohs(tp->th_opcode);
+            tp_block  = ntohs(tp->th_block);
 
-            if (ap_opcode == ACK) {
-                if (ap_block == block) {
-                    break;
-                }
-                /* Re-synchronize with the other side */
-                (void)synchnet(peer);
-                /*
-                 * RFC1129/RFC1350: We MUST NOT re-send the DATA
-                 * packet in response to an invalid ACK.  Doing so
-                 * would cause the Sorcerer's Apprentice bug.
-                 */
+            if (tp_opcode == ERROR) {
+                char error[ERROR_MAXLEN];
+
+                format_error(tp, error);
+                syslog(LOG_WARNING, "%s", error);
+                goto abort;
+            } else if (!(tp_opcode == ACK && tp_block == 0)) {
+                syslog(LOG_WARNING, "unexpected packet %s block=%u", opcode_to_str(tp_opcode), tp_block);
+                send_error(peer, NULL, "Unexpected packet");
+                exit(1);
             }
+        } while (n == 0);
+    }
+
+    r = sender(peer, NULL, segsize, windowsize, TIMEOUT, rollover_val, file, NULL);
 
-        }
-	if (!++block)
-	  block = rollover_val;
-    } while (size == segsize);
     tmp_p = (char *)inet_ntop(from.sa.sa_family, SOCKADDR_P(&from),
-                                          tmpbuf, INET6_ADDRSTRLEN);
+                              tmpbuf, INET6_ADDRSTRLEN);
     if (!tmp_p) {
             tmp_p = tmpbuf;
             strcpy(tmpbuf, "???");
     }
-    syslog(LOG_NOTICE, "Client %s finished %s",tmp_p,filename);
-  abort:
-    (void)fclose(file);
+    syslog(LOG_NOTICE, "Client %s finished %s", tmp_p, filename);
+abort:
+    if (timed_out || r == E_TIMED_OUT)
+        syslog(LOG_NOTICE, "Client %s timed out", tmp_p);
+    fclose(file);
 }
 
 /*
@@ -1673,90 +1572,44 @@ static void tftp_sendfile(const struct formats *pf, struct tftphdr *oap, int oac
  */
 static void tftp_recvfile(const struct formats *pf, struct tftphdr *oap, int oacklen)
 {
-    struct tftphdr *dp;
-    int n, size;
-    /* These are "static" to avoid longjmp funnies */
-    static struct tftphdr *ap;  /* ack buffer */
-    static u_short block = 0;
-    static int acksize;
-    u_short dp_opcode, dp_block;
-    unsigned long r_timeout;
-
-    dp = w_init();
-    do {
-        timeout = rexmtval;
-
-        if (!block && oap) {
-            ap = (struct tftphdr *)ackbuf;
-            acksize = oacklen;
-        } else {
-            ap = (struct tftphdr *)ackbuf;
-            ap->th_opcode = htons((u_short) ACK);
-            ap->th_block = htons((u_short) block);
-            acksize = 4;
-            /* If we're sending a regular ACK, that means we have successfully
-             * sent the OACK. Clear oap so that we won't try to send another
-             * OACK when the block number wraps back to 0. */
-            oap = NULL;
-        }
-        if (!++block)
-	  block = rollover_val;
-        (void)sigsetjmp(timeoutbuf, 1);
-      send_ack:
-        r_timeout = timeout;
-        if (send(peer, ackbuf, acksize, 0) != acksize) {
-            syslog(LOG_WARNING, "tftpd: write(ack): %m");
-            goto abort;
-        }
-        write_behind(file, pf->f_convert);
-        for (;;) {
-            n = recv_time(peer, dp, PKTSIZE, 0, &r_timeout);
-            if (n < 0) {        /* really? */
-                syslog(LOG_WARNING, "tftpd: read: %m");
+    int retries = RETRIES;
+    int timed_out = 0;
+    int r;
+    (void) pf;
+
+    set_verbose(verbosity);
+    if (oap) {
+        do {
+            if (send(peer, oap, oacklen, 0) != oacklen) {
+                syslog(LOG_WARNING, "tftpd: oack: %m\n");
                 goto abort;
             }
-            dp_opcode = ntohs((u_short) dp->th_opcode);
-            dp_block = ntohs((u_short) dp->th_block);
-            if (dp_opcode == ERROR)
-                goto abort;
-            if (dp_opcode == DATA) {
-                if (dp_block == block) {
-                    break;      /* normal */
+            r = recvfrom_flags_with_timeout(peer, pktbuf, sizeof(pktbuf), NULL, TIMEOUT, MSG_PEEK);
+            if (r == 0) {
+                if (--retries <= 0) {
+                    timed_out = 1;
+                    goto abort;
                 }
-                /* Re-synchronize with the other side */
-                (void)synchnet(peer);
-                if (dp_block == (block - 1))
-                    goto send_ack;      /* rexmit */
             }
-        }
-        /*  size = write(file, dp->th_data, n - 4); */
-        size = writeit(file, &dp, n - 4, pf->f_convert);
-        if (size != (n - 4)) {  /* ahem */
-            if (size < 0)
-                nak(errno + 100, NULL);
-            else
-                nak(ENOSPACE, NULL);
-            goto abort;
-        }
-    } while (size == segsize);
-    write_behind(file, pf->f_convert);
-    (void)fclose(file);         /* close data file */
-
-    ap->th_opcode = htons((u_short) ACK);       /* send the "final" ack */
-    ap->th_block = htons((u_short) (block));
-    (void)send(peer, ackbuf, 4, 0);
-
-    timeout_quit = 1;           /* just quit on timeout */
-    n = recv_time(peer, buf, sizeof(buf), 0, &timeout); /* normally times out and quits */
-    timeout_quit = 0;
-
-    if (n >= 4 &&               /* if read some data */
-        dp_opcode == DATA &&    /* and got a data block */
-        block == dp_block) {    /* then my last ack was lost */
-        (void)send(peer, ackbuf, 4, 0); /* resend final ack */
+        } while (r == 0);
+    } else {
+        do {
+            send_ack(peer, NULL, 0);
+            r = recvfrom_flags_with_timeout(peer, pktbuf, sizeof(pktbuf), NULL, TIMEOUT, MSG_PEEK);
+            if (r == 0) {
+                if (--retries <= 0) {
+                    timed_out = 1;
+                    goto abort;
+                }
+            }
+        } while (r == 0);
     }
-  abort:
-    return;
+
+    r = receiver(peer, NULL, segsize, windowsize, TIMEOUT, file, NULL, NULL);
+abort:
+    if (timed_out || r == E_TIMED_OUT)
+        syslog(LOG_NOTICE, "Client %s timed out", tmp_p);
+    fclose(file);
 }
 
 static const char *const errmsgs[] = {