GPLization banner introduced to *.[hc] files
[sip-router] / test / shoot2.c
1 /*
2 shot written by ashhar farhan, is not bound by any licensing at all.
3 you are free to use this code as you deem fit. just dont blame the author
4 for any problems you may have using it.
5 bouquets and brickbats to farhan@hotfoon.com
6  */
7
8
9 /* changes by jiri@iptel.org; now messages can be really received;
10    status code returned is 2 for some local errors , 0 for success
11    and 1 for remote error -- ICMP/timeout; can be used to test if
12    a server is alive; 1xx messages are now ignored; windows support
13    dropped
14 */
15
16 #include <stdlib.h>
17 #include <stdio.h>
18 #include <sys/types.h>
19 #include <sys/time.h>
20 #include <string.h>
21 #include <ctype.h>
22 #include <time.h>
23 #include <unistd.h>
24 #include <netdb.h>
25 #include <sys/socket.h>
26
27 #include <sys/utsname.h>
28
29 #include <regex.h>
30 regex_t* regexp;
31
32 #define RESIZE          1024
33 #define BUFSIZE         1600
34 #define VIA_BEGIN_STR "Via: SIP/2.0/UDP "
35 #define VIA_BEGIN_STR_LEN 17
36
37 /* take either a dot.decimal string of ip address or a 
38 domain name and returns a NETWORK ordered long int containing
39 the address. i chose to internally represent the address as long for speedier
40 comparisions.
41
42 any changes to getaddress have to be patched back to the net library.
43 contact: farhan@hotfoon.com
44
45   returns zero if there is an error.
46   this is convenient as 0 means 'this' host and the traffic of
47   a badly behaving dns system remains inside (you send to 0.0.0.0)
48 */
49
50 long getaddress(char *host)
51 {
52         int i, dotcount=0;
53         char *p = host;
54         struct hostent* pent;
55         long l, *lp;
56
57         /*try understanding if this is a valid ip address
58         we are skipping the values of the octets specified here.
59         for instance, this code will allow 952.0.320.567 through*/
60         while (*p)
61         {
62                 for (i = 0; i < 3; i++, p++)
63                         if (!isdigit(*p))
64                                 break;
65                 if (*p != '.')
66                         break;
67                 p++;
68                 dotcount++;
69         }
70
71         /* three dots with upto three digits in before, between and after ? */
72         if (dotcount == 3 && i > 0 && i <= 3)
73                 return inet_addr(host);
74
75         /* try the system's own resolution mechanism for dns lookup:
76          required only for domain names.
77          inspite of what the rfc2543 :D Using SRV DNS Records recommends,
78          we are leaving it to the operating system to do the name caching.
79
80          this is an important implementational issue especially in the light
81          dynamic dns servers like dynip.com or dyndns.com where a dial
82          ip address is dynamically assigned a sub domain like farhan.dynip.com
83
84          although expensive, this is a must to allow OS to take
85          the decision to expire the DNS records as it deems fit.
86         */
87         pent = gethostbyname(host);
88         if (!pent) {
89                 perror("no gethostbyname");
90                 exit(2);
91         }
92
93         lp = (long *) (pent->h_addr);
94         l = *lp;
95         return l;
96 }
97
98 /* This function tries to add a Via Header Field in the message. */
99 add_via(char *mes)
100 {
101         struct utsname myname;
102         char *via_line, *via, *backup;
103
104         /* get our address, only the first one */
105         if (uname (&myname) <0){
106                 printf("cannot determine hostname\n");
107                 exit(2);
108         }
109 #ifdef DEBUG
110         printf("determined hostname: %s\n", myname.nodename);
111 #endif
112
113         via_line = malloc(VIA_BEGIN_STR_LEN + strlen(myname.nodename) + 3);
114         strcat(via_line, VIA_BEGIN_STR);
115         strcat(via_line, myname.nodename);
116         strcat(via_line, "\r\n");
117 #ifdef DEBUG
118         printf("our Via-Line: %s\n", via_line);
119 #endif
120
121         if (strlen(mes)+strlen(via_line)>= BUFSIZE){
122                 printf("can't add our Via Header Line because file is too big\n");
123                 exit(2);
124         }
125         if ((via=strstr(mes,"Via:"))==NULL){
126                 /* We doesn't find a Via so we insert our via
127                    direct after the first line. */
128                 via=strchr(mes,'\n');
129                 via++;
130         }
131         backup=malloc(strlen(via));
132         strncpy(backup, via, strlen(via));
133         strncpy(via, via_line, strlen(via_line));
134         strncpy(via+strlen(via_line), backup, strlen(backup));
135 #ifdef DEBUG
136         printf("New message:\n%s", mes);
137 #endif
138 }
139
140
141 /*
142 shoot:
143 takes:
144         1. the text message of buff to 
145         2. the address (network orderd byte order)
146         3. and port (not network byte ordered).
147
148 starting from half a second, times-out on replies and
149 keeps retrying with exponential back-off that flattens out
150 at 5 seconds (5000 milliseconds).
151
152 * Does not stop sending unless a final response is received.
153 we are detecting the final response without a '1' as the first
154 letter.
155 */
156 void shoot(char *buff, long address, int lport, int rport )
157 {
158         struct sockaddr_in      addr;
159         /* jku - b  server structures */
160         struct sockaddr_in      sockname;
161         int ssock;
162         /*
163         char compiledre[ RESIZE ];
164         */
165         /* jku - e */
166         int retryAfter = 500;
167         int     nretries = 10;
168         int sock, i, len, ret;
169         struct timeval  tv;
170         fd_set  fd;
171         char    reply[1600];
172
173         /* create a socket */
174         sock = (int)socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
175         if (sock==-1) {
176                 perror("no client socket");
177                 exit(2);
178         }
179
180         /* jku - b */
181         ssock = (int)socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
182         if (ssock==-1) {
183                 perror("no server socket");
184                 exit(2);
185         }
186
187         sockname.sin_family=AF_INET;
188         sockname.sin_addr.s_addr = htonl( INADDR_ANY );
189         sockname.sin_port = htons((short)lport);
190         if (bind( ssock, (struct sockaddr *) &sockname, sizeof(sockname) )==-1) {
191                 perror("no bind");
192                 exit(2);
193         }
194
195         /* should capture: SIP/2.0 100 Trying */
196         /* compile("^SIP/[0-9]\\.[0-9] 1[0-9][0-9] ", compiledre, &compiledre[RESIZE], '\0'); */
197         regexp=(regex_t*)malloc(sizeof(regex_t));
198         regcomp(regexp, "^SIP/[0-9]\\.[0-9] 1[0-9][0-9] ", REG_EXTENDED|REG_NOSUB|REG_ICASE); 
199         
200
201         /* jku - e */
202
203         addr.sin_addr.s_addr = address;
204         addr.sin_port = htons((short)rport);
205         addr.sin_family = AF_INET;
206         
207         /* we connect as per the RFC 2543 recommendations
208         modified from sendto/recvfrom */
209
210         ret = connect(sock, (struct sockaddr *)&addr, sizeof(addr));
211         if (ret==-1) {
212                 perror("no connect");
213                 exit(2);
214         }
215         /* jku - e */
216
217         add_via(buff);
218         
219         for (i = 0; i < nretries; i++)
220         {
221                 puts("/* request */");
222                 puts(buff);
223                 putchar('\n');
224
225                 ret = send(sock, buff, strlen(buff), 0);
226                 if (ret==-1) {
227                         perror("send failure");
228                         exit( 1 );
229                 }
230                 
231
232                 tv.tv_sec = retryAfter/1000;
233                 tv.tv_usec = (retryAfter % 1000) * 1000;
234
235                 FD_ZERO(&fd);
236                 FD_SET(ssock, &fd); 
237
238                 /* TO-DO: there does appear to be a problem with this select returning a zero
239                 even when there is data pending in the recv queue. 
240                 please help, someone! */
241
242                 ret = select(6, &fd, NULL, NULL, &tv);
243                 if (ret == 0)
244                 {
245                         puts("\n/* timeout */\n");
246                         retryAfter = retryAfter * 2;
247                         if (retryAfter > 5000)
248                                 retryAfter = 5000;
249                         /* we should have retrieved the error code and displayed
250                         we are not doing that because there is a great variation
251                         in the process of retrieveing error codes between
252                         micro$oft and *nix world*/
253                         continue;
254                 } else if ( ret == -1 ) {
255                         perror("select error");
256                         exit(2);
257                 } /* no timeout, no error ... something has happened :-) */
258                  else if (FD_ISSET(ssock, &fd)) {
259                         puts ("\nmessage received\n");
260                 } else {
261                         puts("\nselect returned succesfuly, nothing received\n");
262                         continue;
263                 }
264
265                 /* we are retrieving only the extend of a decent MSS = 1500 bytes */
266                 len = sizeof(addr);
267                 ret = recv(ssock, reply, 1500, 0);
268                 if(ret > 0)
269                 {
270                         reply[ret] = 0;
271                         puts("/* reply */");
272                         puts(reply);
273                         putchar('\n');
274                         /* if (step( reply, compiledre )) { */
275                         if (regexec((regex_t*)regexp, reply, 0, 0, 0)==0) {
276                                 puts(" provisional received; still waiting for a final response\n ");
277                                 continue;
278                         } else {
279                                 puts(" final received; congratulations!\n ");
280                                 exit(0);
281                         }
282                 
283                 } 
284                 else    {
285                         perror("recv error");
286                         exit(2);
287                         }
288         }
289         /* after all the retries, nothing has come back :-( */
290         puts("/* I give up retransmission....");
291         exit(1);
292 }
293
294 int main(int argc, char *argv[])
295 {
296         long    address;
297         FILE    *pf;
298         char    buff[BUFSIZE];
299         int             length;
300         int             lport=0;
301         int             rport=5060;
302         char    *delim, *delim2;
303
304         if (! (argc >= 3 && argc <= 5))
305         {
306                 puts("usage: shoot file host rport [lport]");
307                 puts("usage: shoot file sip:[user@]hostname[:rport]");
308                 exit(2);
309         }
310
311         /* support for sip:uri added by noh */
312         if (argc==3){
313                 if ((delim=strchr(argv[2],':'))!=NULL){
314                         delim++;
315                         if (!strncmp(argv[2],"sip",3)){
316                                 if ((delim2=strchr(delim,'@'))!=NULL){
317                                         /* we don't need the username */
318                                         delim2++;
319                                         delim=delim2;
320                                 }
321                                 if ((delim2=strchr(delim,':'))!=NULL){
322                                         *delim2 = '\0';
323                                         delim2++;
324                                         rport = atoi(delim2);
325                                         if (!rport) {
326                                                 puts("error: non-numerical remote port number");
327                                                 exit(2);
328                                         }
329                                 }
330                                 address = getaddress(delim);
331                                 if (!address){
332                                         puts("error:unable to determine the remote host address.");
333                                         exit(2);
334                                 }
335                         }
336                         else{
337                                 puts("sip:uri doesn't not begin with sip");
338                                 exit(2);
339                         }
340                 }
341                 else{
342                         puts("sip:uri doesn't contain a : ?!");
343                         exit(2);
344                 }
345         }
346         else{
347                 address = getaddress(argv[2]);
348                 if (!address){
349                         puts("error:unable to determine the remote host address.");
350                         exit(2);
351                 }
352
353                 /* take the port as 5060 even if it is incorrectly specified */
354                 if (argc >= 4){
355                         rport = atoi(argv[3]);
356                         if (!rport) {
357                                 puts("error: non-numerical remote port number");
358                                 exit(2);
359                         }
360                         if (argc==5) {
361                                 lport=atoi(argv[4]);
362                                 if (!lport) {
363                                         puts("error: non-numerical local port number");
364                                         exit(2);
365                                 }
366                         }
367                 }
368         }
369
370         /* file is opened in binary mode so that the cr-lf is preserved */
371         pf = fopen(argv[1], "rb");
372         if (!pf)
373         {
374                 puts("unable to open the file.\n");
375                 return 1;
376         }
377         length  = fread(buff, 1, sizeof(buff), pf);
378         if (length >= sizeof(buff))
379         {
380                 printf("error:the file is too big. try files of less than %i bytes.\n", BUFSIZE);
381                 puts("      or recompile the program with bigger BUFSIZE defined.");
382                 return 1;
383         }
384         fclose(pf);
385         buff[length] = 0;
386
387         shoot(buff, address, lport, rport );
388
389         /* visual studio closes the debug console as soon as the 
390         program terminates. this is to hold the window from collapsing
391         Uncomment it if needed.
392         getchar();*/
393         
394
395         return 0;
396 }
397
398
399 /*
400 shoot will exercise the all types of sip servers.
401 it is not to be used to measure round-trips and general connectivity.
402 use ping for that. 
403 written by farhan on 10th august, 2000.
404
405 TO-DO:
406 2. understand redirect response and retransmit to the redirected server.
407
408 */
409