shoot.c

00001 /*
00002 shot written by ashhar farhan, is not bound by any licensing at all.
00003 you are free to use this code as you deem fit. just dont blame the author
00004 for any problems you may have using it.
00005 bouquets and brickbats to farhan@hotfoon.com
00006  */
00007 
00008 
00009 /* changes by jiri@iptel.org; now messages can be really received;
00010    status code returned is 2 for some local errors , 0 for success
00011    and 1 for remote error -- ICMP/timeout; can be used to test if
00012    a server is alive; 1xx messages are now ignored; windows support
00013    dropped
00014 */
00015 
00016 #include <stdlib.h>
00017 #include <stdio.h>
00018 #include <sys/types.h>
00019 #include <sys/time.h>
00020 #include <string.h>
00021 #include <ctype.h>
00022 #include <time.h>
00023 #include <unistd.h>
00024 #include <netdb.h>
00025 #include <sys/socket.h>
00026 
00027 #include <regex.h>
00028 regex_t* regexp;
00029 
00030 #define RESIZE          1024
00031 
00032 /* take either a dot.decimal string of ip address or a 
00033 domain name and returns a NETWORK ordered long int containing
00034 the address. i chose to internally represent the address as long for speedier
00035 comparisons.
00036 
00037 any changes to getaddress have to be patched back to the net library.
00038 contact: farhan@hotfoon.com
00039 
00040   returns zero if there is an error.
00041   this is convenient as 0 means 'this' host and the traffic of
00042   a badly behaving dns system remains inside (you send to 0.0.0.0)
00043 */
00044 
00045 long getaddress(char *host)
00046 {
00047         int i, dotcount=0;
00048         char *p = host;
00049         struct hostent* pent;
00050         long l, *lp;
00051 
00052         /*try understanding if this is a valid ip address
00053         we are skipping the values of the octets specified here.
00054         for instance, this code will allow 952.0.320.567 through*/
00055         while (*p)
00056         {
00057                 for (i = 0; i < 3; i++, p++)
00058                         if (!isdigit(*p))
00059                                 break;
00060                 if (*p != '.')
00061                         break;
00062                 p++;
00063                 dotcount++;
00064         }
00065 
00066         /* three dots with up to three digits in before, between and after ? */
00067         if (dotcount == 3 && i > 0 && i <= 3)
00068                 return inet_addr(host);
00069 
00070         /* try the system's own resolution mechanism for dns lookup:
00071          required only for domain names.
00072          inspite of what the rfc2543 :D Using SRV DNS Records recommends,
00073          we are leaving it to the operating system to do the name caching.
00074 
00075          this is an important implementation issue especially in the light
00076          dynamic dns servers like dynip.com or dyndns.com where a dial
00077          ip address is dynamically assigned a sub domain like farhan.dynip.com
00078 
00079          although expensive, this is a must to allow OS to take
00080          the decision to expire the DNS records as it deems fit.
00081         */
00082         pent = gethostbyname(host);
00083         if (!pent) {
00084                 perror("no gethostbyname");
00085                 exit(2);
00086         }
00087 
00088         lp = (long *) (pent->h_addr);
00089         l = *lp;
00090         return l;
00091 }
00092 
00093 
00094 /*
00095 shoot:
00096 takes:
00097         1. the text message of buff to 
00098         2. the address (network ordered byte order)
00099         3. and port (not network byte ordered).
00100 
00101 starting from half a second, times-out on replies and
00102 keeps retrying with exponential back-off that flattens out
00103 at 5 seconds (5000 milliseconds).
00104 
00105 * Does not stop sending unless a final response is received.
00106 we are detecting the final response without a '1' as the first
00107 letter.
00108 */
00109 void shoot(char *buff, long address, int lport, int rport )
00110 {
00111         struct sockaddr_in      addr;
00112         /* jku - b  server structures */
00113         struct sockaddr_in      sockname;
00114         int ssock;
00115         /*
00116         char compiledre[ RESIZE ];
00117         */
00118         /* jku - e */
00119         int retryAfter = 500, i, len, ret;
00120         int     nretries = 10;
00121         int     sock;
00122         struct timeval  tv;
00123         fd_set  fd;
00124         char    reply[1600];
00125 
00126         /* create a socket */
00127         sock = (int)socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
00128         if (sock==-1) {
00129                 perror("no client socket");
00130                 exit(2);
00131         }
00132 
00133         /* jku - b */
00134         ssock = (int)socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
00135         if (sock==-1) {
00136                 perror("no server socket");
00137                 exit(2);
00138         }
00139 
00140         sockname.sin_family=AF_INET;
00141         sockname.sin_addr.s_addr = htonl( INADDR_ANY );
00142         sockname.sin_port = htons((short)lport);
00143         if (bind( ssock, (struct sockaddr *) &sockname, sizeof(sockname) )==-1) {
00144                 perror("no bind");
00145                 exit(2);
00146         }
00147 
00148         /* should capture: SIP/2.0 100 Trying */
00149         /* compile("^SIP/[0-9]\\.[0-9] 1[0-9][0-9] ", compiledre, &compiledre[RESIZE], '\0'); */
00150         regexp=(regex_t*)malloc(sizeof(regex_t));
00151         regcomp(regexp, "^SIP/[0-9]\\.[0-9] 1[0-9][0-9] ", REG_EXTENDED|REG_NOSUB|REG_ICASE); 
00152         
00153 
00154         /* jku - e */
00155 
00156         addr.sin_addr.s_addr = address;
00157         addr.sin_port = htons((short)rport);
00158         addr.sin_family = AF_INET;
00159         
00160         /* we connect as per the RFC 2543 recommendations
00161         modified from sendto/recvfrom */
00162 
00163         ret = connect(sock, (struct sockaddr *)&addr, sizeof(addr));
00164         if (ret==-1) {
00165                 perror("no connect");
00166                 exit(2);
00167         }
00168         /* jku - e */
00169 
00170         for (i = 0; i < nretries; i++)
00171         {
00172                 puts("/* request */");
00173                 puts(buff);
00174                 putchar('\n');
00175 
00176                 ret = send(sock, buff, strlen(buff), 0);
00177                 if (ret==-1) {
00178                         perror("send failure");
00179                         exit( 1 );
00180                 }
00181                 
00182 
00183                 tv.tv_sec = retryAfter/1000;
00184                 tv.tv_usec = (retryAfter % 1000) * 1000;
00185 
00186                 FD_ZERO(&fd);
00187                 FD_SET(ssock, &fd); 
00188 
00189                 /* TO-DO: there does appear to be a problem with this select returning a zero
00190                 even when there is data pending in the recv queue. 
00191                 please help, someone! */
00192 
00193                 ret = select(6, &fd, NULL, NULL, &tv);
00194                 if (ret == 0)
00195                 {
00196                         puts("\n/* timeout */\n");
00197                         retryAfter = retryAfter * 2;
00198                         if (retryAfter > 5000)
00199                                 retryAfter = 5000;
00200                         /* we should have retrieved the error code and displayed
00201                         we are not doing that because there is a great variation
00202                         in the process of retrieving error codes between
00203                         micro$oft and *nix world*/
00204                         continue;
00205                 } else if ( ret == -1 ) {
00206                         perror("select error");
00207                         exit(2);
00208                 } /* no timeout, no error ... something has happened :-) */
00209                  else if (FD_ISSET(ssock, &fd)) {
00210                         puts ("\nmessage received\n");
00211                 } else {
00212                         puts("\nselect returned successfully, nothing received\n");
00213                         continue;
00214                 }
00215 
00216                 /* we are retrieving only the extend of a decent MSS = 1500 bytes */
00217                 len = sizeof(addr);
00218                 ret = recv(ssock, reply, 1500, 0);
00219                 if(ret > 0)
00220                 {
00221                         reply[ret] = 0;
00222                         puts("/* reply */");
00223                         puts(reply);
00224                         putchar('\n');
00225                         /* if (step( reply, compiledre )) { */
00226                         if (regexec((regex_t*)regexp, reply, 0, 0, 0)==0) {
00227                                 puts(" provisional received; still waiting for a final response\n ");
00228                                 continue;
00229                         } else {
00230                                 puts(" final received; congratulations!\n ");
00231                                 exit(0);
00232                         }
00233                 
00234                 } 
00235                 else    {
00236                         perror("recv error");
00237                         exit(2);
00238                         }
00239         }
00240         /* after all the retries, nothing has come back :-( */
00241         puts("/* I give up retransmission....");
00242         exit(1);
00243 }
00244 
00245 int main(int argc, char *argv[])
00246 {
00247         long    address;
00248         FILE    *pf;
00249         char    buff[1600];
00250         int             length;
00251         int     lport=0;
00252         int     rport=5060;
00253 
00254         if (! (argc >= 3 && argc <= 5))
00255         {
00256                 puts("usage: shoot file host [rport] [lport]");
00257                 exit(2);
00258         }
00259 
00260         address = getaddress(argv[2]);
00261         if (!address)
00262         {
00263                 puts("error:unable to determine the remote host address.");
00264                 exit(2);
00265         }
00266 
00267         /* take the port as 5060 even if it is incorrectly specified */
00268         if (argc >= 4)
00269         {
00270                 rport = atoi(argv[3]);
00271                 if (!rport) {
00272                         puts("error: non-numerical remote port number");
00273                         exit(1);
00274                 }
00275                 if (argc==5) {
00276                         lport=atoi(argv[4]);
00277                         if (!lport) {
00278                                 puts("error: non-numerical local port number");
00279                                 exit(1);
00280                         }
00281                 }
00282         }
00283 
00284         /* file is opened in binary mode so that the cr-lf is preserved */
00285         pf = fopen(argv[1], "rb");
00286         if (!pf)
00287         {
00288                 puts("unable to open the file.\n");
00289                 return 1;
00290         }
00291         length  = fread(buff, 1, sizeof(buff), pf);
00292         if (length >= sizeof(buff))
00293         {
00294                 puts("error:the file is too big. try files of less than 1500 bytes.");
00295                 return 1;
00296         }
00297         fclose(pf);
00298         buff[length] = 0;
00299 
00300         shoot(buff, address, lport, rport );
00301 
00302         /* visual studio closes the debug console as soon as the 
00303         program terminates. this is to hold the window from collapsing
00304         Uncomment it if needed.
00305         getchar();*/
00306         
00307 
00308         return 0;
00309 }
00310 
00311 
00312 /*
00313 shoot will exercise the all types of sip servers.
00314 it is not to be used to measure round-trips and general connectivity.
00315 use ping for that. 
00316 written by farhan on 10th august, 2000.
00317 
00318 TO-DO:
00319 1. replace the command line arguments with just a sip url like this:
00320         shoot invite.txt sip:farhan@sip.hotfoon.com:5060
00321 
00322 2. understand redirect response and retransmit to the redirected server.
00323 
00324 */
00325