aboutsummaryrefslogtreecommitdiff
path: root/libexec/tftpd/tftp-transfer.c
diff options
context:
space:
mode:
Diffstat (limited to 'libexec/tftpd/tftp-transfer.c')
-rw-r--r--libexec/tftpd/tftp-transfer.c199
1 files changed, 151 insertions, 48 deletions
diff --git a/libexec/tftpd/tftp-transfer.c b/libexec/tftpd/tftp-transfer.c
index c2c9e0793354..67b2289305a0 100644
--- a/libexec/tftpd/tftp-transfer.c
+++ b/libexec/tftpd/tftp-transfer.c
@@ -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 *ts)
goto abort;
}
if (rp->th_opcode == ACK) {
- ts->blocks++;
- if (rp->th_block == *block) {
- ts->amount += size;
- break;
+ /*
+ * Look for the ACKed block in our open
+ * window.
+ */
+ for (i = 0; i < windowblock; i++) {
+ if (rp->th_block == window[i].block)
+ break;
}
- /* Re-synchronize with the other side */
- (void) synchnet(peer);
- if (rp->th_block == (*block - 1)) {
+ if (i == windowblock) {
+ /* Did not recognize ACK. */
+ if (debug&DEBUG_SIMPLE)
+ tftp_log(LOG_DEBUG,
+ "ACK %d out of window",
+ rp->th_block);
+
+ /* Re-synchronize with the other side */
+ (void) synchnet(peer);
+
+ /* Resend the current window. */
ts->retries++;
- continue;
+ seek_file(window[0].offset);
+ *block = window[0].block;
+ windowblock = 0;
+ goto read_block;
+ }
+
+ /* ACKed at least some data. */
+ acktry = 0;
+ for (j = 0; j <= i; j++) {
+ if (debug&DEBUG_SIMPLE)
+ tftp_log(LOG_DEBUG,
+ "ACKed block %d",
+ window[j].block);
+ ts->blocks++;
+ ts->amount += window[j].size;
+ }
+
+ /*
+ * Partial ACK. Rewind state to first
+ * un-ACKed block.
+ */
+ if (i + 1 != windowblock) {
+ if (debug&DEBUG_SIMPLE)
+ tftp_log(LOG_DEBUG,
+ "Partial ACK");
+ seek_file(window[i + 1].offset);
+ *block = window[i + 1].block;
+ windowblock = 0;
+ ts->retries++;
+ goto read_block;
}
+
+ windowblock = 0;
}
}
@@ -161,31 +228,35 @@ tftp_receive(int peer, uint16_t *block, struct tftp_stats *ts,
struct tftphdr *firstblock, size_t fb_size)
{
struct tftphdr *rp;
- uint16_t oldblock;
- int n_data, n_ack, writesize, i, retry;
+ uint16_t oldblock, windowstart;
+ int n_data, n_ack, writesize, i, retry, windowblock;
char recvbuffer[MAXPKTSIZE];
ts->amount = 0;
+ windowblock = 0;
if (firstblock != NULL) {
writesize = write_file(firstblock->th_data, fb_size);
ts->amount += writesize;
- for (i = 0; ; i++) {
- n_ack = send_ack(peer, *block);
- if (n_ack > 0) {
- if (i == maxtimeouts) {
+ windowblock++;
+ if (windowsize == 1 || fb_size != segsize) {
+ for (i = 0; ; i++) {
+ n_ack = send_ack(peer, *block);
+ if (n_ack > 0) {
+ if (i == maxtimeouts) {
+ tftp_log(LOG_ERR,
+ "Cannot send ACK packet #%d, "
+ "giving up", *block);
+ return;
+ }
tftp_log(LOG_ERR,
- "Cannot send ACK packet #%d, "
- "giving up", *block);
- return;
+ "Cannot send ACK packet #%d, trying again",
+ *block);
+ continue;
}
- tftp_log(LOG_ERR,
- "Cannot send ACK packet #%d, trying again",
- *block);
- continue;
- }
- break;
+ break;
+ }
}
if (fb_size != segsize) {
@@ -216,7 +287,8 @@ tftp_receive(int peer, uint16_t *block, struct tftp_stats *ts,
for (retry = 0; ; retry++) {
if (debug&DEBUG_SIMPLE)
tftp_log(LOG_DEBUG,
- "Receiving DATA block %d", *block);
+ "Receiving DATA block %d (window block %d)",
+ *block, windowblock);
n_data = receive_packet(peer, recvbuffer,
MAXPKTSIZE, NULL, timeoutpacket);
@@ -232,6 +304,7 @@ tftp_receive(int peer, uint16_t *block, struct tftp_stats *ts,
"Timeout #%d on DATA block %d",
retry, *block);
send_ack(peer, oldblock);
+ windowblock = 0;
continue;
}
@@ -247,18 +320,41 @@ tftp_receive(int peer, uint16_t *block, struct tftp_stats *ts,
if (rp->th_block == *block)
break;
+ /*
+ * Ignore duplicate blocks within the
+ * window.
+ *
+ * This does not handle duplicate
+ * blocks during a rollover as
+ * gracefully, but that should still
+ * recover eventually.
+ */
+ if (*block > windowsize)
+ windowstart = *block - windowsize;
+ else
+ windowstart = 0;
+ if (rp->th_block > windowstart &&
+ rp->th_block < *block) {
+ if (debug&DEBUG_SIMPLE)
+ tftp_log(LOG_DEBUG,
+ "Ignoring duplicate DATA block %d",
+ rp->th_block);
+ windowblock++;
+ retry = 0;
+ continue;
+ }
+
tftp_log(LOG_WARNING,
"Expected DATA block %d, got block %d",
*block, rp->th_block);
/* Re-synchronize with the other side */
(void) synchnet(peer);
- if (rp->th_block == (*block-1)) {
- tftp_log(LOG_INFO, "Trying to sync");
- *block = oldblock;
- ts->retries++;
- goto send_ack; /* rexmit */
- }
+
+ tftp_log(LOG_INFO, "Trying to sync");
+ *block = oldblock;
+ ts->retries++;
+ goto send_ack; /* rexmit */
} else {
tftp_log(LOG_WARNING,
@@ -282,7 +378,11 @@ tftp_receive(int peer, uint16_t *block, struct tftp_stats *ts,
if (n_data != segsize)
write_close();
}
+ windowblock++;
+ /* Only send ACKs for the last block in the window. */
+ if (windowblock < windowsize && n_data == segsize)
+ continue;
send_ack:
for (i = 0; ; i++) {
n_ack = send_ack(peer, *block);
@@ -301,6 +401,9 @@ send_ack:
continue;
}
+ if (debug&DEBUG_SIMPLE)
+ tftp_log(LOG_DEBUG, "Sent ACK for %d", *block);
+ windowblock = 0;
break;
}
gettimeofday(&(ts->tstop), NULL);