GPLization banner introduced to *.[hc] files
[sip-router] / test / shoot.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 <regex.h>
28 regex_t* regexp;
29
30 #define RESIZE          1024
31
32 /* take either a dot.decimal string of ip address or a 
33 domain name and returns a NETWORK ordered long int containing
34 the address. i chose to internally represent the address as long for speedier
35 comparisions.
36
37 any changes to getaddress have to be patched back to the net library.
38 contact: farhan@hotfoon.com
39
40   returns zero if there is an error.
41   this is convenient as 0 means 'this' host and the traffic of
42   a badly behaving dns system remains inside (you send to 0.0.0.0)
43 */
44
45 long getaddress(char *host)
46 {
47         int i, dotcount=0;
48         char *p = host;
49         struct hostent* pent;
50         long l, *lp;
51
52         /*try understanding if this is a valid ip address
53         we are skipping the values of the octets specified here.
54         for instance, this code will allow 952.0.320.567 through*/
55         while (*p)
56         {
57                 for (i = 0; i < 3; i++, p++)
58                         if (!isdigit(*p))
59                                 break;
60                 if (*p != '.')
61                         break;
62                 p++;
63                 dotcount++;
64         }
65
66         /* three dots with upto three digits in before, between and after ? */
67         if (dotcount == 3 && i > 0 && i <= 3)
68                 return inet_addr(host);
69
70         /* try the system's own resolution mechanism for dns lookup:
71          required only for domain names.
72          inspite of what the rfc2543 :D Using SRV DNS Records recommends,
73          we are leaving it to the operating system to do the name caching.
74
75          this is an important implementational issue especially in the light
76          dynamic dns servers like dynip.com or dyndns.com where a dial
77          ip address is dynamically assigned a sub domain like farhan.dynip.com
78
79          although expensive, this is a must to allow OS to take
80          the decision to expire the DNS records as it deems fit.
81         */
82         pent = gethostbyname(host);
83         if (!pent) {
84                 perror("no gethostbyname");
85                 exit(2);
86         }
87
88         lp = (long *) (pent->h_addr);
89         l = *lp;
90         return l;
91 }
92
93
94 /*
95 shoot:
96 takes:
97         1. the text message of buff to 
98         2. the address (network orderd byte order)
99         3. and port (not network byte ordered).
100
101 starting from half a second, times-out on replies and
102 keeps retrying with exponential back-off that flattens out
103 at 5 seconds (5000 milliseconds).
104
105 * Does not stop sending unless a final response is received.
106 we are detecting the final response without a '1' as the first
107 letter.
108 */
109 void shoot(char *buff, long address, int lport, int rport )
110 {
111         struct sockaddr_in      addr;
112         /* jku - b  server structures */
113         struct sockaddr_in      sockname;
114         int ssock;
115         /*
116         char compiledre[ RESIZE ];
117         */
118         /* jku - e */
119         int retryAfter = 500, i, len, ret;
120         int     nretries = 10;
121         int     sock;
122         struct timeval  tv;
123         fd_set  fd;
124         char    reply[1600];
125
126         /* create a socket */
127         sock = (int)socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
128         if (sock==-1) {
129                 perror("no client socket");
130                 exit(2);
131         }
132
133         /* jku - b */
134         ssock = (int)socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
135         if (sock==-1) {
136                 perror("no server socket");
137                 exit(2);
138         }
139
140         sockname.sin_family=AF_INET;
141         sockname.sin_addr.s_addr = htonl( INADDR_ANY );
142         sockname.sin_port = htons((short)lport);
143         if (bind( ssock, (struct sockaddr *) &sockname, sizeof(sockname) )==-1) {
144                 perror("no bind");
145                 exit(2);
146         }
147
148         /* should capture: SIP/2.0 100 Trying */
149         /* compile("^SIP/[0-9]\\.[0-9] 1[0-9][0-9] ", compiledre, &compiledre[RESIZE], '\0'); */
150         regexp=(regex_t*)malloc(sizeof(regex_t));
151         regcomp(regexp, "^SIP/[0-9]\\.[0-9] 1[0-9][0-9] ", REG_EXTENDED|REG_NOSUB|REG_ICASE); 
152         
153
154         /* jku - e */
155
156         addr.sin_addr.s_addr = address;
157         addr.sin_port = htons((short)rport);
158         addr.sin_family = AF_INET;
159         
160         /* we connect as per the RFC 2543 recommendations
161         modified from sendto/recvfrom */
162
163         ret = connect(sock, (struct sockaddr *)&addr, sizeof(addr));
164         if (ret==-1) {
165                 perror("no connect");
166                 exit(2);
167         }
168         /* jku - e */
169
170         for (i = 0; i < nretries; i++)
171         {
172                 puts("/* request */");
173                 puts(buff);
174                 putchar('\n');
175
176                 ret = send(sock, buff, strlen(buff), 0);
177                 if (ret==-1) {
178                         perror("send failure");
179                         exit( 1 );
180                 }
181                 
182
183                 tv.tv_sec = retryAfter/1000;
184                 tv.tv_usec = (retryAfter % 1000) * 1000;
185
186                 FD_ZERO(&fd);
187                 FD_SET(ssock, &fd); 
188
189                 /* TO-DO: there does appear to be a problem with this select returning a zero
190                 even when there is data pending in the recv queue. 
191                 please help, someone! */
192
193                 ret = select(6, &fd, NULL, NULL, &tv);
194                 if (ret == 0)
195                 {
196                         puts("\n/* timeout */\n");
197                         retryAfter = retryAfter * 2;
198                         if (retryAfter > 5000)
199                                 retryAfter = 5000;
200                         /* we should have retrieved the error code and displayed
201                         we are not doing that because there is a great variation
202                         in the process of retrieveing error codes between
203                         micro$oft and *nix world*/
204                         continue;
205                 } else if ( ret == -1 ) {
206                         perror("select error");
207                         exit(2);
208                 } /* no timeout, no error ... something has happened :-) */
209                  else if (FD_ISSET(ssock, &fd)) {
210                         puts ("\nmessage received\n");
211                 } else {
212                         puts("\nselect returned succesfuly, nothing received\n");
213                         continue;
214                 }
215
216                 /* we are retrieving only the extend of a decent MSS = 1500 bytes */
217                 len = sizeof(addr);
218                 ret = recv(ssock, reply, 1500, 0);
219                 if(ret > 0)
220                 {
221                         reply[ret] = 0;
222                         puts("/* reply */");
223                         puts(reply);
224                         putchar('\n');
225                         /* if (step( reply, compiledre )) { */
226                         if (regexec((regex_t*)regexp, reply, 0, 0, 0)==0) {
227                                 puts(" provisional received; still waiting for a final response\n ");
228                                 continue;
229                         } else {
230                                 puts(" final received; congratulations!\n ");
231                                 exit(0);
232                         }
233                 
234                 } 
235                 else    {
236                         perror("recv error");
237                         exit(2);
238                         }
239         }
240         /* after all the retries, nothing has come back :-( */
241         puts("/* I give up retransmission....");
242         exit(1);
243 }
244
245 int main(int argc, char *argv[])
246 {
247         long    address;
248         FILE    *pf;
249         char    buff[1600];
250         int             length;
251         int     lport=0;
252         int     rport=5060;
253
254         if (! (argc >= 3 && argc <= 5))
255         {
256                 puts("usage: shoot file host [rport] [lport]");
257                 exit(2);
258         }
259
260         address = getaddress(argv[2]);
261         if (!address)
262         {
263                 puts("error:unable to determine the remote host address.");
264                 exit(2);
265         }
266
267         /* take the port as 5060 even if it is incorrectly specified */
268         if (argc >= 4)
269         {
270                 rport = atoi(argv[3]);
271                 if (!rport) {
272                         puts("error: non-numerical remote port number");
273                         exit(1);
274                 }
275                 if (argc==5) {
276                         lport=atoi(argv[4]);
277                         if (!lport) {
278                                 puts("error: non-numerical local port number");
279                                 exit(1);
280                         }
281                 }
282         }
283
284         /* file is opened in binary mode so that the cr-lf is preserved */
285         pf = fopen(argv[1], "rb");
286         if (!pf)
287         {
288                 puts("unable to open the file.\n");
289                 return 1;
290         }
291         length  = fread(buff, 1, sizeof(buff), pf);
292         if (length >= sizeof(buff))
293         {
294                 puts("error:the file is too big. try files of less than 1500 bytes.");
295                 return 1;
296         }
297         fclose(pf);
298         buff[length] = 0;
299
300         shoot(buff, address, lport, rport );
301
302         /* visual studio closes the debug console as soon as the 
303         program terminates. this is to hold the window from collapsing
304         Uncomment it if needed.
305         getchar();*/
306         
307
308         return 0;
309 }
310
311
312 /*
313 shoot will exercise the all types of sip servers.
314 it is not to be used to measure round-trips and general connectivity.
315 use ping for that. 
316 written by farhan on 10th august, 2000.
317
318 TO-DO:
319 1. replace the command line arguments with just a sip url like this:
320         shoot invite.txt sip:farhan@sip.hotfoon.com:5060
321
322 2. understand redirect response and retransmit to the redirected server.
323
324 */
325