test/shoot.c
42669f7d
 /*
 shot written by ashhar farhan, is not bound by any licensing at all.
 you are free to use this code as you deem fit. just dont blame the author
 for any problems you may have using it.
 bouquets and brickbats to farhan@hotfoon.com
7dd0b342
  */
 
42669f7d
 
 /* changes by jiri@iptel.org; now messages can be really received;
    status code returned is 2 for some local errors , 0 for success
    and 1 for remote error -- ICMP/timeout; can be used to test if
f571aa35
    a server is alive; 1xx messages are now ignored; windows support
    dropped
42669f7d
 */
 
f571aa35
 #include <stdlib.h>
42669f7d
 #include <stdio.h>
f571aa35
 #include <sys/types.h>
 #include <sys/time.h>
42669f7d
 #include <string.h>
 #include <ctype.h>
 #include <time.h>
 #include <unistd.h>
 #include <netdb.h>
 #include <sys/socket.h>
f571aa35
 
 #include <regex.h>
 regex_t* regexp;
42669f7d
 
 #define RESIZE		1024
 
 /* take either a dot.decimal string of ip address or a 
 domain name and returns a NETWORK ordered long int containing
 the address. i chose to internally represent the address as long for speedier
c32feee5
 comparisons.
42669f7d
 
 any changes to getaddress have to be patched back to the net library.
 contact: farhan@hotfoon.com
 
   returns zero if there is an error.
   this is convenient as 0 means 'this' host and the traffic of
   a badly behaving dns system remains inside (you send to 0.0.0.0)
 */
 
 long getaddress(char *host)
 {
 	int i, dotcount=0;
 	char *p = host;
f571aa35
 	struct hostent* pent;
 	long l, *lp;
42669f7d
 
 	/*try understanding if this is a valid ip address
 	we are skipping the values of the octets specified here.
 	for instance, this code will allow 952.0.320.567 through*/
 	while (*p)
 	{
 		for (i = 0; i < 3; i++, p++)
 			if (!isdigit(*p))
 				break;
 		if (*p != '.')
 			break;
 		p++;
 		dotcount++;
 	}
 
c32feee5
 	/* three dots with up to three digits in before, between and after ? */
42669f7d
 	if (dotcount == 3 && i > 0 && i <= 3)
 		return inet_addr(host);
 
 	/* try the system's own resolution mechanism for dns lookup:
 	 required only for domain names.
 	 inspite of what the rfc2543 :D Using SRV DNS Records recommends,
 	 we are leaving it to the operating system to do the name caching.
 
c32feee5
 	 this is an important implementation issue especially in the light
42669f7d
 	 dynamic dns servers like dynip.com or dyndns.com where a dial
 	 ip address is dynamically assigned a sub domain like farhan.dynip.com
 
 	 although expensive, this is a must to allow OS to take
 	 the decision to expire the DNS records as it deems fit.
 	*/
 	pent = gethostbyname(host);
 	if (!pent) {
 		perror("no gethostbyname");
 		exit(2);
 	}
 
f571aa35
 	lp = (long *) (pent->h_addr);
 	l = *lp;
 	return l;
42669f7d
 }
 
 
 /*
 shoot:
 takes:
 	1. the text message of buff to 
c32feee5
 	2. the address (network ordered byte order)
42669f7d
 	3. and port (not network byte ordered).
 
 starting from half a second, times-out on replies and
 keeps retrying with exponential back-off that flattens out
 at 5 seconds (5000 milliseconds).
 
 * Does not stop sending unless a final response is received.
 we are detecting the final response without a '1' as the first
 letter.
 */
f571aa35
 void shoot(char *buff, long address, int lport, int rport )
