Author: Kev <klmitch@mit.edu>
[ircu2.10.12-pk.git] / ircd / uping.c
1 /*
2  * IRC - Internet Relay Chat, ircd/uping.c
3  * Copyright (C) 1994 Carlo Wood ( Run @ undernet.org )
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2, or (at your option)
8  * any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18  *
19  * $Id$
20  */
21 #include "config.h"
22
23 #include "uping.h"
24 #include "client.h"
25 #include "ircd.h"
26 #include "ircd_alloc.h"
27 #include "ircd_log.h"
28 #include "ircd_osdep.h"
29 #include "ircd_string.h"
30 #include "match.h"
31 #include "msg.h"
32 #include "numeric.h"
33 #include "numnicks.h"
34 #include "s_bsd.h"    /* VirtualHost */
35 #include "s_conf.h"
36 #include "s_debug.h"
37 #include "s_misc.h"
38 #include "s_user.h"
39 #include "send.h"
40 #include "sys.h"
41
42 #include <arpa/inet.h>
43 #include <assert.h>
44 #include <errno.h>
45 #include <netdb.h>
46 #include <stdio.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include <sys/socket.h>
50 #include <sys/time.h>
51 #include <unistd.h>
52
53 #define UPINGTIMEOUT 60   /* Timeout waiting for ping responses */
54
55 #ifndef INADDR_NONE
56 #define INADDR_NONE 0xffffffff
57 #endif
58
59 static struct UPing* pingList = 0;
60 int UPingFileDescriptor       = -1; /* UDP listener socket for upings */
61
62 /*
63  * pings_begin - iterator function for ping list 
64  */
65 struct UPing* uping_begin(void)
66 {
67   return pingList;
68 }
69
70 /*
71  * pings_erase - removes ping struct from ping list
72  */
73 static void uping_erase(struct UPing* p)
74 {
75   struct UPing* it;
76   struct UPing* last = 0;
77
78   assert(0 != p);
79   
80   for (it = pingList; it; last = it, it = it->next) {
81     if (p == it) {
82       if (last)
83         last->next = p->next;
84       else
85         pingList = p->next;
86       break;
87     }
88   }
89 }
90
91 /*
92  * Setup a UDP socket and listen for incoming packets
93  */
94 int uping_init(void)
95 {
96   struct sockaddr_in from = { 0 };
97   int fd;
98
99   memset(&from, 0, sizeof(from));
100   from.sin_addr = VirtualHost.sin_addr;
101   from.sin_port = htons(atoi(UDP_PORT));
102   from.sin_family = AF_INET;
103
104   if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
105     Debug((DEBUG_ERROR, "UPING: UDP listener socket call failed: %s", 
106            (strerror(errno)) ? strerror(errno) : "Unknown error"));
107     return -1;
108   }
109   if (!os_set_reuseaddr(fd)) {
110     log_write(LS_SOCKET, L_ERROR, 0,
111               "UPING: set reuseaddr on UDP listener failed: %m (fd %d)", fd);
112     Debug((DEBUG_ERROR, "UPING: set reuseaddr on UDP listener failed: %s",
113            (strerror(errno)) ? strerror(errno) : "Unknown error"));
114     close(fd);
115     return -1;
116   }
117   if (bind(fd, (struct sockaddr*) &from, sizeof(from)) == -1) {
118     log_write(LS_SOCKET, L_ERROR, 0,
119               "UPING: bind on UDP listener (%d fd %d) failed: %m",
120               htons(from.sin_port), fd);
121     Debug((DEBUG_ERROR, "UPING: bind on UDP listener failed : %s",
122            (strerror(errno)) ? strerror(errno) : "Unknown error"));
123     close(fd);
124     return -1;
125   }
126   if (!os_set_nonblocking(fd)) {
127     Debug((DEBUG_ERROR, "UPING: set non-blocking: %s",
128            (strerror(errno)) ? strerror(errno) : "Unknown error"));
129     close(fd);
130     return -1;
131   }
132   UPingFileDescriptor = fd;
133   return fd;
134 }
135
136
137 /*
138  * max # of pings set to 15/sec.
139  */
140 void uping_echo()
141 {
142   struct sockaddr_in from = { 0 };
143   unsigned int       len = 0;
144   static time_t      last = 0;
145   static int         counter = 0;
146   char               buf[BUFSIZE + 1];
147
148   Debug((DEBUG_DEBUG, "UPING: uping_echo"));
149
150   if (IO_SUCCESS != os_recvfrom_nonb(UPingFileDescriptor, buf, BUFSIZE, &len, &from))
151     return;
152   /*
153    * count em even if we're getting flooded so we can tell we're getting
154    * flooded.
155    */
156   ++ServerStats->uping_recv;
157   if (CurrentTime == last) {
158     if (++counter > 10)
159       return;
160   }
161   else {
162     counter = 0;
163     last    = CurrentTime;
164   }
165   if (len < 19)
166     return;
167   sendto(UPingFileDescriptor, buf, len, 0, (struct sockaddr*) &from, sizeof(from));
168 }
169
170
171 /*
172  * start_ping
173  */
174 static void uping_start(struct UPing* pptr)
175 {
176   assert(0 != pptr);
177
178   sendcmdto_one(&me, CMD_NOTICE, pptr->client, "%C :Sending %d ping%s to %s",
179                 pptr->client, pptr->count, (pptr->count == 1) ? "" : "s",
180                 pptr->name);
181   pptr->timeout = CurrentTime + UPINGTIMEOUT;
182   pptr->active = 1;
183 }
184
185 /*
186  * uping_send
187  *
188  */
189 void uping_send(struct UPing* pptr)
190 {
191   struct timeval tv;
192   char buf[BUFSIZE + 1];
193
194   assert(0 != pptr);
195   if (pptr->sent == pptr->count)
196     return;
197   memset(buf, 0, sizeof(buf));
198
199   gettimeofday(&tv, NULL);
200   sprintf(buf, " %10lu%c%6lu", tv.tv_sec, '\0', tv.tv_usec);
201
202   Debug((DEBUG_SEND, "send_ping: sending [%s %s] to %s.%d on %d",
203           buf, &buf[12],
204           ircd_ntoa((const char*) &pptr->sin.sin_addr), ntohs(pptr->sin.sin_port),
205           pptr->fd));
206
207   if (sendto(pptr->fd, buf, BUFSIZE, 0, (struct sockaddr*) &pptr->sin,
208              sizeof(struct sockaddr_in)) != BUFSIZE)
209   {
210     const char* msg = strerror(errno);
211     if (!msg)
212       msg = "Unknown error";
213     if (pptr->client)
214       sendcmdto_one(&me, CMD_NOTICE, pptr->client, "%C :UPING: send failed: "
215                     "%s", pptr->client, msg);
216     Debug((DEBUG_DEBUG, "UPING: send_ping: sendto failed on %d: %s", pptr->fd, msg));
217     uping_end(pptr);
218     return;
219   }
220   ++pptr->sent;
221 }
222
223 /*
224  * read_ping
225  */
226 void uping_read(struct UPing* pptr)
227 {
228   struct sockaddr_in sin;
229   struct timeval     tv;
230   unsigned int       len;
231   unsigned int       pingtime;
232   char*              s;
233   char               buf[BUFSIZE + 1];
234   IOResult           ior;
235
236   assert(0 != pptr);
237
238   gettimeofday(&tv, NULL);
239
240   ior = os_recvfrom_nonb(pptr->fd, buf, BUFSIZE, &len, &sin);
241   if (IO_BLOCKED == ior)
242     return;
243   else if (IO_FAILURE == ior) {
244     const char* msg = strerror(errno);
245     if (!msg)
246       msg = "Unknown error";
247     sendcmdto_one(&me, CMD_NOTICE, pptr->client, "%C :UPING: receive error: "
248                   "%s", pptr->client, msg);
249     uping_end(pptr);
250     return;
251   }    
252
253   if (len < 19)
254     return;                     /* Broken packet */
255    
256   ++pptr->received;
257
258   buf[len] = 0;
259   pingtime = (tv.tv_sec - atol(&buf[1])) * 1000
260              + (tv.tv_usec - atol(buf + strlen(buf) + 1)) / 1000;
261
262   pptr->ms_ave += pingtime;
263   if (!pptr->ms_min || pptr->ms_min > pingtime)
264     pptr->ms_min = pingtime;
265   if (pingtime > pptr->ms_max)
266     pptr->ms_max = pingtime;
267   
268   pptr->timeout = CurrentTime + UPINGTIMEOUT;
269
270   Debug((DEBUG_SEND, "read_ping: %d bytes, ti %lu: [%s %s] %lu ms",
271          len, pptr->timeout, buf, (buf + strlen(buf) + 1), pingtime));
272
273   s = pptr->buf + strlen(pptr->buf);
274   sprintf(s, " %u", pingtime);
275
276   if (pptr->received == pptr->count)
277     uping_end(pptr);
278   return;
279 }
280
281 int uping_server(struct Client* sptr, struct ConfItem* aconf, int port, int count)
282 {
283   int fd;
284   struct UPing* pptr;
285
286   assert(0 != sptr);
287   assert(0 != aconf);
288
289   if (INADDR_NONE == aconf->ipnum.s_addr) {
290     sendcmdto_one(&me, CMD_NOTICE, sptr, "%C :UPING: Host lookup failed for "
291                   "%s", sptr, aconf->name);
292     return 0;
293   }
294
295   if (IsUPing(sptr))
296     uping_cancel(sptr, sptr);  /* Cancel previous ping request */
297
298   if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
299     sendcmdto_one(&me, CMD_NOTICE, sptr, "%C :UPING: Unable to create udp "
300                   "ping socket", sptr);
301     return 0;
302   }
303
304   if (!os_set_nonblocking(fd)) {
305     sendcmdto_one(&me, CMD_NOTICE, sptr, "%C :UPING: Can't set fd non-"
306                   "blocking", sptr);
307     close(fd);
308     return 0;
309   }
310   pptr = (struct UPing*) MyMalloc(sizeof(struct UPing));
311   assert(0 != pptr);
312   memset(pptr, 0, sizeof(struct UPing));
313
314   pptr->fd                  = fd;
315   pptr->sin.sin_port        = htons(port);
316   pptr->sin.sin_addr.s_addr = aconf->ipnum.s_addr;
317   pptr->sin.sin_family      = AF_INET;
318   pptr->count               = IRCD_MIN(20, count);
319   pptr->client              = sptr;
320   pptr->index               = -1;
321   strcpy(pptr->name, aconf->name);
322
323   pptr->next = pingList;
324   pingList   = pptr;
325
326   SetUPing(sptr);
327   uping_start(pptr);
328   return 0;
329 }
330
331
332 void uping_end(struct UPing* pptr)
333 {
334   Debug((DEBUG_DEBUG, "uping_end: %p", pptr));
335
336   if (pptr->client) {
337     if (pptr->lastsent) {
338       if (0 < pptr->received) {
339         sendcmdto_one(&me, CMD_NOTICE, pptr->client, "%C :UPING %s%s",
340                       pptr->client, pptr->name, pptr->buf);
341         sendcmdto_one(&me, CMD_NOTICE, pptr->client, "%C :UPING Stats: "
342                       "sent %d recvd %d ; min/avg/max = %1lu/%1lu/%1lu ms",
343                       pptr->client, pptr->sent, pptr->received, pptr->ms_min,
344                       (2 * pptr->ms_ave) / (2 * pptr->received), pptr->ms_max);
345       } else
346         sendcmdto_one(&me, CMD_NOTICE, pptr->client, "%C :UPING: no response "
347                       "from %s within %d seconds", pptr->client, pptr->name,
348                       UPINGTIMEOUT);
349     } else
350       sendcmdto_one(&me, CMD_NOTICE, pptr->client, "%C :UPING: Could not "
351                     "start ping to %s", pptr->client, pptr->name);
352   }
353
354   close(pptr->fd);
355   pptr->fd = -1;
356   uping_erase(pptr);
357   if (pptr->client)
358     ClearUPing(pptr->client);
359   MyFree(pptr);
360 }
361
362 void uping_cancel(struct Client *sptr, struct Client* acptr)
363 {
364   struct UPing* ping;
365   struct UPing* ping_next;
366
367   Debug((DEBUG_DEBUG, "UPING: cancelling uping for %s", cli_name(sptr)));
368   for (ping = pingList; ping; ping = ping_next) {
369     ping_next = ping->next;
370     if (sptr == ping->client) {
371       ping->client = acptr;
372       uping_end(ping);
373     }
374   }
375   ClearUPing(sptr);
376 }
377
378