shoot2.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 <sys/utsname.h>
00028 
00029 #include <regex.h>
00030 regex_t* regexp;
00031 
00032 #define RESIZE          1024
00033 #define BUFSIZE         1600
00034 #define VIA_BEGIN_STR "Via: SIP/2.0/UDP "
00035 #define VIA_BEGIN_STR_LEN 17
00036 
00037 /* take either a dot.decimal string of ip address or a 
00038 domain name and returns a NETWORK ordered long int containing
00039 the address. i chose to internally represent the address as long for speedier
00040 comparisons.
00041 
00042 any changes to getaddress have to be patched back to the net library.
00043 contact: farhan@hotfoon.com
00044 
00045   returns zero if there is an error.
00046   this is convenient as 0 means 'this' host and the traffic of
00047   a badly behaving dns system remains inside (you send to 0.0.0.0)
00048 */
00049 
00050 long getaddress(char *host)
00051 {
00052         int i, dotcount=0;
00053         char *p = host;
00054         struct hostent* pent;
00055         long l, *lp;
00056 
00057         /*try understanding if this is a valid ip address
00058         we are skipping the values of the octets specified here.
00059         for instance, this code will allow 952.0.320.567 through*/
00060         while (*p)
00061         {
00062                 for (i = 0; i < 3; i++, p++)
00063                         if (!isdigit(*p))
00064                                 break;
00065                 if (*p != '.')
00066                         break;
00067                 p++;
00068                 dotcount++;
00069         }
00070 
00071         /* three dots with up to three digits in before, between and after ? */
00072         if (dotcount == 3 && i > 0 && i <= 3)
00073                 return inet_addr(host);
00074 
00075         /* try the system's own resolution mechanism for dns lookup:
00076          required only for domain names.
00077          inspite of what the rfc2543 :D Using SRV DNS Records recommends,
00078          we are leaving it to the operating system to do the name caching.
00079 
00080          this is an important implementation issue especially in the light
00081          dynamic dns servers like dynip.com or dyndns.com where a dial
00082          ip address is dynamically assigned a sub domain like farhan.dynip.com
00083 
00084          although expensive, this is a must to allow OS to take
00085          the decision to expire the DNS records as it deems fit.
00086         */
00087         pent = gethostbyname(host);
00088         if (!pent) {
00089                 perror("no gethostbyname");
00090                 exit(2);
00091         }
00092 
00093         lp = (long *) (pent->h_addr);
00094         l = *lp;
00095         return l;
00096 }
00097 
00098 /* This function tries to add a Via Header Field in the message. */
00099 add_via(char *mes)
00100 {
00101         struct utsname myname;
00102         char *via_line, *via, *backup;
00103 
00104         /* get our address, only the first one */
00105         if (uname (&myname) <0){
00106                 printf("cannot determine hostname\n");
00107                 exit(2);
00108         }
00109 #ifdef DEBUG
00110         printf("determined hostname: %s\n", myname.nodename);
00111 #endif
00112 
00113         via_line = malloc(VIA_BEGIN_STR_LEN + strlen(myname.nodename) + 3);
00114         strcat(via_line, VIA_BEGIN_STR);
00115         strcat(via_line, myname.nodename);
00116         strcat(via_line, "\r\n");
00117 #ifdef DEBUG
00118         printf("our Via-Line: %s\n", via_line);
00119 #endif
00120 
00121         if (strlen(mes)+strlen(via_line)>= BUFSIZE){
00122                 printf("can't add our Via Header Line because file is too big\n");
00123                 exit(2);
00124         }
00125         if ((via=strstr(mes,"Via:"))==NULL){
00126                 /* We doesn't find a Via so we insert our via
00127                    direct after the first line. */
00128                 via=strchr(mes,'\n');
00129                 via++;
00130         }
00131         backup=malloc(strlen(via));
00132         strncpy(backup, via, strlen(via));
00133         strncpy(via, via_line, strlen(via_line));
00134         strncpy(via+strlen(via_line), backup, strlen(backup));
00135 #ifdef DEBUG
00136         printf("New message:\n%s", mes);
00137 #endif
00138 }
00139 
00140 
00141 /*
00142 shoot:
00143 takes:
00144         1. the text message of buff to 
00145         2. the address (network ordered byte order)
00146         3. and port (not network byte ordered).
00147 
00148 starting from half a second, times-out on replies and
00149 keeps retrying with exponential back-off that flattens out
00150 at 5 seconds (5000 milliseconds).
00151 
00152 * Does not stop sending unless a final response is received.
00153 we are detecting the final response without a '1' as the first
00154 letter.
00155 */
00156 void shoot(char *buff, long address, int lport, int rport )
00157 {
00158         struct sockaddr_in      addr;
00159         /* jku - b  server structures */
00160         struct sockaddr_in      sockname;
00161         int ssock;
00162         /*
00163         char compiledre[ RESIZE ];
00164         */
00165         /* jku - e */
00166         int retryAfter = 500;
00167         int     nretries = 10;
00168         int sock, i, len, ret;
00169         struct timeval  tv;
00170         fd_set  fd;
00171         char    reply[1600];
00172 
00173         /* create a socket */
00174         sock = (int)socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
00175         if (sock==-1) {
00176                 perror("no client socket");
00177                 exit(2);
00178         }
00179 
00180         /* jku - b */
00181         ssock = (int)socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
00182         if (ssock==-1) {
00183                 perror("no server socket");
00184                 exit(2);
00185         }
00186 
00187         sockname.sin_family=AF_INET;
00188         sockname.sin_addr.s_addr = htonl( INADDR_ANY );
00189         sockname.sin_port = htons((short)lport);
00190         if (bind( ssock, (struct sockaddr *) &sockname, sizeof(sockname) )==-1) {
00191                 perror("no bind");
00192                 exit(2);
00193         }
00194 
00195         /* should capture: SIP/2.0 100 Trying */
00196         /* compile("^SIP/[0-9]\\.[0-9] 1[0-9][0-9] ", compiledre, &compiledre[RESIZE], '\0'); */
00197         regexp=(regex_t*)malloc(sizeof(regex_t));
00198         regcomp(regexp, "^SIP/[0-9]\\.[0-9] 1[0-9][0-9] ", REG_EXTENDED|REG_NOSUB|REG_ICASE); 
00199         
00200 
00201         /* jku - e */
00202 
00203         addr.sin_addr.s_addr = address;
00204         addr.sin_port = htons((short)rport);
00205         addr.sin_family = AF_INET;
00206         
00207         /* we connect as per the RFC 2543 recommendations
00208         modified from sendto/recvfrom */
00209 
00210         ret = connect(sock, (struct sockaddr *)&addr, sizeof(addr));
00211         if (ret==-1) {
00212                 perror("no connect");
00213                 exit(2);
00214         }
00215         /* jku - e */
00216 
00217         add_via(buff);
00218         
00219         for (i = 0; i < nretries; i++)
00220         {
00221                 puts("/* request */");
00222                 puts(buff);
00223                 putchar('\n');
00224 
00225                 ret = send(sock, buff, strlen(buff), 0);
00226                 if (ret==-1) {
00227                         perror("send failure");
00228                         exit( 1 );
00229                 }
00230                 
00231 
00232                 tv.tv_sec = retryAfter/1000;
00233                 tv.tv_usec = (retryAfter % 1000) * 1000;
00234 
00235                 FD_ZERO(&fd);
00236                 FD_SET(ssock, &fd); 
00237 
00238                 /* TO-DO: there does appear to be a problem with this select returning a zero
00239                 even when there is data pending in the recv queue. 
00240                 please help, someone! */
00241 
00242                 ret = select(6, &fd, NULL, NULL, &tv);
00243                 if (ret == 0)
00244                 {
00245                         puts("\n/* timeout */\n");
00246                         retryAfter = retryAfter * 2;
00247                         if (retryAfter > 5000)
00248                                 retryAfter = 5000;
00249                         /* we should have retrieved the error code and displayed
00250                         we are not doing that because there is a great variation
00251                         in the process of retrieving error codes between
00252                         micro$oft and *nix world*/
00253                         continue;
00254                 } else if ( ret == -1 ) {
00255                         perror("select error");
00256                         exit(2);
00257                 } /* no timeout, no error ... something has happened :-) */
00258                  else if (FD_ISSET(ssock, &fd)) {
00259                         puts ("\nmessage received\n");
00260                 } else {
00261                         puts("\nselect returned successfully, nothing received\n");
00262                         continue;
00263                 }
00264 
00265                 /* we are retrieving only the extend of a decent MSS = 1500 bytes */
00266                 len = sizeof(addr);
00267                 ret = recv(ssock, reply, 1500, 0);
00268                 if(ret > 0)
00269                 {
00270                         reply[ret] = 0;
00271                         puts("/* reply */");
00272                         puts(reply);
00273                         putchar('\n');
00274                         /* if (step( reply, compiledre )) { */
00275                         if (regexec((regex_t*)regexp, reply, 0, 0, 0)==0) {
00276                                 puts(" provisional received; still waiting for a final response\n ");
00277                                 continue;
00278                         } else {
00279                                 puts(" final received; congratulations!\n ");
00280                                 exit(0);
00281                         }
00282                 
00283                 } 
00284                 else    {
00285                         perror("recv error");
00286                         exit(2);
00287                         }
00288         }
00289         /* after all the retries, nothing has come back :-( */
00290         puts("/* I give up retransmission....");
00291         exit(1);
00292 }
00293 
00294 int main(int argc, char *argv[])
00295 {
00296         long    address;
00297         FILE    *pf;
00298         char    buff[BUFSIZE];
00299         int             length;
00300         int             lport=0;
00301         int             rport=5060;
00302         char    *delim, *delim2;
00303 
00304         if (! (argc >= 3 && argc <= 5))
00305         {
00306                 puts("usage: shoot file host rport [lport]");
00307                 puts("usage: shoot file sip:[user@]hostname[:rport]");
00308                 exit(2);
00309         }
00310 
00311         /* support for sip:uri added by noh */
00312         if (argc==3){
00313                 if ((delim=strchr(argv[2],':'))!=NULL){
00314                         delim++;
00315                         if (!strncmp(argv[2],"sip",3)){
00316                                 if ((delim2=strchr(delim,'@'))!=NULL){
00317                                         /* we don't need the username */
00318                                         delim2++;
00319                                         delim=delim2;
00320                                 }
00321                                 if ((delim2=strchr(delim,':'))!=NULL){
00322                                         *delim2 = '\0';
00323                                         delim2++;
00324                                         rport = atoi(delim2);
00325                                         if (!rport) {
00326                                                 puts("error: non-numerical remote port number");
00327                                                 exit(2);
00328                                         }
00329                                 }
00330                                 address = getaddress(delim);
00331                                 if (!address){
00332                                         puts("error:unable to determine the remote host address.");
00333                                         exit(2);
00334                                 }
00335                         }
00336                         else{
00337                                 puts("sip:uri doesn't not begin with sip");
00338                                 exit(2);
00339                         }
00340                 }
00341                 else{
00342                         puts("sip:uri doesn't contain a : ?!");
00343                         exit(2);
00344                 }
00345         }
00346         else{
00347                 address = getaddress(argv[2]);
00348                 if (!address){
00349                         puts("error:unable to determine the remote host address.");
00350                         exit(2);
00351                 }
00352 
00353                 /* take the port as 5060 even if it is incorrectly specified */
00354                 if (argc >= 4){
00355                         rport = atoi(argv[3]);
00356                         if (!rport) {
00357                                 puts("error: non-numerical remote port number");
00358                                 exit(2);
00359                         }
00360                         if (argc==5) {
00361                                 lport=atoi(argv[4]);
00362                                 if (!lport) {
00363                                         puts("error: non-numerical local port number");
00364                                         exit(2);
00365                                 }
00366                         }
00367                 }
00368         }
00369 
00370         /* file is opened in binary mode so that the cr-lf is preserved */
00371         pf = fopen(argv[1], "rb");
00372         if (!pf)
00373         {
00374                 puts("unable to open the file.\n");
00375                 return 1;
00376         }
00377         length  = fread(buff, 1, sizeof(buff), pf);
00378         if (length >= sizeof(buff))
00379         {
00380                 printf("error:the file is too big. try files of less than %i bytes.\n", BUFSIZE);
00381                 puts("      or recompile the program with bigger BUFSIZE defined.");
00382                 return 1;
00383         }
00384         fclose(pf);
00385         buff[length] = 0;
00386 
00387         shoot(buff, address, lport, rport );
00388 
00389         /* visual studio closes the debug console as soon as the 
00390         program terminates. this is to hold the window from collapsing
00391         Uncomment it if needed.
00392         getchar();*/
00393         
00394 
00395         return 0;
00396 }
00397 
00398 
00399 /*
00400 shoot will exercise the all types of sip servers.
00401 it is not to be used to measure round-trips and general connectivity.
00402 use ping for that. 
00403 written by farhan on 10th august, 2000.
00404 
00405 TO-DO:
00406 2. understand redirect response and retransmit to the redirected server.
00407 
00408 */
00409