// TAB-SIZE=4
/*
 *  NetPipe Version 1.0.0
 *
 *  (c) 1999 by Jan Oberländer <mindriot@gmx.net>
 *
 *  NetPipe is a  simple Unix data distribution utility which  allows to pipe
 *  data over a network and  optionally  broadcast  it. This  way NetPipe can
 *  serve as a cloning system.
 */

// System libraries

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>

// Internal libraries

#include "include/crc.h"		// CRC-32 code
#include "include/protocol.h"	// protocol data types
#include "include/utime.h"		// time functions for struct timeval
#include "include/safealloc.h"	// memory allocation with error checking

// Defines

#define __NP_VERSION "NetPipe v1.0.0 Beta-2"

#define xperror(str) { perror(str); exit(1); }
#define v_printf(Arguments...) { 							\
			if(opt_verbose) fprintf(stderr,##Arguments);	\
		}

// Global variables

int	opt_verbose	= 0,			// Cmd-line option flags
	opt_client	= 0,			// default mode		: Server
	opt_timeout	= 1,			// default timeout	: 1 second
	opt_port	= 9000,			// default port		: 9000
	opt_onlyone	= 0,			// only one client ACKs (default: no)
	num_clients	= 0,
	on			= 1,
	r			= 0,			// Server receive socket (client send)
	s			= 0,			// Server transmit socket
	sockaddr_len= sizeof(struct sockaddr_in);
	
struct sockaddr_in
	server_in,					// Server receive sockaddr	(port 9001)
	server_out,					// Server transmit sockaddr	(port 9000)
	client_in,					// Client receive sockaddr	(port 9000)
	client_out;					// Client transmit sockaddr	(port 9001)
struct in_addr
	dest_addr;					// Guess what...



/* exit_help()
 *
 * exits the program and prints a help screen.
 *
 * version:		if set, prints version number as well.
 */

void exit_help(int version)
{
	if (version)
		printf(__NP_VERSION "\nmindriot@gmx.net (c) 1999\n\n");
	printf("Usage: netpipe [-?]\n"
	       "       netpipe [-v] -c [-p port] [-t timeout]\n"
	       "       netpipe [-v] [-n num_clients] [-p port] [-t timeout] [-1]"
	       " Dest_IP\n");
	exit(1);
}

/* t_sendto()
 *
 * an improved sendto() with a timeout function. See the sendto manpage for
 * an explanation of the parameters.
 *
 */

int t_sendto(int s, const void *msg, int len, unsigned int flags,
             const struct sockaddr *to, int tolen)
{
	start_timer();
	while (sendto(s,msg,len,flags,to,tolen)==-1) {
		if (errno!=EWOULDBLOCK) return 0;
		if (timer_sec()>=opt_timeout) return -1;
	}
	return 1;
}

/* send_error()
 *
 * sends a CM_ERROR packet. These packets are fatal. See t_sendto() for return
 * values.
 *
 * s:			The socket
 * to:			Destination sockaddr
 * tolen:		Length of the above field
 */

int send_error(int s, unsigned long error, int er_no, struct sockaddr *to,
               int tolen)
{
	union packet pk;
	
	pk.cmd			= CM_ERROR;
	pk.error.error	= error;
	pk.error.er_no	= er_no;
	
	return t_sendto(s,&pk,S_ERROR,0,to,tolen);
}

/* send_rxerr()
 *
 * sends an CM_RXERR packet. See t_sendto() for return values.
 */

int send_rxerr(int s, unsigned long error, const struct sockaddr *to,
               int tolen)
{
	union packet pk;
	
	pk.cmd			= CM_RXERR;
	pk.rxerr.error	= error;
	
	return t_sendto(s,&pk,S_RXERR,0,to,tolen);
}

/* catch_signal()
 *
 * on a SIGINT signal (Ctrl+C), exits the program after sending and abort
 * message. This is only active once the socket is bound.
 *
 * sig:			contains the signal number
 */

static void catch_signal(int sig)
{
	fprintf(stderr,"\nSIGINT caught, aborting.\n");

	if (opt_client)
		send_error(r,ER_UABORT,0,(struct sockaddr *)&client_out,
		           sockaddr_len);
	else
		send_error(s,ER_UABORT,0,(struct sockaddr *)&server_out,
		           sockaddr_len);
	close(r);
	close(s);
	exit(2);
}

