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 /patches | |
| parent | ec58bce3638b73013f7bbdce3047ce0797de079c (diff) | |
| download | busybox-db6ee81c994c7571a80a5e37c6a331a69e5dccde.tar.gz | |
Patch from John Powers, adds multicast (rfc2090) and timeout (rfc2349)
options
Diffstat (limited to 'patches')
| -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 | 
