]>
Commit | Line | Data |
---|---|---|
189935b1 | 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 |