/* remove_client()
 *
 * discard a single client and remove it from the list; reorder the list.
 *
 * client_db:	the client list
 * addr:		its IP
 */

void remove_client(struct in_addr *client_db, struct in_addr addr)
{
	int x,y;
	
	if (opt_onlyone) {
		if (client_db[num_clients-1].s_addr==addr.s_addr) {
			fprintf(stderr,"\nFATAL: Master client died, aborting.\n");
			send_error(s,ER_UABORT,0,(struct sockaddr *)&server_out,
			           sockaddr_len);
			close(r);
			close(s);
			exit(1);
		}
	}
	for (x=0;x<num_clients;x++)
		if (client_db[x].s_addr==addr.s_addr) {
			num_clients--;
			for (y=x;y<num_clients;y++)
				client_db[y]=client_db[y+1];
		}
	if (!num_clients) {
		fprintf(stderr,"\nNo more clients left, aborting.\n");
		close(r);
		close(s);
		exit(1);
	}
}

// Main functions

void client(void);
void server(void);

int main(int argc, char **argv) {
	int o;
	
	// check command line
	while ((o=getopt(argc,argv,"+?vn:t:p:c1"))!=EOF) {
		switch (o) {
		case '?':			// help screen
			exit_help(1);
			break;
		case 'v':			// verbose mode
			opt_verbose=1;
			v_printf(__NP_VERSION "\n");
			break;
		case 'c':			// client mode
			opt_client=1;
			break;
		case 'n':			// # of clients
			errno=0;
			num_clients=strtoul(optarg,NULL,10);
			if (errno) xperror("netpipe: num_clients");
			break;
		case 't':			// timeout value
			opt_timeout=strtoul(optarg,NULL,10);
			if (errno) xperror("netpipe: opt_timeout");
			if (opt_timeout<1) {
				fprintf(stderr,"netpipe: invalid timeout value\n");
				exit_help(0);
			}
			break;
		case 'p':			// udp port to use
			opt_port=strtoul(optarg,NULL,10);
			if (errno) xperror("netpipe: opt_port");
			if (opt_port<1 || opt_port>65535) {
				fprintf(stderr,"netpipe: invalid port number\n");
				exit_help(0);
			}
			break;
		case '1':			// only one client needs to ACK
			if (opt_client)
				fprintf(stderr,"netpipe: -1: this is a server option.\n");
			opt_onlyone=1;
			break;
		default:
			exit_help(0);
			break;
		}
	}
	if(!opt_client)
		if(!argv[optind++]) {
			fprintf(stderr,"netpipe: no destination address specified\n");
			exit_help(0);
		}
	if(argv[optind]) {
		fprintf(stderr,"netpipe: destination address only valid in "
		               "server mode.\n");
	}

	// initialize and bind server transmit socket
	if ((s=socket(AF_INET,SOCK_DGRAM,0))==-1) xperror("netpipe: socket");
	if (fcntl(s,F_SETFL,O_NONBLOCK)==-1) xperror("netpipe: fcntl");
	if (setsockopt(s,SOL_SOCKET,SO_BROADCAST,&on,sizeof(on))==-1)
		xperror("netpipe: setsockopt");
	server_out.sin_family		= AF_INET;
	server_out.sin_port			= htons(opt_port);
	server_out.sin_addr.s_addr	= INADDR_ANY;
	client_in=server_out;
	if (bind(s,(struct sockaddr *)&server_out,sockaddr_len)==-1)
		xperror("netpipe: bind");

	// initialize and bind server receive socket
	if ((r=socket(AF_INET,SOCK_DGRAM,0))==-1) xperror("netpipe: socket");
	if (fcntl(r,F_SETFL,O_NONBLOCK)==-1) xperror("netpipe: fcntl");
	server_in.sin_family		= AF_INET;
	server_in.sin_port			= htons(opt_port+1);
	server_in.sin_addr.s_addr	= INADDR_ANY;
	client_out=server_in;
	if (bind(r,(struct sockaddr *)&server_in,sockaddr_len)==-1)
		xperror("netpipe: bind");
	
	// sockets are ready, now install SIGINT handler
	if(signal(SIGINT,&catch_signal)==SIG_ERR) {
		fprintf(stderr,"signal(SIGINT) failed, exiting.\n");
		exit(3);
	}

	// choose client or server mode
	if(opt_client) client(); 
	else {
		if (!inet_aton(argv[--optind],&dest_addr)) {
			fprintf(stderr,"netpipe: invalid destination address.\n");
			exit(1);
		}
		server();
	}
	
	// done, exit
	close(r);
	close(s);
	return 0;
}

