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 /** @file
20  * @brief UDP ping implementation.
21  * @version $Id$
22  */
23 #include "config.h"
24
25 #include "uping.h"
26 #include "client.h"
27 #include "ircd.h"
28 #include "ircd_alloc.h"
29 #include "ircd_events.h"
30 #include "ircd_log.h"
31 #include "ircd_osdep.h"
32 #include "ircd_string.h"
33 #include "match.h"
34 #include "msg.h"
35 #include "numeric.h"
36 #include "numnicks.h"
37 #include "s_bsd.h"    /* VirtualHost */
38 #include "s_conf.h"
39 #include "s_debug.h"
40 #include "s_misc.h"
41 #include "s_user.h"
42 #include "send.h"
43 #include "sys.h"
44
45 #include <arpa/inet.h>
46 /* #include <assert.h> -- Now using assert in ircd_log.h */
47 #include <errno.h>
48 #include <netdb.h>
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <sys/socket.h>
53 #include <sys/time.h>
54 #include <unistd.h>
55
56 #define UPINGTIMEOUT 60   /**< Timeout waiting for ping responses */
57
58 static struct UPing* pingList = 0; /**< Linked list of UPing structs */
59 static int UPingFileDescriptor       = -1; /**< UDP listener socket for upings */
60 static struct Socket upingSock; /**< Socket struct for upings */
61
62 /** Start iteration of uping list.
63  * @return Start of uping list.
64  */
65 struct UPing* uping_begin(void)
66 {
67   return pingList;
68 }
69
70 /** Removes \a p from uping list.
71  * @param[in,out] p UPing to remove from 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 /** Callback for uping listener socket.
92  * @param[in] ev I/O event for uping socket.
93  */
94 static void uping_echo_callback(struct Event* ev)
95 {
96   assert(ev_type(ev) == ET_READ || ev_type(ev) == ET_ERROR);
97
98   uping_echo();
99 }
100
101 /** Initialize a UDP socket for upings.
102  * @returns File descriptor of UDP socket (-1 on error).
103  */
104 int uping_init(void)
105 {
106   struct irc_sockaddr from;
107   int fd;
108
109   memcpy(&from, &VirtualHost, sizeof(from));
110   from.port = atoi(UDP_PORT);
111
112   fd = os_socket(&from, SOCK_DGRAM, "UDP listener socket");
113   if (fd < 0)
114     return -1;
115   if (!socket_add(&upingSock, uping_echo_callback, 0, SS_DATAGRAM,
116                   SOCK_EVENT_READABLE, fd)) {
117     Debug((DEBUG_ERROR, "UPING: Unable to queue fd to event system"));
118     close(fd);
119     return -1;
120   }
121   UPingFileDescriptor = fd;
122   return fd;
123 }
124
125
126 /** Read a uping from the socket and respond (but not more than 10
127  * times per second).
128  */
129 void uping_echo()
130 {
131   struct irc_sockaddr from;
132   unsigned int       len = 0;
133   static time_t      last = 0;
134   static int         counter = 0;
135   char               buf[BUFSIZE + 1];
136
137   Debug((DEBUG_DEBUG, "UPING: uping_echo"));
138
139   if (IO_SUCCESS != os_recvfrom_nonb(UPingFileDescriptor, buf, BUFSIZE, &len, &from))
140     return;
141   /*
142    * count em even if we're getting flooded so we can tell we're getting
143    * flooded.
144    */
145   ++ServerStats->uping_recv;
146   if (CurrentTime == last) {
147     if (++counter > 10)
148       return;
149   }
150   else {
151     counter = 0;
152     last    = CurrentTime;
153   }
154   if (len < 19)
155     return;
156   os_sendto_nonb(UPingFileDescriptor, buf, len, NULL, 0, &from);
157 }
158
159
160 /** Callback for socket activity on an outbound uping socket.
161  * @param[in] ev I/O event for socket.
162  */
163 static void uping_read_callback(struct Event* ev)
164 {
165   struct UPing *pptr;
166
167   assert(0 != ev_socket(ev));
168   assert(0 != s_data(ev_socket(ev)));
169
170   pptr = (struct UPing*) s_data(ev_socket(ev));
171
172   Debug((DEBUG_SEND, "uping_read_callback called, %p (%d)", pptr,
173          ev_type(ev)));
174
175   if (ev_type(ev) == ET_DESTROY) { /* being destroyed */
176     pptr->freeable &= ~UPING_PENDING_SOCKET;
177
178     if (!pptr->freeable)
179       MyFree(pptr); /* done with it, finally */
180   } else {
181     assert(ev_type(ev) == ET_READ || ev_type(ev) == ET_ERROR);
182
183     uping_read(pptr); /* read uping response */
184   }
185 }
186
187 /** Timer callback to send another outbound uping.
188  * @param[in] ev Event for uping timer.
189  */
190 static void uping_sender_callback(struct Event* ev)
191 {
192   struct UPing *pptr;
193
194   assert(0 != ev_timer(ev));
195   assert(0 != t_data(ev_timer(ev)));
196
197   pptr = (struct UPing*) t_data(ev_timer(ev));
198
199   Debug((DEBUG_SEND, "uping_sender_callback called, %p (%d)", pptr,
200          ev_type(ev)));
201
202   if (ev_type(ev) == ET_DESTROY) { /* being destroyed */
203     pptr->freeable &= ~UPING_PENDING_SENDER;
204
205     if (!pptr->freeable)
206       MyFree(pptr); /* done with it, finally */
207   } else {
208     assert(ev_type(ev) == ET_EXPIRE);
209
210     pptr->lastsent = CurrentTime; /* store last ping time */
211     uping_send(pptr); /* send a ping */
212
213     if (pptr->sent == pptr->count) /* done sending pings, don't send more */
214       timer_del(ev_timer(ev));
215   }
216 }
217
218 /** Timer callback to stop upings.
219  * @param[in] ev Event for uping expiration.
220  */
221 static void uping_killer_callback(struct Event* ev)
222 {
223   struct UPing *pptr;
224
225   assert(0 != ev_timer(ev));
226   assert(0 != t_data(ev_timer(ev)));
227
228   pptr = (struct UPing*) t_data(ev_timer(ev));
229
230   Debug((DEBUG_SEND, "uping_killer_callback called, %p (%d)", pptr,
231          ev_type(ev)));
232
233   if (ev_type(ev) == ET_DESTROY) { /* being destroyed */
234     pptr->freeable &= ~UPING_PENDING_KILLER;
235
236     if (!pptr->freeable)
237       MyFree(pptr); /* done with it, finally */
238   } else {
239     assert(ev_type(ev) == ET_EXPIRE);
240
241     uping_end(pptr); /* <FUDD>kill the uping, kill the uping!</FUDD> */
242   }
243 }
244
245 /** Start a uping.
246  * This sets up the timers, UPing flags, and sends a notice to the
247  * requesting client.
248  */
249 static void uping_start(struct UPing* pptr)
250 {
251   assert(0 != pptr);
252
253   timer_add(timer_init(&pptr->sender), uping_sender_callback, (void*) pptr,
254             TT_PERIODIC, 1);
255   timer_add(timer_init(&pptr->killer), uping_killer_callback, (void*) pptr,
256             TT_RELATIVE, UPINGTIMEOUT);
257   pptr->freeable |= UPING_PENDING_SENDER | UPING_PENDING_KILLER;
258
259   sendcmdto_one(&me, CMD_NOTICE, pptr->client, "%C :Sending %d ping%s to %s",
260                 pptr->client, pptr->count, (pptr->count == 1) ? "" : "s",
261                 pptr->name);
262   pptr->active = 1;
263 }
264
265 /** Send a uping to another server.
266  * @param[in] pptr Descriptor for uping.
267  */
268 void uping_send(struct UPing* pptr)
269 {
270   struct timeval tv;
271   char buf[BUFSIZE + 1];
272
273   assert(0 != pptr);
274   if (pptr->sent == pptr->count)
275     return;
276   memset(buf, 0, sizeof(buf));
277
278   gettimeofday(&tv, NULL);
279   sprintf(buf, " %10lu%c%6lu", (unsigned long)tv.tv_sec, '\0', (unsigned long)tv.tv_usec);
280
281   Debug((DEBUG_SEND, "send_ping: sending [%s %s] to %s.%d on %d",
282           buf, &buf[12],
283           ircd_ntoa(&pptr->addr.addr), pptr->addr.port,
284           pptr->fd));
285
286   if (os_sendto_nonb(pptr->fd, buf, BUFSIZE, NULL, 0, &pptr->addr) != IO_SUCCESS)
287   {
288     const char* msg = strerror(errno);
289     if (!msg)
290       msg = "Unknown error";
291     if (pptr->client)
292       sendcmdto_one(&me, CMD_NOTICE, pptr->client, "%C :UPING: send failed: "
293                     "%s", pptr->client, msg);
294     Debug((DEBUG_DEBUG, "UPING: send_ping: sendto failed on %d: %s", pptr->fd, msg));
295     uping_end(pptr);
296     return;
297   }
298   ++pptr->sent;
299 }
300
301 /** Read the response from an outbound uping.
302  * @param[in] pptr UPing to check.
303  */
304 void uping_read(struct UPing* pptr)
305 {
306   struct irc_sockaddr sin;
307   struct timeval     tv;
308   unsigned int       len;
309   unsigned int       pingtime;
310   char*              s;
311   char               buf[BUFSIZE + 1];
312   IOResult           ior;
313
314   assert(0 != pptr);
315
316   gettimeofday(&tv, NULL);
317
318   ior = os_recvfrom_nonb(pptr->fd, buf, BUFSIZE, &len, &sin);
319   if (IO_BLOCKED == ior)
320     return;
321   else if (IO_FAILURE == ior) {
322     const char* msg = strerror(errno);
323     if (!msg)
324       msg = "Unknown error";
325     sendcmdto_one(&me, CMD_NOTICE, pptr->client, "%C :UPING: receive error: "
326                   "%s", pptr->client, msg);
327     uping_end(pptr);
328     return;
329   }
330
331   if (len < 19)
332     return;                     /* Broken packet */
333
334   ++pptr->received;
335
336   buf[len] = 0;
337   pingtime = (tv.tv_sec - atol(&buf[1])) * 1000
338              + (tv.tv_usec - atol(buf + strlen(buf) + 1)) / 1000;
339
340   pptr->ms_ave += pingtime;
341   if (!pptr->ms_min || pptr->ms_min > pingtime)
342     pptr->ms_min = pingtime;
343   if (pingtime > pptr->ms_max)
344     pptr->ms_max = pingtime;
345
346   timer_chg(&pptr->killer, TT_RELATIVE, UPINGTIMEOUT);
347
348   s = pptr->buf + strlen(pptr->buf);
349   sprintf(s, " %u", pingtime);
350
351   if (pptr->received == pptr->count)
352     uping_end(pptr);
353   return;
354 }
355
356 /** Start sending upings to a server.
357  * @param[in] sptr Client requesting the upings.
358  * @param[in] aconf ConfItem containing the address to ping.
359  * @param[in] port Port number to ping.
360  * @param[in] count Number of times to ping (should be at least 20).
361  * @return Zero.
362  */
363 int uping_server(struct Client* sptr, struct ConfItem* aconf, int port, int count)
364 {
365   int fd;
366   struct UPing* pptr;
367
368   assert(0 != sptr);
369   assert(0 != aconf);
370
371   if (!irc_in_addr_valid(&aconf->address.addr)) {
372     sendcmdto_one(&me, CMD_NOTICE, sptr, "%C :UPING: Host lookup failed for "
373                   "%s", sptr, aconf->name);
374     return 0;
375   }
376
377   if (IsUPing(sptr))
378     uping_cancel(sptr, sptr);  /* Cancel previous ping request */
379
380   fd = os_socket(NULL, SOCK_DGRAM, "UDP ping socket");
381   if (fd < 0)
382     return 0;
383
384   pptr = (struct UPing*) MyMalloc(sizeof(struct UPing));
385   assert(0 != pptr);
386   memset(pptr, 0, sizeof(struct UPing));
387
388   if (!socket_add(&pptr->socket, uping_read_callback, (void*) pptr,
389                   SS_DATAGRAM, SOCK_EVENT_READABLE, fd)) {
390     sendcmdto_one(&me, CMD_NOTICE, sptr, "%C :UPING: Can't queue fd for "
391                   "reading", sptr);
392     close(fd);
393     MyFree(pptr);
394     return 0;
395   }
396
397   pptr->fd                  = fd;
398   memcpy(&pptr->addr.addr, &aconf->address.addr, sizeof(pptr->addr.addr));
399   pptr->addr.port           = port;
400   pptr->count               = IRCD_MIN(20, count);
401   pptr->client              = sptr;
402   pptr->freeable            = UPING_PENDING_SOCKET;
403   strcpy(pptr->name, aconf->name);
404
405   pptr->next = pingList;
406   pingList   = pptr;
407
408   SetUPing(sptr);
409   uping_start(pptr);
410   return 0;
411 }
412
413 /** Clean up a UPing structure, reporting results to the requester.
414  * @param[in,out] pptr UPing results.
415  */
416 void uping_end(struct UPing* pptr)
417 {
418   Debug((DEBUG_DEBUG, "uping_end: %p", pptr));
419
420   if (pptr->client) {
421     if (pptr->lastsent) {
422       if (0 < pptr->received) {
423         sendcmdto_one(&me, CMD_NOTICE, pptr->client, "%C :UPING %s%s",
424                       pptr->client, pptr->name, pptr->buf);
425         sendcmdto_one(&me, CMD_NOTICE, pptr->client, "%C :UPING Stats: "
426                       "sent %d recvd %d ; min/avg/max = %1lu/%1lu/%1lu ms",
427                       pptr->client, pptr->sent, pptr->received, pptr->ms_min,
428                       (2 * pptr->ms_ave) / (2 * pptr->received), pptr->ms_max);
429       } else
430         sendcmdto_one(&me, CMD_NOTICE, pptr->client, "%C :UPING: no response "
431                       "from %s within %d seconds", pptr->client, pptr->name,
432                       UPINGTIMEOUT);
433     } else
434       sendcmdto_one(&me, CMD_NOTICE, pptr->client, "%C :UPING: Could not "
435                     "start ping to %s", pptr->client, pptr->name);
436   }
437
438   close(pptr->fd);
439   pptr->fd = -1;
440   uping_erase(pptr);
441   if (pptr->client)
442     ClearUPing(pptr->client);
443   if (pptr->freeable & UPING_PENDING_SOCKET)
444     socket_del(&pptr->socket);
445   if (pptr->freeable & UPING_PENDING_SENDER)
446     timer_del(&pptr->sender);
447   if (pptr->freeable & UPING_PENDING_KILLER)
448     timer_del(&pptr->killer);
449 }
450
451 /** Change notifications for any upings by \a sptr.
452  * @param[in] sptr Client to stop notifying.
453  * @param[in] acptr New client to notify (or NULL).
454  */
455 void uping_cancel(struct Client *sptr, struct Client* acptr)
456 {
457   struct UPing* ping;
458   struct UPing* ping_next;
459
460   Debug((DEBUG_DEBUG, "UPING: cancelling uping for %s", cli_name(sptr)));
461   for (ping = pingList; ping; ping = ping_next) {
462     ping_next = ping->next;
463     if (sptr == ping->client) {
464       ping->client = acptr;
465       uping_end(ping);
466     }
467   }
468   ClearUPing(sptr);
469 }
470
471