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