// Global vars for the server / client modules

union packet pk, *in_pk;
unsigned char buf[8192];

// Client module

void client(void)
{
	unsigned	length 		= 0,	// total # of bytes received
				seek_phase	= 1,	// seek phase indicator
				rx_phase	= 1,	// receive phase indicator
				index		= 0,	// current position in packet
				x			= 0,	// dummy var
				write_data	= 0,	// write data only if 1
				seqnum		= 0,	// sequential packet number
				must_ack	= 1,	// client must send rxok
				got_ack_plz	= 0;	// server told client to ack
	unsigned long crc		= 0;	// CRC-32
	
	v_printf("Going to client mode.\n");
	if (num_clients) fprintf(stderr,"-n option invalid in client mode.\n");

	v_printf("Waiting for server...");

	seek_phase=1;	
	do {
		v_printf("\x0D");

		// We'll receive from anywhere using this sockaddr via socket s
		client_in.sin_family		= AF_INET;
		client_in.sin_port			= htons(opt_port);
		client_in.sin_addr.s_addr	= INADDR_ANY;
		
		// watch for ping
		start_timer();
		do {
			if(recvfrom(s,&buf,S_PACKET,0,(struct sockaddr *)&client_in,
		       &sockaddr_len)!=-1) break;
		} while (timer_sec()<opt_timeout);

		if (timer_sec()>=opt_timeout) {
			v_printf("Waiting for server...           ");
			continue;
		}

		// evaluate packet
		in_pk=(union packet *)&buf;
		switch (in_pk->cmd) {
			case CM_PING:				// ping, send normal reply
				break;
			case CM_SEQ_INIT:			// end seek phase
				seek_phase=0;
				if (in_pk->seq_init.flags & FL_ONLYONE) {
					must_ack=got_ack_plz;
				}
				continue;
			case CM_ACK_PLZ:			// client must ack
				got_ack_plz=1;
				break;
			case CM_ERROR: {			// server error
				switch (in_pk->error.error) {
				case ER_UABORT:			// user aborts on server
					fprintf(stderr,"\nTransaction aborted by user.\n");
					exit(1);
				case ER_ERRNO:			// internal server error
					fprintf(stderr,"\nServer error: %s\n",
					        strerror(in_pk->error.er_no));
					exit(1);
				default:				// what was that?!?
					fprintf(stderr,"\nUnknown server error.\n");
					exit(1);
				}
			}
			default:
				v_printf("Invalid packet type (0x%02X)      ",in_pk->cmd);
				continue;
		}
		v_printf("Server call from %s",inet_ntoa(client_in.sin_addr));
		
		// ping reply
		pk.cmd		= CM_PONG;
		pk.ping.tv	= in_pk->ping.tv;

		// set up client transmit sockaddr, sending via socket r
		client_out.sin_addr=client_in.sin_addr;
		
		t_sendto(r,&pk,S_PONG,0,(struct sockaddr *)&client_out,sockaddr_len);
	} while (seek_phase);

	v_printf("Initiating transaction sequence - ");

	// ready to start transaction
	memset(&pk,0,sizeof(pk));
	pk.cmd=CM_READY;
	
	// if we have an error now, it will be fatal!
	switch (t_sendto(r,&pk,S_READY,0,(struct sockaddr *)&client_out,
	                 sockaddr_len)) {
		case 0:
			perror("\nt_sendto");
			send_error(r,ER_ERRNO,errno,(struct sockaddr *)&client_out,
			           sockaddr_len);
			exit(1);
		case -1:
			send_error(r,ER_TIMEOUT,0,(struct sockaddr *)&client_out,
			           sockaddr_len);
			fprintf(stderr,"\nnetpipe: timeout during initialization.\n");
			exit(1);
	}
	
	v_printf("ready.\n");

	// receive stuff
	length=0;
	seqnum=0;
	do {
		v_printf("\x0D");

		while (recvfrom(s,&buf,sizeof(pk),0,(struct sockaddr *)&client_in,
		       &sockaddr_len)==-1);
		pk=*(union packet *)&buf;

		switch (pk.cmd) {
			case CM_TX:					// data coming in
				index=0;
				while ((x=recvfrom(s,&(buf[index]),pk.tx.len+4,0,
				                   (struct sockaddr *)&client_in,
				                   &sockaddr_len))==-1);

				// Did we get enough bytes?
				if (x!=pk.tx.len+4) {
					v_printf("\nData missing, sending retry.\n");
					send_rxerr(r,RX_DATA,(struct sockaddr *)&client_out,
					           sockaddr_len);
					write_data=0;
				}
				
				// Check sequence number
				if (seqnum==pk.tx.seqnum) {				// correct number
					seqnum++;
					write_data=1;
				} else if (seqnum>pk.tx.seqnum) {		// retry packet
					v_printf("\x0Dretry packet, ignoring ");
					write_data=0;
				} else {
					fprintf(stderr,"\nFATAL: sequence error (%u vs. %lu), "
					        "aborting.\n",seqnum,pk.tx.seqnum);
					send_rxerr(r,RX_SEQ,(struct sockaddr *)&client_out,
					           sockaddr_len);
					exit(1);
				}

				if (write_data) {
					// Calculate CRC
					crc=0;
					for (x=0;x<pk.tx.len;x++) crc=crc32(buf[x],crc);

					// CRC error, tell server and wait for same packet again
					if (htonl(crc)!=*(unsigned long *)(&buf[pk.tx.len])) {
						fprintf(stderr,
						        "\nCRC ERROR: Packet #%u (%08lX vs. %08lX)\n",
						        seqnum,(unsigned long) htonl(crc),
						        *(unsigned long *)(&buf[pk.tx.len]));
						send_rxerr(r,RX_CRC,(struct sockaddr *)&client_out,
						           sockaddr_len);
						seqnum--;
						write_data=0;
						break;
					}
				}

				if (write_data) write(1,&buf,pk.tx.len);
				
				// OK, acknowledge packet
				if (must_ack) {
					pk.cmd=CM_RXOK;
					while(t_sendto(r,&pk,S_RXOK,0,
					               (struct sockaddr *)&client_out,
					               sockaddr_len)==-1) {
						v_printf("\x0Dtimeout (%s)",strerror(errno));
					}
				}
				v_printf("%08u: %8u bytes transferred",seqnum,
				         length+=pk.tx.len);
				break;
			case CM_ERROR: {			// server error
				switch (pk.error.error) {
				case ER_UABORT:			// user aborts on server
					fprintf(stderr,"\nTransaction aborted by user.\n");
					exit(1);
				case ER_ERRNO:			// internal server error
					fprintf(stderr,"\nServer error: %s\n",
					        strerror(pk.error.er_no));
					exit(1);
				default:				// what was that?!?
					fprintf(stderr,"\nUnknown server error.\n");
					exit(1);
				}
				break;
			}
			case CM_EOT:
				rx_phase=0;
				break;
			default:
				v_printf("Invalid packet type (0x%02X)      ",pk.cmd);
				send_rxerr(r,RX_DATA,(struct sockaddr *)&client_out,
				           sockaddr_len);
				continue;
		}

	} while(rx_phase);

	v_printf("\nEOT	encountered.\n");
	pk.cmd=CM_DONE;
	t_sendto(r,&pk,S_DONE,0,(struct sockaddr *)&client_out,sockaddr_len);
	v_printf("Transaction sequence completed.\n");
}

