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