diff options
author | Glenn L McGrath <bug1@ihug.co.nz> | 2004-03-05 15:52:57 +0000 |
---|---|---|
committer | Glenn L McGrath <bug1@ihug.co.nz> | 2004-03-05 15:52:57 +0000 |
commit | db6ee81c994c7571a80a5e37c6a331a69e5dccde (patch) | |
tree | 4063bb110b5b3e46bc9ba936c130128aa47fb85d | |
parent | ec58bce3638b73013f7bbdce3047ce0797de079c (diff) | |
download | busybox-db6ee81c994c7571a80a5e37c6a331a69e5dccde.tar.gz |
Patch from John Powers, adds multicast (rfc2090) and timeout (rfc2349)
options
-rw-r--r-- | patches/tftp_timeout_multicast.diff | 1053 |
1 files changed, 1053 insertions, 0 deletions
diff --git a/patches/tftp_timeout_multicast.diff b/patches/tftp_timeout_multicast.diff new file mode 100644 index 000000000..a76a18c61 --- /dev/null +++ b/patches/tftp_timeout_multicast.diff @@ -0,0 +1,1053 @@ +Index: AUTHORS +=================================================================== +RCS file: /var/cvs/busybox/AUTHORS,v +retrieving revision 1.40 +diff -u -r1.40 AUTHORS +--- a/AUTHORS 9 Oct 2003 21:19:21 -0000 1.40 ++++ b/AUTHORS 5 Mar 2004 15:45:47 -0000 +@@ -92,6 +92,9 @@ + Original author of BusyBox in 1995, 1996. Some of his code can + still be found hiding here and there... + ++John Powers <jpp@ti.com> ++ Added multicast option (rfc2090) and timeout option (rfc2349) to tftp. ++ + Tim Riker <Tim@Rikers.org> + bug fixes, member of fan club + +Index: include/usage.h +=================================================================== +RCS file: /var/cvs/busybox/include/usage.h,v +retrieving revision 1.191 +diff -u -r1.191 usage.h +--- a/include/usage.h 25 Feb 2004 10:35:55 -0000 1.191 ++++ b/include/usage.h 5 Mar 2004 15:45:59 -0000 +@@ -2492,6 +2492,21 @@ + #else + #define USAGE_TFTP_BS(a) + #endif ++#ifdef CONFIG_FEATURE_TFTP_TIMEOUT ++ #define USAGE_TFTP_TIMEOUT(a) a ++#else ++ #define USAGE_TFTP_TIMEOUT(a) ++#endif ++#ifdef CONFIG_FEATURE_TFTP_MULTICAST ++ #define USAGE_TFTP_MULTICAST(a) a ++#else ++ #define USAGE_TFTP_MULTICAST(a) ++#endif ++#ifdef CONFIG_FEATURE_TFTP_DEBUG ++ #define USAGE_TFTP_DEBUG(a) a ++#else ++ #define USAGE_TFTP_DEBUG(a) ++#endif + + #define tftp_trivial_usage \ + "[OPTION]... HOST [PORT]" +@@ -2508,6 +2523,16 @@ + ) \ + USAGE_TFTP_BS( \ + "\t-b SIZE\tTransfer blocks of SIZE octets.\n" \ ++ ) \ ++ USAGE_TFTP_TIMEOUT( \ ++ "\t-T SEC\tClient timeout SEC seconds (default: 5).\n" \ ++ "\t-t SEC\tServer timeout SEC seconds\n" \ ++ ) \ ++ USAGE_TFTP_MULTICAST( \ ++ "\t-m\tMulticast get file.\n" \ ++ ) \ ++ USAGE_TFTP_DEBUG( \ ++ "\t-D\tPrint debug messages.\n" \ + ) + #define time_trivial_usage \ + "[OPTION]... COMMAND [ARGS...]" +Index: networking/Config.in +=================================================================== +RCS file: /var/cvs/busybox/networking/Config.in,v +retrieving revision 1.27 +diff -u -r1.27 Config.in +--- a/networking/Config.in 22 Feb 2004 12:25:47 -0000 1.27 ++++ b/networking/Config.in 5 Mar 2004 15:45:59 -0000 +@@ -522,6 +522,13 @@ + Add support for the GET command within the TFTP client. This allows + a client to retrieve a file from a TFTP server. + ++config CONFIG_FEATURE_TFTP_MULTICAST ++ bool " Enable \"multicast\" option" ++ default n ++ depends on CONFIG_FEATURE_TFTP_GET ++ help ++ Allow the client to receive multicast file transfers. ++ + config CONFIG_FEATURE_TFTP_PUT + bool " Enable \"put\" command" + default y +@@ -531,12 +538,19 @@ + a client to transfer a file to a TFTP server. + + config CONFIG_FEATURE_TFTP_BLOCKSIZE +- bool " Enable \"blocksize\" command" ++ bool " Enable \"blksize\" option" + default n + depends on CONFIG_TFTP + help + Allow the client to specify the desired block size for transfers. + ++config CONFIG_FEATURE_TFTP_TIMEOUT ++ bool " Enable \"timeout\" option" ++ default n ++ depends on CONFIG_TFTP ++ help ++ Allow the client to negotiate timeout option with server. ++ + config CONFIG_FEATURE_TFTP_DEBUG + bool " Enable debug" + default n +Index: networking/tftp.c +=================================================================== +RCS file: /var/cvs/busybox/networking/tftp.c,v +retrieving revision 1.25 +diff -u -r1.25 tftp.c +--- a/networking/tftp.c 5 Mar 2004 13:04:39 -0000 1.25 ++++ b/networking/tftp.c 5 Mar 2004 15:46:00 -0000 +@@ -1,11 +1,26 @@ ++/* vi: set sw=4 ts=4: */ + /* ------------------------------------------------------------------------- */ + /* tftp.c */ ++/* Copyright (c) 2003, 2004 Texas Instruments */ ++/* */ ++/* This package is free software; you can redistribute it and/or */ ++/* modify it under the terms of the license found in the file */ ++/* named COPYING that should have accompanied this file. */ ++/* */ ++/* THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR */ ++/* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED */ ++/* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ + /* */ + /* A simple tftp client for busybox. */ + /* Tries to follow RFC1350. */ + /* Only "octet" mode supported. */ + /* Optional blocksize negotiation (RFC2347 + RFC2348) */ + /* */ ++/* New features added at Texas Instruments, October 2003 */ ++/* Author: John Powers */ ++/* Multicast option: rfc2090 */ ++/* Timeout option: rfc2349 */ ++/* */ + /* Copyright (C) 2001 Magnus Damm <damm@opensource.se> */ + /* */ + /* Parts of the code based on: */ +@@ -46,8 +61,20 @@ + + #include "busybox.h" + ++#if defined(CONFIG_FEATURE_TFTP_BLOCKSIZE) || defined(CONFIG_FEATURE_TFTP_MULTICAST) || defined(CONFIG_FEATURE_TFTP_TIMEOUT) ++ #define TFTP_OPTIONS ++#endif ++ + //#define CONFIG_FEATURE_TFTP_DEBUG + ++#ifdef CONFIG_FEATURE_TFTP_DEBUG ++ static void printtime(void); ++ #define dprintf(fmt...) if (debug) {printtime(); printf(fmt);} ++ int debug = 0; ++#else ++ #define dprintf(fmt...) ++#endif ++ + #define TFTP_BLOCKSIZE_DEFAULT 512 /* according to RFC 1350, don't change */ + #define TFTP_TIMEOUT 5 /* seconds */ + +@@ -68,12 +95,24 @@ + "Illegal TFTP operation", + "Unknown transfer ID", + "File already exists", +- "No such user" ++ "No such user", ++#ifdef TFTP_OPTIONS ++ "Unsupported option", ++#endif + }; + + const int tftp_cmd_get = 1; + const int tftp_cmd_put = 2; + ++ ++struct tftp_option { ++ int multicast; ++ int blksize; ++ int client_timeout; ++ int server_timeout; ++}; ++ ++ + #ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE + + static int tftp_blocksize_check(int blocksize, int bufsize) +@@ -93,16 +132,158 @@ + return blocksize; + } + ++#endif ++ ++#ifdef CONFIG_FEATURE_TFTP_TIMEOUT ++ ++static int ++tftp_timeout_check(int timeout) ++{ ++ /* Check if timeout seconds is valid: ++ * RFC2349 says between 1 and 255. ++ */ ++ ++ if (timeout < 1 || timeout > 255) { ++ bb_error_msg("bad timeout value"); ++ return 0; ++ } ++ return timeout; ++} ++ ++#endif ++ ++#ifdef CONFIG_FEATURE_TFTP_MULTICAST ++static int ++tftp_multicast_check(const char *opt, char **phost, unsigned short *pport, int *pactive) ++{ ++ /* Option string contains comma delimited addr,port,active. ++ * addr = multicast IP address ++ * port = port number ++ * active = 1 if active client ++ * 0 if passive client ++ * ++ * Addr and port will be empty fields when the server notifies a ++ * passive client that it is now the active client. ++ * ++ * The host address string must be freed by the caller. Neither host ++ * nor port will be set/changed if the input fields are empty. ++ * ++ * If any tokenization errors occur in the opt string, the host ++ * address string is automatically freed. ++ * ++ * Return 0 if any tokenization error, 1 if all parameters are good. ++ */ ++ ++ char *token = NULL; ++ char *parse_buf = NULL; ++ char *tokenv = NULL; ++ char *host = NULL; ++ int port; ++ int active; ++ ++ parse_buf = bb_xstrdup(opt); ++ ++ dprintf("multicast option=%s\n", opt); ++ ++ /* IP address */ ++ if ((token = strtok_r(parse_buf, ",", &tokenv)) == NULL) { ++ dprintf("tftp_multicast_check: cannot parse IP address from %s\n", parse_buf); ++ free(parse_buf); ++ return 0; ++ } ++ if (strlen(token) > 0) ++ *phost = host = bb_xstrdup(token); ++ ++ /* Port */ ++ if ((token = strtok_r(NULL, ",", &tokenv)) == NULL) { ++ dprintf("tftp_multicast_check: cannot parse port number from %s\n", tokenv); ++ goto token_error; ++ } ++ if (strlen(token) > 0) { ++ port = atoi(token); ++ if (port < 0 || port > 0xFFFF) { ++ dprintf("tftp_multicast_check: bad port number (%d)\n", port); ++ goto token_error; ++ } ++ *pport = htons(port); ++ } ++ ++ /* Active/passive */ ++ if ((token = strtok_r(NULL, ",", &tokenv)) == NULL) { ++ dprintf("tftp_multicast_check: cannot parse active/passive from %s\n", tokenv); ++ goto token_error; ++ } ++ active = atoi(token); ++ if (active != 0 && active != 1) { ++ dprintf("tftp_multicast_check: bad active/passive flag (%d)\n", active); ++ goto token_error; ++ } ++ *pactive = active; ++ ++ free(parse_buf); ++ return 1; ++ ++token_error: ++ free(parse_buf); ++ if (host != NULL) ++ free(host); ++ *phost = NULL; ++ return 0; ++ ++} ++ ++#define VECTOR_QUANTUM_WIDTH 8 ++#define VECTOR_QUANTUM_ALL_ONES ((1<<VECTOR_QUANTUM_WIDTH)-1) ++ ++static void inline ++bit_set(int bit, unsigned char *vector) ++{ ++ int offset = bit / VECTOR_QUANTUM_WIDTH; ++ int mask = 1 << (bit % VECTOR_QUANTUM_WIDTH); ++ vector[offset] |= mask; ++} ++ ++static int inline ++bit_isset(int bit, const unsigned char *vector) ++{ ++ int offset = bit / VECTOR_QUANTUM_WIDTH; ++ int mask = 1 << (bit % VECTOR_QUANTUM_WIDTH); ++ return vector[offset] & mask ? 1 : 0; ++} ++ ++static int inline ++bit_lmz(const unsigned char *vector) ++{ ++ /* Return number of left-most zero in bit vector */ ++ const unsigned char *vp = vector; ++ int i; ++ unsigned char velem; ++ ++ while (*vp == VECTOR_QUANTUM_ALL_ONES) ++ vp++; ++ velem = *vp; ++ for (i = 0; i < VECTOR_QUANTUM_WIDTH; i++) { ++ if ((velem & (1 << i)) == 0) ++ break; ++ } ++ dprintf("bit_lmz: block=%d\n", (vp - vector)*VECTOR_QUANTUM_WIDTH + i); ++ return (vp - vector)*VECTOR_QUANTUM_WIDTH + i; ++} ++ ++#endif ++ ++ ++ ++#ifdef TFTP_OPTIONS ++ + static char *tftp_option_get(char *buf, int len, char *option) + { +- int opt_val = 0; ++ int opt_val = 0; + int opt_found = 0; + int k; +- +- while (len > 0) { + ++ while (len > 0) { + /* Make sure the options are terminated correctly */ +- + for (k = 0; k < len; k++) { + if (buf[k] == '\0') { + break; +@@ -117,9 +298,8 @@ + if (strcasecmp(buf, option) == 0) { + opt_found = 1; + } +- } +- else { +- if (opt_found) { ++ } else { ++ if (opt_found) { + return buf; + } + } +@@ -138,7 +318,8 @@ + #endif + + static inline int tftp(const int cmd, const struct hostent *host, +- const char *remotefile, int localfd, const unsigned short port, int tftp_bufsize) ++ const char *remotefile, int localfd, const unsigned short port, ++ struct tftp_option *option) + { + const int cmd_get = cmd & tftp_cmd_get; + const int cmd_put = cmd & tftp_cmd_put; +@@ -155,18 +336,29 @@ + int len; + int opcode = 0; + int finished = 0; +- int timeout = bb_tftp_num_retries; ++ int retry = bb_tftp_num_retries; + unsigned short block_nr = 1; + +-#ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE +- int want_option_ack = 0; ++#ifdef CONFIG_FEATURE_TFTP_MULTICAST ++ struct hostent *mchost; ++ struct sockaddr_in mcsa; ++ char *mchostname; ++ unsigned short mcport; ++ unsigned char *mcblockmap = NULL; ++ int master_client = 1; ++ int mcfd = -1; ++ int mcmaxblock = 0x10000; ++ int ack_oack = 0; ++#else ++ #define master_client 1 ++ #define ack_oack 0 + #endif + + /* Can't use RESERVE_CONFIG_BUFFER here since the allocation + * size varies meaning BUFFERS_GO_ON_STACK would fail */ +- char *buf=xmalloc(tftp_bufsize + 4); ++ char *buf=xmalloc(option->blksize + 4); + +- tftp_bufsize += 4; ++ int tftp_bufsize = option->blksize + 4; + + if ((socketfd = socket(PF_INET, SOCK_DGRAM, 0)) < 0) { + bb_perror_msg("socket"); +@@ -183,15 +375,21 @@ + memcpy(&sa.sin_addr, (struct in_addr *) host->h_addr, + sizeof(sa.sin_addr)); + +- /* build opcode */ +- +- if (cmd_get) { +- opcode = TFTP_RRQ; ++#ifdef CONFIG_FEATURE_TFTP_MULTICAST ++ if (option->multicast) { ++ const int bmsize = 0x10000 / VECTOR_QUANTUM_WIDTH; ++ if ((mcfd = socket(PF_INET, SOCK_DGRAM, 0)) < 0) { ++ bb_perror_msg("multicast socket"); ++ return EXIT_FAILURE; ++ } ++ mcblockmap = xmalloc(bmsize+1); ++ memset(mcblockmap, 0, bmsize+1); + } ++#endif + +- if (cmd_put) { +- opcode = TFTP_WRQ; +- } ++ /* build opcode */ ++ ++ opcode = cmd_get ? TFTP_RRQ : TFTP_WRQ; + + while (1) { + +@@ -203,7 +401,7 @@ + + cp += 2; + +- /* add filename and mode */ ++ /* First packet of file transfer includes file name, mode, and options */ + + if ((cmd_get && (opcode == TFTP_RRQ)) || + (cmd_put && (opcode == TFTP_WRQ))) { +@@ -223,7 +421,7 @@ + } + + if (too_long || ((&buf[tftp_bufsize - 1] - cp) < 6)) { +- bb_error_msg("too long remote-filename"); ++ bb_error_msg("too long: remote filename"); + break; + } + +@@ -238,8 +436,8 @@ + + if (len != TFTP_BLOCKSIZE_DEFAULT) { + +- if ((&buf[tftp_bufsize - 1] - cp) < 15) { +- bb_error_msg("too long remote-filename"); ++ if ((&buf[tftp_bufsize - 1] - cp) < 15) { ++ bb_error_msg("buffer too small for blksize option"); + break; + } + +@@ -249,16 +447,65 @@ + cp += 8; + + cp += snprintf(cp, 6, "%d", len) + 1; ++ } ++#endif ++ ++ ++ ++#ifdef CONFIG_FEATURE_TFTP_MULTICAST ++ ++ if (option->multicast) { ++ if ((&buf[tftp_bufsize - 1] - cp) < 12) { ++ bb_error_msg("buffer too small for multicast option"); ++ break; ++ } ++ ++ /* add "multicast" option */ + +- want_option_ack = 1; ++ memcpy(cp, "multicast\0", 11); ++ cp += 11; ++ ++ option->multicast = 0; /* turn back on when server accepts option */ ++ ack_oack = 1; /* acknowledge OACK */ + } ++ + #endif ++ ++#ifdef CONFIG_FEATURE_TFTP_TIMEOUT ++ ++ if (option->server_timeout != TFTP_TIMEOUT) { ++ if ((&buf[tftp_bufsize - 1] - cp) < 12) { ++ bb_error_msg("buffer too small for timeout option"); ++ break; ++ } ++ ++ /* add "timeout" option */ ++ ++ memcpy(cp, "timeout", 8); ++ cp += 8; ++ ++ cp += snprintf(cp, 4, "%d", option->server_timeout) + 1; ++ } ++#endif ++ + } + + /* add ack and data */ + +- if ((cmd_get && (opcode == TFTP_ACK)) || +- (cmd_put && (opcode == TFTP_DATA))) { ++#ifdef CONFIG_FEATURE_TFTP_MULTICAST ++ else if (option->multicast && opcode == TFTP_ACK) { ++ if (master_client || ack_oack) { ++ int blocknum = bit_lmz(mcblockmap); ++ *((unsigned short *) cp) = htons(blocknum); ++ cp += 2; ++ if (blocknum >= mcmaxblock) ++ finished = 1; ++ dprintf("ack block %d/%d %s\n", blocknum, mcmaxblock, finished? "finished": ""); ++ } ++ } ++#endif ++ else if ((cmd_get && opcode == TFTP_ACK) || ++ (cmd_put && opcode == TFTP_DATA)) { + + *((unsigned short *) cp) = htons(block_nr); + +@@ -275,7 +522,7 @@ + } + + if (len != (tftp_bufsize - 4)) { +- finished++; ++ finished = 1; + } + + cp += len; +@@ -283,82 +530,119 @@ + } + + +- /* send packet */ ++ /* send packet and receive reply */ + + +- timeout = bb_tftp_num_retries; /* re-initialize */ ++ retry = bb_tftp_num_retries; /* re-initialize */ + do { +- ++ int selectrc; + len = cp - buf; + +-#ifdef CONFIG_FEATURE_TFTP_DEBUG +- fprintf(stderr, "sending %u bytes\n", len); +- for (cp = buf; cp < &buf[len]; cp++) +- fprintf(stderr, "%02x ", (unsigned char)*cp); +- fprintf(stderr, "\n"); +-#endif +- if (sendto(socketfd, buf, len, 0, +- (struct sockaddr *) &sa, sizeof(sa)) < 0) { +- bb_perror_msg("send"); +- len = -1; +- break; +- } +- ++ /* send packet */ ++ if ((len > 2) && (! option->multicast || master_client || ack_oack)) { + +- if (finished && (opcode == TFTP_ACK)) { +- break; ++#ifdef CONFIG_FEATURE_TFTP_DEBUG ++ dprintf("sending %u bytes\n", len); ++ for (cp = buf; cp < &buf[len]; cp++) ++ if (debug) ++ printf("%02x ", *(unsigned char *)cp); ++ if (debug) ++ printf("\n"); ++#endif ++#ifdef CONFIG_FEATURE_TFTP_MULTICAST ++ ack_oack = 0; ++#endif ++ if (sendto(socketfd, buf, len, 0, (struct sockaddr *) &sa, sizeof(sa)) < 0) { ++ bb_perror_msg("send"); ++ len = -1; ++ break; ++ } ++ if (finished && opcode == TFTP_ACK) { ++ break; ++ } + } + +- /* receive packet */ ++ /* receive reply packet */ + + memset(&from, 0, sizeof(from)); + fromlen = sizeof(from); + +- tv.tv_sec = TFTP_TIMEOUT; ++ tv.tv_sec = option->client_timeout; + tv.tv_usec = 0; + + FD_ZERO(&rfds); + FD_SET(socketfd, &rfds); ++ dprintf("set to receive from socketfd (%d)\n", socketfd); ++#ifdef CONFIG_FEATURE_TFTP_MULTICAST ++ if (option->multicast) { ++ FD_SET(mcfd, &rfds); ++ dprintf("set to receive from mcfd (%d)\n", mcfd); ++ } ++#endif + +- switch (select(FD_SETSIZE, &rfds, NULL, NULL, &tv)) { +- case 1: +- len = recvfrom(socketfd, buf, tftp_bufsize, 0, +- (struct sockaddr *) &from, &fromlen); +- +- if (len < 0) { +- bb_perror_msg("recvfrom"); +- break; ++ dprintf("select\n"); ++ selectrc = select(FD_SETSIZE, &rfds, NULL, NULL, &tv); ++ if (selectrc > 0) { ++ /* A packet was received */ ++ if (FD_ISSET(socketfd, &rfds)) { /* Unicast packet */ ++ dprintf("from socketfd\n"); ++ len = recvfrom(socketfd, buf, tftp_bufsize, 0, (struct sockaddr *) &from, &fromlen); ++ ++ if (len < 0) { ++ bb_perror_msg("recvfrom"); ++ } else { ++ if (sa.sin_port == port) { ++ sa.sin_port = from.sin_port; ++ } ++ if (sa.sin_port == from.sin_port) { ++ retry = 0; ++ } else { ++ /* bad packet */ ++ /* discard the packet - treat as timeout */ ++ retry = bb_tftp_num_retries; ++ bb_error_msg("timeout"); ++ } ++ } + } + +- timeout = 0; +- +- if (sa.sin_port == port) { +- sa.sin_port = from.sin_port; ++#ifdef CONFIG_FEATURE_TFTP_MULTICAST ++ else if (option->multicast && FD_ISSET(mcfd, &rfds)) { /* Multicast packet */ ++ dprintf("from mcfd\n"); ++ len = recvfrom(mcfd, buf, tftp_bufsize, 0, (struct sockaddr *) &from, &fromlen); ++ if (len < 0) { ++ bb_perror_msg("multicast recvfrom"); ++ } else { ++ if (mcsa.sin_port == mcport) { ++ mcsa.sin_port = from.sin_port; ++ } ++ if (mcsa.sin_port == from.sin_port) { ++ retry = 0; ++ } else { ++ retry = bb_tftp_num_retries; ++ bb_error_msg("multicast timeout"); ++ } ++ } + } +- if (sa.sin_port == from.sin_port) { +- break; +- } +- +- /* fall-through for bad packets! */ +- /* discard the packet - treat as timeout */ +- timeout = bb_tftp_num_retries; ++#endif + +- case 0: ++ } else if (selectrc == 0) { ++ /* Time out */ ++ dprintf("timeout\n"); + bb_error_msg("timeout"); + +- timeout--; +- if (timeout == 0) { ++ retry--; ++ if (retry == 0) { + len = -1; + bb_error_msg("last timeout"); + } +- break; +- +- default: ++ } else { ++ /* Error condition */ ++ dprintf("error\n"); + bb_perror_msg("select"); + len = -1; + } + +- } while (timeout && (len >= 0)); ++ } while (retry && len >= 0); + + if ((finished) || (len < 0)) { + break; +@@ -370,9 +654,8 @@ + opcode = ntohs(*((unsigned short *) buf)); + tmp = ntohs(*((unsigned short *) &buf[2])); + +-#ifdef CONFIG_FEATURE_TFTP_DEBUG +- fprintf(stderr, "received %d bytes: %04x %04x\n", len, opcode, tmp); +-#endif ++ dprintf("received %d bytes: %04x %04x\n", len, opcode, tmp); ++ dprintf("master_client=%d\n", master_client); + + if (opcode == TFTP_ERROR) { + char *msg = NULL; +@@ -393,55 +676,116 @@ + break; + } + +-#ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE +- if (want_option_ack) { ++#ifdef TFTP_OPTIONS + +- want_option_ack = 0; ++ if (opcode == TFTP_OACK) { + +- if (opcode == TFTP_OACK) { ++ /* server seems to support options */ + +- /* server seems to support options */ ++ char *res; ++ ++ block_nr = 0; /* acknowledge option packet with block number 0 */ ++ opcode = cmd_put ? TFTP_DATA : TFTP_ACK; + +- char *res; + +- res = tftp_option_get(&buf[2], len-2, +- "blksize"); ++#ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE ++ res = tftp_option_get(&buf[2], len-2, "blksize"); + +- if (res) { +- int blksize = atoi(res); +- +- if (tftp_blocksize_check(blksize, +- tftp_bufsize - 4)) { ++ if (res) { ++ int blksize = atoi(res); + +- if (cmd_put) { +- opcode = TFTP_DATA; +- } +- else { +- opcode = TFTP_ACK; +- } +-#ifdef CONFIG_FEATURE_TFTP_DEBUG +- fprintf(stderr, "using blksize %u\n", blksize); ++ if (tftp_blocksize_check(blksize, tftp_bufsize - 4)) { ++ dprintf("using blksize %d\n", blksize); ++ tftp_bufsize = blksize + 4; ++ free(buf); ++ buf = xmalloc(tftp_bufsize); ++ } else { ++ bb_error_msg("bad blksize %d", blksize); ++ break; ++ } ++ } + #endif +- tftp_bufsize = blksize + 4; +- block_nr = 0; +- continue; +- } +- } +- /* FIXME: +- * we should send ERROR 8 */ +- bb_error_msg("bad server option"); +- break; +- } + +- bb_error_msg("warning: blksize not supported by server" +- " - reverting to 512"); + +- tftp_bufsize = TFTP_BLOCKSIZE_DEFAULT + 4; ++#ifdef CONFIG_FEATURE_TFTP_MULTICAST ++ res = tftp_option_get(&buf[2], len-2, "multicast"); ++ ++ if (res) { ++ ack_oack = 1; ++ if (tftp_multicast_check(res, &mchostname, &mcport, &master_client)) { ++ struct ip_mreq mreq; ++ struct in_addr mcaddr; ++ ++ dprintf("using multicast\n"); ++ ++ mchost = xgethostbyname(mchostname); ++ if (mchost) { ++ memcpy(&mcaddr, mchost->h_addr, mchost->h_length); ++ if (! IN_MULTICAST(ntohl(mcaddr.s_addr))) { ++ bb_error_msg("bad multicast address: %s", mchostname); ++ break; ++ } ++ } else { ++ bb_error_msg("bad multicast address: %s", mchostname); ++ break; ++ } ++ ++ memset(&mcsa, 0, sizeof(mcsa)); ++ mcsa.sin_family = AF_INET; ++ mcsa.sin_addr.s_addr = htonl(INADDR_ANY); ++ mcsa.sin_port = mcport; ++ ++ bind(mcfd, (struct sockaddr *)&mcsa, sizeof(mcsa)); ++ ++ mreq.imr_multiaddr.s_addr = mcaddr.s_addr; ++ mreq.imr_interface.s_addr = htonl(INADDR_ANY); ++ ++ if (setsockopt(mcfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) ++ { ++ bb_error_msg("setsockopt"); ++ break; ++ } ++ ++ option->multicast = 1; ++ } else { ++ bb_error_msg("bad multicast option value: %s", res); ++ break; ++ } ++ } ++#endif ++ + } ++ else + #endif + + if (cmd_get && (opcode == TFTP_DATA)) { + ++#ifdef CONFIG_FEATURE_TFTP_MULTICAST ++ if (option->multicast) { ++ int bn = tmp - 1; ++ /* Do I need this block? */ ++ if (! bit_isset(bn, mcblockmap)) { ++ lseek(localfd, bn*(tftp_bufsize-4), SEEK_SET); ++ len = write(localfd, &buf[4], len-4); ++ if (len < 0) { ++ bb_perror_msg("write"); ++ break; ++ } ++ bit_set(bn, mcblockmap); ++ if (len != (tftp_bufsize-4)) { ++ mcmaxblock = tmp; ++ dprintf("mcmaxblock=%d, (len(%d) != tftp_bufsize-4(%d))\n", mcmaxblock, len, tftp_bufsize-4); ++ } ++ opcode = TFTP_ACK; ++ } ++ /* Do not acknowledge block if I already have a copy of the block. A situation can arise when the server ++ * and client timeout nearly simultaneously. The server retransmits the block at the same time the client ++ * re-requests the block. From then on out, each block is transmitted twice--not a good use of bandwidth. ++ */ ++ } ++ else ++#endif ++ + if (tmp == block_nr) { + + len = write(localfd, &buf[4], len - 4); +@@ -452,15 +796,14 @@ + } + + if (len != (tftp_bufsize - 4)) { +- finished++; ++ finished = 1; + } + + opcode = TFTP_ACK; +- continue; + } + } + +- if (cmd_put && (opcode == TFTP_ACK)) { ++ else if (cmd_put && opcode == TFTP_ACK) { + + if (tmp == (unsigned short)(block_nr - 1)) { + if (finished) { +@@ -468,15 +811,19 @@ + } + + opcode = TFTP_DATA; +- continue; + } + } + } + + #ifdef CONFIG_FEATURE_CLEAN_UP + close(socketfd); ++ free(buf); ++ ++#ifdef CONFIG_FEATURE_TFTP_MULTICAST ++ if (mcblockmap != NULL) ++ free(mcblockmap); ++#endif + +- free(buf); + #endif + + return finished ? EXIT_SUCCESS : EXIT_FAILURE; +@@ -487,13 +834,18 @@ + struct hostent *host = NULL; + char *localfile = NULL; + char *remotefile = NULL; +- int port; ++ unsigned short port; + int cmd = 0; + int fd = -1; + int flags = 0; + int opt; + int result; +- int blocksize = TFTP_BLOCKSIZE_DEFAULT; ++ struct tftp_option option = { ++ .multicast = 0, ++ .blksize = TFTP_BLOCKSIZE_DEFAULT, ++ .client_timeout = TFTP_TIMEOUT, ++ .server_timeout = TFTP_TIMEOUT, ++ }; + + /* figure out what to pass to getopt */ + +@@ -515,13 +867,45 @@ + #define PUT + #endif + +- while ((opt = getopt(argc, argv, BS GET PUT "l:r:")) != -1) { ++#ifdef CONFIG_FEATURE_TFTP_TIMEOUT ++#define TO "T:t:" ++#else ++#define TO ++#endif ++ ++#ifdef CONFIG_FEATURE_TFTP_MULTICAST ++#define MC "m" ++#else ++#define MC ++#endif ++ ++#ifdef CONFIG_FEATURE_TFTP_DEBUG ++#define DB "D" ++#else ++#define DB ++#endif ++ ++ while ((opt = getopt(argc, argv, BS GET PUT TO MC DB "l:r:")) != -1) { + switch (opt) { + #ifdef CONFIG_FEATURE_TFTP_BLOCKSIZE + case 'b': +- blocksize = atoi(optarg); +- if (!tftp_blocksize_check(blocksize, 0)) { +- return EXIT_FAILURE; ++ option.blksize = atoi(optarg); ++ if (!tftp_blocksize_check(option.blksize, 0)) { ++ return EXIT_FAILURE; ++ } ++ break; ++#endif ++#ifdef CONFIG_FEATURE_TFTP_TIMEOUT ++ case 'T': ++ option.client_timeout = atoi(optarg); ++ if (!tftp_timeout_check(option.client_timeout)) { ++ return EXIT_FAILURE; ++ } ++ break; ++ case 't': ++ option.server_timeout = atoi(optarg); ++ if (!tftp_timeout_check(option.server_timeout)) { ++ return EXIT_FAILURE; + } + break; + #endif +@@ -537,18 +921,34 @@ + flags = O_RDONLY; + break; + #endif ++#ifdef CONFIG_FEATURE_TFTP_MULTICAST ++ case 'm': ++ option.multicast = 1; /* receive multicast file */ ++ break; ++#endif ++#ifdef CONFIG_FEATURE_TFTP_DEBUG ++ case 'D': ++ debug = 1; ++ break; ++#endif + case 'l': + localfile = bb_xstrdup(optarg); + break; + case 'r': + remotefile = bb_xstrdup(optarg); + break; ++ default: ++ bb_show_usage(); + } + } + + if ((cmd == 0) || (optind == argc)) { + bb_show_usage(); + } ++ if (cmd == tftp_cmd_put && option.multicast) { ++ fprintf(stderr, "Multicast (-m) invalid option with put (-p) command\n"); ++ exit(EXIT_FAILURE); ++ } + if(localfile && strcmp(localfile, "-") == 0) { + fd = fileno((cmd==tftp_cmd_get)? stdout : stdin); + } +@@ -566,14 +966,12 @@ + host = xgethostbyname(argv[optind]); + port = bb_lookup_port(argv[optind + 1], "udp", 69); + +-#ifdef CONFIG_FEATURE_TFTP_DEBUG +- fprintf(stderr, "using server \"%s\", remotefile \"%s\", " ++ dprintf("using server \"%s\", remotefile \"%s\", " + "localfile \"%s\".\n", + inet_ntoa(*((struct in_addr *) host->h_addr)), + remotefile, localfile); +-#endif + +- result = tftp(cmd, host, remotefile, fd, port, blocksize); ++ result = tftp(cmd, host, remotefile, fd, port, &option); + + #ifdef CONFIG_FEATURE_CLEAN_UP + if (!(fd == fileno(stdout) || fd == fileno(stdin))) { +@@ -582,3 +980,18 @@ + #endif + return(result); + } ++ ++ ++#ifdef CONFIG_FEATURE_TFTP_DEBUG ++ ++#include <sys/time.h> ++ ++static void ++printtime(void) ++{ ++ struct timeval tv; ++ gettimeofday(&tv, NULL); ++ printf("%11lu.%06lu ", tv.tv_sec, tv.tv_usec); ++} ++ ++#endif |