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