42669f7d
 {
 	struct sockaddr_in	addr;
 	/* jku - b  server structures */
 	struct sockaddr_in	sockname;
 	int ssock;
f571aa35
 	/*
42669f7d
 	char compiledre[ RESIZE ];
f571aa35
 	*/
42669f7d
 	/* jku - e */
 	int retryAfter = 500, i, len, ret;
 	int	nretries = 10;
 	int	sock;
f571aa35
 	struct timeval	tv;
42669f7d
 	fd_set	fd;
 	char	reply[1600];
 
 	/* create a socket */
 	sock = (int)socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
 	if (sock==-1) {
 		perror("no client socket");
 		exit(2);
 	}
 
 	/* jku - b */
 	ssock = (int)socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
 	if (sock==-1) {
 		perror("no server socket");
 		exit(2);
 	}
 
 	sockname.sin_family=AF_INET;
 	sockname.sin_addr.s_addr = htonl( INADDR_ANY );
f571aa35
 	sockname.sin_port = htons((short)lport);
 	if (bind( ssock, (struct sockaddr *) &sockname, sizeof(sockname) )==-1) {
42669f7d
 		perror("no bind");
 		exit(2);
 	}
 
 	/* should capture: SIP/2.0 100 Trying */
f571aa35
 	/* compile("^SIP/[0-9]\\.[0-9] 1[0-9][0-9] ", compiledre, &compiledre[RESIZE], '\0'); */
 	regexp=(regex_t*)malloc(sizeof(regex_t));
 	regcomp(regexp, "^SIP/[0-9]\\.[0-9] 1[0-9][0-9] ", REG_EXTENDED|REG_NOSUB|REG_ICASE); 
42669f7d
 	
 
 	/* jku - e */
 
 	addr.sin_addr.s_addr = address;
f571aa35
 	addr.sin_port = htons((short)rport);
42669f7d
 	addr.sin_family = AF_INET;
 	
 	/* we connect as per the RFC 2543 recommendations
 	modified from sendto/recvfrom */
 
f571aa35
 	ret = connect(sock, (struct sockaddr *)&addr, sizeof(addr));
42669f7d
 	if (ret==-1) {
 		perror("no connect");
 		exit(2);
 	}
 	/* jku - e */
 
 	for (i = 0; i < nretries; i++)
 	{
 		puts("/* request */");
 		puts(buff);
 		putchar('\n');
 
 		ret = send(sock, buff, strlen(buff), 0);
 		if (ret==-1) {
 			perror("send failure");
 			exit( 1 );
 		}
 		
 
 		tv.tv_sec = retryAfter/1000;
 		tv.tv_usec = (retryAfter % 1000) * 1000;
 
 		FD_ZERO(&fd);
 		FD_SET(ssock, &fd); 
 
 		/* TO-DO: there does appear to be a problem with this select returning a zero
 		even when there is data pending in the recv queue. 
 		please help, someone! */
 
 		ret = select(6, &fd, NULL, NULL, &tv);
 		if (ret == 0)
 		{
 			puts("\n/* timeout */\n");
 			retryAfter = retryAfter * 2;
 			if (retryAfter > 5000)
 				retryAfter = 5000;
 			/* we should have retrieved the error code and displayed
 			we are not doing that because there is a great variation
c32feee5
 			in the process of retrieving error codes between
42669f7d
 			micro$oft and *nix world*/
 			continue;
 		} else if ( ret == -1 ) {
 			perror("select error");
 			exit(2);
 		} /* no timeout, no error ... something has happened :-) */
                  else if (FD_ISSET(ssock, &fd)) {
 			puts ("\nmessage received\n");
 		} else {
c32feee5
 			puts("\nselect returned successfully, nothing received\n");
42669f7d
 			continue;
 		}
 
 		/* we are retrieving only the extend of a decent MSS = 1500 bytes */
 		len = sizeof(addr);
 		ret = recv(ssock, reply, 1500, 0);
 		if(ret > 0)
 		{
 			reply[ret] = 0;
 			puts("/* reply */");
 			puts(reply);
 			putchar('\n');
f571aa35
 			/* if (step( reply, compiledre )) { */
 			if (regexec((regex_t*)regexp, reply, 0, 0, 0)==0) {
42669f7d
 				puts(" provisional received; still waiting for a final response\n ");
 				continue;
 			} else {
 				puts(" final received; congratulations!\n ");
 				exit(0);
 			}
 		
 		} 
 		else	{
 			perror("recv error");
 			exit(2);
 			}
 	}
 	/* after all the retries, nothing has come back :-( */
 	puts("/* I give up retransmission....");
 	exit(1);
 }
 
 int main(int argc, char *argv[])
 {
 	long	address;
 	FILE	*pf;
 	char	buff[1600];
 	int		length;
f571aa35
 	int	lport=0;
 	int	rport=5060;
42669f7d
 
f571aa35
 	if (! (argc >= 3 && argc <= 5))
42669f7d
 	{
f571aa35
 		puts("usage: shoot file host [rport] [lport]");
42669f7d
 		exit(2);
 	}
 
 	address = getaddress(argv[2]);
 	if (!address)
 	{
 		puts("error:unable to determine the remote host address.");
 		exit(2);
 	}
 
 	/* take the port as 5060 even if it is incorrectly specified */
f571aa35
 	if (argc >= 4)
42669f7d
 	{
f571aa35
 		rport = atoi(argv[3]);
 		if (!rport) {
 			puts("error: non-numerical remote port number");
 			exit(1);
 		}
 		if (argc==5) {
 			lport=atoi(argv[4]);
 			if (!lport) {
 				puts("error: non-numerical local port number");
 				exit(1);
 			}
 		}
42669f7d
 	}
 
 	/* file is opened in binary mode so that the cr-lf is preserved */
 	pf = fopen(argv[1], "rb");
 	if (!pf)
 	{
 		puts("unable to open the file.\n");
 		return 1;
 	}
 	length  = fread(buff, 1, sizeof(buff), pf);
 	if (length >= sizeof(buff))
 	{
 		puts("error:the file is too big. try files of less than 1500 bytes.");
 		return 1;
 	}
 	fclose(pf);
 	buff[length] = 0;
 
f571aa35
 	shoot(buff, address, lport, rport );
42669f7d
 
 	/* visual studio closes the debug console as soon as the 
 	program terminates. this is to hold the window from collapsing
 	Uncomment it if needed.
 	getchar();*/
 	
 
 	return 0;
 }
 
 
 /*
 shoot will exercise the all types of sip servers.
 it is not to be used to measure round-trips and general connectivity.
 use ping for that. 
 written by farhan on 10th august, 2000.
 
 TO-DO:
 1. replace the command line arguments with just a sip url like this:
 	shoot invite.txt sip:farhan@sip.hotfoon.com:5060
 
 2. understand redirect response and retransmit to the redirected server.
 
 */