// Server module

void server(void)
{
	unsigned	length		= 0,	// total # of bytes sent
				clients		= 0,	// client counter
				x			= 0,	// dummy var
				count		= 0,	// ping count
				crc_ok		= 1,	// CRC flag
				seqnum		= 0;	// sequential packet number
	unsigned long crc		= 0;	// CRC-32
	struct timeval tv;				// Time to check ping delay
	struct in_addr *client_db;		// list of client IPs

	// Allocate enough room for client list
	client_db=scmalloc(sizeof(unsigned long int)*num_clients);

	v_printf("Going to server mode.\n");

	if(!num_clients) {
		v_printf("Number of clients not given, assuming one.\n");
		num_clients=1;
	}
	v_printf("Searching for clients...\n");

	// We'll send out on the base port via socket s
	server_out.sin_family	= AF_INET;
	server_out.sin_port		= htons(opt_port);
	server_out.sin_addr		= dest_addr;
	
	do {
		v_printf("\x0D%3u: ",++count);

		// Send PING packet

		pk.cmd=CM_PING;
		gettimeofday(&pk.ping.tv,NULL);
		if (t_sendto(s,&pk,S_PING,0,(struct sockaddr *)&server_out,
		    sockaddr_len)==-1) {
			v_printf("Sendto timeout.         ");
			continue;
		}

		// Listen for replies

		clients=0;
		start_timer();
		do {
			// We'll receive here, on base_port+1, via socket r.
			server_in.sin_family		= AF_INET;
			server_in.sin_port			= htons(opt_port+1);
			server_in.sin_addr.s_addr	= INADDR_ANY;
			
			if (recvfrom(r,&buf,256,0,(struct sockaddr *)&server_in,
			             &sockaddr_len)==-1) continue;

			gettimeofday(&tv,NULL);

			// evaluate packet
			in_pk=(union packet *)&buf;
			switch (in_pk->cmd) {
				case CM_PONG:				// ping, send normal reply
					client_db[clients++]=server_in.sin_addr;
					v_printf("%lims ",udiff((*in_pk).ping.tv,tv));
					break;
				case CM_ERROR: {			// server error
					switch (in_pk->error.error) {
					case ER_UABORT:			// user aborts on client
						fprintf(stderr,"\nTransaction aborted by user.\n");
						exit(1);
					case ER_ERRNO:			// internal client error
						fprintf(stderr,"\nClient error: %s\n",
						        strerror(in_pk->error.er_no));
						exit(1);
					default:				// what was that?!?
						fprintf(stderr,"\nUnknown client error.\n");
						exit(1);
					}
					break;
				}
				default:
					v_printf("Invalid packet type (0x%02X)      ",in_pk->cmd);
					continue;
			}
		} while (timer_sec()<opt_timeout);

		if (clients) {
			v_printf("- %i clients.",clients);
		} else {
			v_printf("Ping timeout.                  ");
		}
	} while (clients<num_clients);	
	
	v_printf("\nClient list:\n");
	for (x=0;x<num_clients;x++)
		v_printf("%i. %s\n",x,inet_ntoa(client_db[x]));
	clients=0;

	// send one client the ACK_PLZ packet
	
	if (opt_onlyone) {
		v_printf("Telling %s to send answers.\n",
		         inet_ntoa(client_db[num_clients-1]));
		pk.cmd = CM_ACK_PLZ;
		server_out.sin_addr=client_db[num_clients-1];
		while (t_sendto(s,&pk,S_SEQ_INIT,0,(struct sockaddr *)&server_out,
		                sockaddr_len)==-1);
	}
	
	// Prepare transaction
	
	v_printf("Initiating transaction sequence - ");

	pk.cmd				= CM_SEQ_INIT;
	pk.seq_init.flags	= opt_onlyone * FL_ONLYONE;
	server_out.sin_addr	= dest_addr;
	
	while (t_sendto(s,&pk,S_SEQ_INIT,0,(struct sockaddr *)&server_out,
	                sockaddr_len)==-1);
	do {
		server_in.sin_family		= AF_INET;
		server_in.sin_port			= htons(opt_port+1);
		server_in.sin_addr.s_addr	= INADDR_ANY;
		while (recvfrom(r,&buf,256,0,(struct sockaddr *)&server_in,
		       &sockaddr_len)==-1);

		// evaluate packet
		in_pk=(union packet *)&buf;
		switch (in_pk->cmd) {
			case CM_ERROR: {			// server error
				switch (in_pk->error.error) {
				case ER_UABORT:			// user aborts on client
					fprintf(stderr,"\nTransaction aborted by user.\n");
					exit(1);
				case ER_ERRNO:			// internal client error
					fprintf(stderr,"\nClient error: %s\n",
					        strerror(in_pk->error.er_no));
					exit(1);
				default:				// what was that?!?
					fprintf(stderr,"\nUnknown client error.\n");
					exit(1);
				}
				break;
			}
			case CM_READY:
				for (x=0;x<num_clients;x++) {
					if (client_db[x].s_addr==server_in.sin_addr.s_addr)
						clients++;
				}
				break;
		}
	} while(clients<num_clients);
	v_printf("all clients ready.\n");
	
	// Transmit data

	seqnum		= 0;
	length		= 0;
	crc_ok  	= 1;
	for(;;) {
		if(crc_ok)
			if (!(pk.tx.len=read(0,&buf,sizeof(buf)-4))) break;
		v_printf("\x0D");
		
		pk.cmd			= CM_TX;
		pk.tx.seqnum	= seqnum;
		
		// Calculate CRC
		crc=0;
		for (x=0;x<pk.tx.len;x++) crc=crc32(buf[x],crc);
		*(unsigned long *)(&buf[pk.tx.len])=htonl(crc);
		
		while (t_sendto(s,&pk,sizeof(pk),0,(struct sockaddr *)&server_out,
		                sockaddr_len)==-1);
		while (t_sendto(s,&buf,pk.tx.len+sizeof(unsigned long),0,
		                (struct sockaddr *)&server_out,sockaddr_len)==-1)
			v_printf("sendto timeout \x0D");

		// Now that we've sent the stuff, get the ACKs
		
		crc_ok=1;
		clients=0;
		start_timer();
		do {
			server_in.sin_family		= AF_INET;
			server_in.sin_port			= htons(opt_port+1);
			server_in.sin_addr.s_addr	= INADDR_ANY;

			crc_ok=1;
			while (recvfrom(r,&buf,sizeof(pk),0,(struct sockaddr *)&server_in,
			                &sockaddr_len)==-1) {
				if(timer_sec()>=opt_timeout) {
					v_printf("\x0Dtimeout (%s) ",strerror(errno));
					// Send retry
					crc_ok=0;
					break;
				}
			}

			if (!crc_ok) break;
			// evaluate packet

			in_pk=(union packet *)&buf;
			switch (in_pk->cmd) {
				case CM_ERROR: {			// server error
					switch (in_pk->error.error) {
					case ER_UABORT:			// user aborts on client
						fprintf(stderr,"\nUser abort on %s, discarding "
						        "client.\n",inet_ntoa(server_in.sin_addr));
						remove_client(client_db,server_in.sin_addr);
						break;
					case ER_ERRNO:			// internal client error
						fprintf(stderr,"\nClient error: %s\n",
						        strerror(in_pk->error.er_no));
						exit(1);
					default:				// what was that?!?
						fprintf(stderr,"\nUnknown client error.\n");
						exit(1);
					}
					break;
				}
				case CM_RXOK:
					for (x=0;x<num_clients;x++) {
						if (client_db[x].s_addr==server_in.sin_addr.s_addr)
							clients++;
					}
					if (opt_onlyone) clients=num_clients;
					break;
				case CM_RXERR:
					fprintf(stderr,"\n%s: ",inet_ntoa(server_in.sin_addr));
					switch (in_pk->rxerr.error) {
						case RX_CRC:
							fprintf(stderr,"CRC error, will retry\n");
							crc_ok=0;
							break;
						case RX_SEQ:
							fprintf(stderr,"FATAL: Sequence error, "
							        "will discard client.\n");
							remove_client(client_db,server_in.sin_addr);
							break;
						case RX_DATA:
							fprintf(stderr,"Data error, will retry\n");
							break;
						default:
							fprintf(stderr,"FATAL: Unknown error, "
							        "will discard client.\n");
							remove_client(client_db,server_in.sin_addr);
					}
			}

			// missing clients ?
			if ((timer_sec()>=opt_timeout)&&(clients<num_clients)) {
				v_printf("\nMissing client replies, retrying.\n");
				crc_ok=0;
			}
		} while(clients<num_clients);

		if(crc_ok) 
			v_printf("%08u: %8u bytes transferred",seqnum++,length+=pk.tx.len);
	}
	
	// Last packet thru, send EOT
	v_printf("\nDone, sending EOT...");

	pk.cmd=CM_EOT;
	t_sendto(s,&pk,S_EOT,0,(struct sockaddr *)&server_out,sockaddr_len);
	
	// Wait for DONE message
	do {
		server_in.sin_family		= AF_INET;
		server_in.sin_port			= htons(opt_port+1);
		server_in.sin_addr.s_addr	= INADDR_ANY;

		if ((length=recvfrom(r,&buf,256,0,
			(struct sockaddr *)&server_in,&sockaddr_len))!=-1) {
			if(buf[0]==CM_DONE) {
				for(x=0;x<num_clients;x++) {
					if(client_db[x].s_addr==server_in.sin_addr.s_addr)
						break;
				}
			}
		}
	} while(clients<num_clients);
	
	v_printf(" transaction sequence completed.\n");
}
