]>
Commit | Line | Data |
---|---|---|
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.2.1 2006/03/14 03:45:52 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", AF_INET); | |
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 | #ifdef AF_INET6 | |
149 | memcpy(&from, &VirtualHost_v6, sizeof(from)); | |
150 | from.port = atoi(UDP_PORT); | |
151 | ||
152 | fd = os_socket(&from, SOCK_DGRAM, "IPv6 uping listener", AF_INET6); | |
153 | if (fd < 0) | |
154 | return -1; | |
155 | if (!socket_add(&upingSock_v6, uping_echo_callback, 0, SS_DATAGRAM, | |
156 | SOCK_EVENT_READABLE, fd)) { | |
157 | Debug((DEBUG_ERROR, "UPING: Unable to queue fd to event system")); | |
158 | close(fd); | |
159 | return -1; | |
160 | } | |
161 | #endif | |
162 | ||
163 | return 0; | |
164 | } | |
165 | ||
166 | ||
167 | /** Callback for socket activity on an outbound uping socket. | |
168 | * @param[in] ev I/O event for socket. | |
169 | */ | |
170 | static void uping_read_callback(struct Event* ev) | |
171 | { | |
172 | struct UPing *pptr; | |
173 | ||
174 | assert(0 != ev_socket(ev)); | |
175 | assert(0 != s_data(ev_socket(ev))); | |
176 | ||
177 | pptr = (struct UPing*) s_data(ev_socket(ev)); | |
178 | ||
179 | Debug((DEBUG_SEND, "uping_read_callback called, %p (%d)", pptr, | |
180 | ev_type(ev))); | |
181 | ||
182 | if (ev_type(ev) == ET_DESTROY) { /* being destroyed */ | |
183 | pptr->freeable &= ~UPING_PENDING_SOCKET; | |
184 | ||
185 | if (!pptr->freeable) | |
186 | MyFree(pptr); /* done with it, finally */ | |
187 | } else { | |
188 | assert(ev_type(ev) == ET_READ || ev_type(ev) == ET_ERROR); | |
189 | ||
190 | uping_read(pptr); /* read uping response */ | |
191 | } | |
192 | } | |
193 | ||
194 | /** Timer callback to send another outbound uping. | |
195 | * @param[in] ev Event for uping timer. | |
196 | */ | |
197 | static void uping_sender_callback(struct Event* ev) | |
198 | { | |
199 | struct UPing *pptr; | |
200 | ||
201 | assert(0 != ev_timer(ev)); | |
202 | assert(0 != t_data(ev_timer(ev))); | |
203 | ||
204 | pptr = (struct UPing*) t_data(ev_timer(ev)); | |
205 | ||
206 | Debug((DEBUG_SEND, "uping_sender_callback called, %p (%d)", pptr, | |
207 | ev_type(ev))); | |
208 | ||
209 | if (ev_type(ev) == ET_DESTROY) { /* being destroyed */ | |
210 | pptr->freeable &= ~UPING_PENDING_SENDER; | |
211 | ||
212 | if (!pptr->freeable) | |
213 | MyFree(pptr); /* done with it, finally */ | |
214 | } else { | |
215 | assert(ev_type(ev) == ET_EXPIRE); | |
216 | ||
217 | pptr->lastsent = CurrentTime; /* store last ping time */ | |
218 | uping_send(pptr); /* send a ping */ | |
219 | ||
220 | if (pptr->sent == pptr->count) /* done sending pings, don't send more */ | |
221 | timer_del(ev_timer(ev)); | |
222 | } | |
223 | } | |
224 | ||
225 | /** Timer callback to stop upings. | |
226 | * @param[in] ev Event for uping expiration. | |
227 | */ | |
228 | static void uping_killer_callback(struct Event* ev) | |
229 | { | |
230 | struct UPing *pptr; | |
231 | ||
232 | assert(0 != ev_timer(ev)); | |
233 | assert(0 != t_data(ev_timer(ev))); | |
234 | ||
235 | pptr = (struct UPing*) t_data(ev_timer(ev)); | |
236 | ||
237 | Debug((DEBUG_SEND, "uping_killer_callback called, %p (%d)", pptr, | |
238 | ev_type(ev))); | |
239 | ||
240 | if (ev_type(ev) == ET_DESTROY) { /* being destroyed */ | |
241 | pptr->freeable &= ~UPING_PENDING_KILLER; | |
242 | ||
243 | if (!pptr->freeable) | |
244 | MyFree(pptr); /* done with it, finally */ | |
245 | } else { | |
246 | assert(ev_type(ev) == ET_EXPIRE); | |
247 | ||
248 | uping_end(pptr); /* <FUDD>kill the uping, kill the uping!</FUDD> */ | |
249 | } | |
250 | } | |
251 | ||
252 | /** Start a uping. | |
253 | * This sets up the timers, UPing flags, and sends a notice to the | |
254 | * requesting client. | |
255 | */ | |
256 | static void uping_start(struct UPing* pptr) | |
257 | { | |
258 | assert(0 != pptr); | |
259 | ||
260 | timer_add(timer_init(&pptr->sender), uping_sender_callback, (void*) pptr, | |
261 | TT_PERIODIC, 1); | |
262 | timer_add(timer_init(&pptr->killer), uping_killer_callback, (void*) pptr, | |
263 | TT_RELATIVE, UPINGTIMEOUT); | |
264 | pptr->freeable |= UPING_PENDING_SENDER | UPING_PENDING_KILLER; | |
265 | ||
266 | sendcmdto_one(&me, CMD_NOTICE, pptr->client, "%C :Sending %d ping%s to %s", | |
267 | pptr->client, pptr->count, (pptr->count == 1) ? "" : "s", | |
268 | pptr->name); | |
269 | pptr->active = 1; | |
270 | } | |
271 | ||
272 | /** Send a uping to another server. | |
273 | * @param[in] pptr Descriptor for uping. | |
274 | */ | |
275 | void uping_send(struct UPing* pptr) | |
276 | { | |
277 | struct timeval tv; | |
278 | char buf[BUFSIZE + 1]; | |
279 | ||
280 | assert(0 != pptr); | |
281 | if (pptr->sent == pptr->count) | |
282 | return; | |
283 | memset(buf, 0, sizeof(buf)); | |
284 | ||
285 | gettimeofday(&tv, NULL); | |
286 | sprintf(buf, " %10lu%c%6lu", (unsigned long)tv.tv_sec, '\0', (unsigned long)tv.tv_usec); | |
287 | ||
288 | Debug((DEBUG_SEND, "send_ping: sending [%s %s] to %s.%d on %d", | |
289 | buf, &buf[12], | |
290 | ircd_ntoa(&pptr->addr.addr), pptr->addr.port, | |
291 | pptr->fd)); | |
292 | ||
293 | if (os_sendto_nonb(pptr->fd, buf, BUFSIZE, NULL, 0, &pptr->addr) != IO_SUCCESS) | |
294 | { | |
295 | const char* msg = strerror(errno); | |
296 | if (!msg) | |
297 | msg = "Unknown error"; | |
298 | if (pptr->client) | |
299 | sendcmdto_one(&me, CMD_NOTICE, pptr->client, "%C :UPING: send failed: " | |
300 | "%s", pptr->client, msg); | |
301 | Debug((DEBUG_DEBUG, "UPING: send_ping: sendto failed on %d: %s", pptr->fd, msg)); | |
302 | uping_end(pptr); | |
303 | return; | |
304 | } | |
305 | ++pptr->sent; | |
306 | } | |
307 | ||
308 | /** Read the response from an outbound uping. | |
309 | * @param[in] pptr UPing to check. | |
310 | */ | |
311 | void uping_read(struct UPing* pptr) | |
312 | { | |
313 | struct irc_sockaddr sin; | |
314 | struct timeval tv; | |
315 | unsigned int len; | |
316 | unsigned int pingtime; | |
317 | char* s; | |
318 | char buf[BUFSIZE + 1]; | |
319 | IOResult ior; | |
320 | ||
321 | assert(0 != pptr); | |
322 | ||
323 | gettimeofday(&tv, NULL); | |
324 | ||
325 | ior = os_recvfrom_nonb(pptr->fd, buf, BUFSIZE, &len, &sin); | |
326 | if (IO_BLOCKED == ior) | |
327 | return; | |
328 | else if (IO_FAILURE == ior) { | |
329 | const char* msg = strerror(errno); | |
330 | if (!msg) | |
331 | msg = "Unknown error"; | |
332 | sendcmdto_one(&me, CMD_NOTICE, pptr->client, "%C :UPING: receive error: " | |
333 | "%s", pptr->client, msg); | |
334 | uping_end(pptr); | |
335 | return; | |
336 | } | |
337 | ||
338 | if (len < 19) | |
339 | return; /* Broken packet */ | |
340 | ||
341 | ++pptr->received; | |
342 | ||
343 | buf[len] = 0; | |
344 | pingtime = (tv.tv_sec - atol(&buf[1])) * 1000 | |
345 | + (tv.tv_usec - atol(buf + strlen(buf) + 1)) / 1000; | |
346 | ||
347 | pptr->ms_ave += pingtime; | |
348 | if (!pptr->ms_min || pptr->ms_min > pingtime) | |
349 | pptr->ms_min = pingtime; | |
350 | if (pingtime > pptr->ms_max) | |
351 | pptr->ms_max = pingtime; | |
352 | ||
353 | timer_chg(&pptr->killer, TT_RELATIVE, UPINGTIMEOUT); | |
354 | ||
355 | s = pptr->buf + strlen(pptr->buf); | |
356 | sprintf(s, " %u", pingtime); | |
357 | ||
358 | if (pptr->received == pptr->count) | |
359 | uping_end(pptr); | |
360 | return; | |
361 | } | |
362 | ||
363 | /** Start sending upings to a server. | |
364 | * @param[in] sptr Client requesting the upings. | |
365 | * @param[in] aconf ConfItem containing the address to ping. | |
366 | * @param[in] port Port number to ping. | |
367 | * @param[in] count Number of times to ping (should be at least 20). | |
368 | * @return Zero. | |
369 | */ | |
370 | int uping_server(struct Client* sptr, struct ConfItem* aconf, int port, int count) | |
371 | { | |
372 | int fd; | |
373 | int family = 0; | |
374 | struct UPing* pptr; | |
375 | struct irc_sockaddr *local; | |
376 | ||
377 | assert(0 != sptr); | |
378 | assert(0 != aconf); | |
379 | ||
380 | if (!irc_in_addr_valid(&aconf->address.addr)) { | |
381 | sendcmdto_one(&me, CMD_NOTICE, sptr, "%C :UPING: Host lookup failed for " | |
382 | "%s", sptr, aconf->name); | |
383 | return 0; | |
384 | } | |
385 | ||
386 | if (IsUPing(sptr)) | |
387 | uping_cancel(sptr, sptr); /* Cancel previous ping request */ | |
388 | ||
389 | if (irc_in_addr_is_ipv4(&aconf->address.addr)) { | |
390 | local = &VirtualHost_v4; | |
391 | family = AF_INET; | |
392 | } else { | |
393 | local = &VirtualHost_v6; | |
394 | } | |
395 | fd = os_socket(local, SOCK_DGRAM, "Outbound uping socket", family); | |
396 | if (fd < 0) | |
397 | return 0; | |
398 | ||
399 | pptr = (struct UPing*) MyMalloc(sizeof(struct UPing)); | |
400 | assert(0 != pptr); | |
401 | memset(pptr, 0, sizeof(struct UPing)); | |
402 | ||
403 | if (!socket_add(&pptr->socket, uping_read_callback, (void*) pptr, | |
404 | SS_DATAGRAM, SOCK_EVENT_READABLE, fd)) { | |
405 | sendcmdto_one(&me, CMD_NOTICE, sptr, "%C :UPING: Can't queue fd for " | |
406 | "reading", sptr); | |
407 | close(fd); | |
408 | MyFree(pptr); | |
409 | return 0; | |
410 | } | |
411 | ||
412 | pptr->fd = fd; | |
413 | memcpy(&pptr->addr.addr, &aconf->address.addr, sizeof(pptr->addr.addr)); | |
414 | pptr->addr.port = port; | |
415 | pptr->count = IRCD_MIN(20, count); | |
416 | pptr->client = sptr; | |
417 | pptr->freeable = UPING_PENDING_SOCKET; | |
418 | strcpy(pptr->name, aconf->name); | |
419 | ||
420 | pptr->next = pingList; | |
421 | pingList = pptr; | |
422 | ||
423 | SetUPing(sptr); | |
424 | uping_start(pptr); | |
425 | return 0; | |
426 | } | |
427 | ||
428 | /** Clean up a UPing structure, reporting results to the requester. | |
429 | * @param[in,out] pptr UPing results. | |
430 | */ | |
431 | void uping_end(struct UPing* pptr) | |
432 | { | |
433 | Debug((DEBUG_DEBUG, "uping_end: %p", pptr)); | |
434 | ||
435 | if (pptr->client) { | |
436 | if (pptr->lastsent) { | |
437 | if (0 < pptr->received) { | |
438 | sendcmdto_one(&me, CMD_NOTICE, pptr->client, "%C :UPING %s%s", | |
439 | pptr->client, pptr->name, pptr->buf); | |
440 | sendcmdto_one(&me, CMD_NOTICE, pptr->client, "%C :UPING Stats: " | |
441 | "sent %d recvd %d ; min/avg/max = %u/%u/%u ms", | |
442 | pptr->client, pptr->sent, pptr->received, pptr->ms_min, | |
443 | (2 * pptr->ms_ave) / (2 * pptr->received), pptr->ms_max); | |
444 | } else | |
445 | sendcmdto_one(&me, CMD_NOTICE, pptr->client, "%C :UPING: no response " | |
446 | "from %s within %d seconds", pptr->client, pptr->name, | |
447 | UPINGTIMEOUT); | |
448 | } else | |
449 | sendcmdto_one(&me, CMD_NOTICE, pptr->client, "%C :UPING: Could not " | |
450 | "start ping to %s", pptr->client, pptr->name); | |
451 | } | |
452 | ||
453 | close(pptr->fd); | |
454 | pptr->fd = -1; | |
455 | uping_erase(pptr); | |
456 | if (pptr->client) | |
457 | ClearUPing(pptr->client); | |
458 | if (pptr->freeable & UPING_PENDING_SOCKET) | |
459 | socket_del(&pptr->socket); | |
460 | if (pptr->freeable & UPING_PENDING_SENDER) | |
461 | timer_del(&pptr->sender); | |
462 | if (pptr->freeable & UPING_PENDING_KILLER) | |
463 | timer_del(&pptr->killer); | |
464 | } | |
465 | ||
466 | /** Change notifications for any upings by \a sptr. | |
467 | * @param[in] sptr Client to stop notifying. | |
468 | * @param[in] acptr New client to notify (or NULL). | |
469 | */ | |
470 | void uping_cancel(struct Client *sptr, struct Client* acptr) | |
471 | { | |
472 | struct UPing* ping; | |
473 | struct UPing* ping_next; | |
474 | ||
475 | Debug((DEBUG_DEBUG, "UPING: canceling uping for %s", cli_name(sptr))); | |
476 | for (ping = pingList; ping; ping = ping_next) { | |
477 | ping_next = ping->next; | |
478 | if (sptr == ping->client) { | |
479 | ping->client = acptr; | |
480 | uping_end(ping); | |
481 | } | |
482 | } | |
483 | ClearUPing(sptr); | |
484 | } |