Clean a bit - to be continued...
[seven-1.x.git] / libseven / commio.c
blobc2f9997d28debebc7da8f487ee97ea27c20546d3
1 /*
2 * ircd-ratbox: A slightly useful ircd.
3 * commio.c: Network/file related functions
5 * Copyright (C) 1990 Jarkko Oikarinen and University of Oulu, Co Center
6 * Copyright (C) 1996-2002 Hybrid Development Team
7 * Copyright (C) 2002-2005 ircd-ratbox development team
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
22 * USA
26 #include "libseven.h"
28 #ifndef IN_LOOPBACKNET
29 #define IN_LOOPBACKNET 0x7f
30 #endif
32 #ifndef INADDR_NONE
33 #define INADDR_NONE ((unsigned int) 0xffffffff)
34 #endif
36 const char *const NONB_ERROR_MSG = "set_non_blocking failed for %s:%s";
37 const char *const SETBUF_ERROR_MSG = "set_sock_buffers failed for server %s:%s";
39 static const char *comm_err_str[] = { "Comm OK", "Error during bind()",
40 "Error during DNS lookup", "connect timeout",
41 "Error during connect()",
42 "Comm Error"
45 fde_t *fd_table = NULL;
47 static void fdlist_update_biggest(int fd, int opening);
49 /* Highest FD and number of open FDs .. */
50 int highest_fd = -1; /* Its -1 because we haven't started yet -- adrian */
51 int number_fd = 0;
53 static void comm_connect_callback(int fd, int status);
54 static PF comm_connect_timeout;
55 static void comm_connect_dns_callback(void *vptr, struct DNSReply *reply);
56 static PF comm_connect_tryconnect;
58 /* 32bit solaris is kinda slow and stdio only supports fds < 256
59 * so we got to do this crap below.
60 * (BTW Fuck you Sun, I hate your guts and I hope you go bankrupt soon)
62 #if defined (__SVR4) && defined (__sun)
63 static void comm_fd_hack(int *fd)
65 int newfd;
66 if(*fd > 256 || *fd < 0)
67 return;
68 if((newfd = fcntl(*fd, F_DUPFD, 256)) != -1)
70 close(*fd);
71 *fd = newfd;
73 return;
75 #else
76 #define comm_fd_hack(fd)
77 #endif
80 /* close_all_connections() can be used *before* the system come up! */
82 void
83 comm_close_all(void)
85 int i;
86 #ifndef NDEBUG
87 int fd;
88 #endif
90 /* XXX someone tell me why we care about 4 fd's ? */
91 /* XXX btw, fd 3 is used for profiler ! */
93 for (i = 4; i < MAXCONNECTIONS; ++i)
95 if(fd_table[i].flags.open)
96 comm_close(i);
97 else
98 close(i);
101 /* XXX should his hack be done in all cases? */
102 #ifndef NDEBUG
103 /* fugly hack to reserve fd == 2 */
104 (void) close(2);
105 fd = open("stderr.log", O_WRONLY | O_CREAT | O_APPEND, 0644);
106 if(fd >= 0)
108 dup2(fd, 2);
109 close(fd);
111 #endif
115 * get_sockerr - get the error value from the socket or the current errno
117 * Get the *real* error from the socket (well try to anyway..).
118 * This may only work when SO_DEBUG is enabled but its worth the
119 * gamble anyway.
122 comm_get_sockerr(int fd)
124 int errtmp = errno;
125 #ifdef SO_ERROR
126 int err = 0;
127 socklen_t len = sizeof(err);
129 if(-1 < fd && !getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *) &err, (socklen_t *) & len))
131 if(err)
132 errtmp = err;
134 errno = errtmp;
135 #endif
136 return errtmp;
140 * set_sock_buffers - set send and receive buffers for socket
142 * inputs - fd file descriptor
143 * - size to set
144 * output - returns true (1) if successful, false (0) otherwise
145 * side effects -
148 comm_set_buffers(int fd, int size)
150 if(setsockopt
151 (fd, SOL_SOCKET, SO_RCVBUF, (char *) &size, sizeof(size))
152 || setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (char *) &size, sizeof(size)))
153 return 0;
154 return 1;
158 * set_non_blocking - Set the client connection into non-blocking mode.
160 * inputs - fd to set into non blocking mode
161 * output - 1 if successful 0 if not
162 * side effects - use POSIX compliant non blocking and
163 * be done with it.
166 comm_set_nb(int fd)
168 int nonb = 0;
169 int res;
171 nonb |= O_NONBLOCK;
172 res = fcntl(fd, F_GETFL, 0);
173 if(-1 == res || fcntl(fd, F_SETFL, res | nonb) == -1)
174 return 0;
176 fd_table[fd].flags.nonblocking = 1;
177 return 1;
182 * stolen from squid - its a neat (but overused! :) routine which we
183 * can use to see whether we can ignore this errno or not. It is
184 * generally useful for non-blocking network IO related errnos.
185 * -- adrian
188 ignoreErrno(int ierrno)
190 switch (ierrno)
192 case EINPROGRESS:
193 case EWOULDBLOCK:
194 #if EAGAIN != EWOULDBLOCK
195 case EAGAIN:
196 #endif
197 case EALREADY:
198 case EINTR:
199 #ifdef ERESTART
200 case ERESTART:
201 #endif
202 return 1;
203 default:
204 return 0;
210 * comm_settimeout() - set the socket timeout
212 * Set the timeout for the fd
214 void
215 comm_settimeout(int fd, time_t timeout, PF * callback, void *cbdata)
217 fde_t *F;
218 s_assert(fd >= 0);
219 F = &fd_table[fd];
220 s_assert(F->flags.open);
222 F->timeout = CurrentTime + (timeout / 1000);
223 F->timeout_handler = callback;
224 F->timeout_data = cbdata;
229 * comm_setflush() - set a flush function
231 * A flush function is simply a function called if found during
232 * comm_timeouts(). Its basically a second timeout, except in this case
233 * I'm too lazy to implement multiple timeout functions! :-)
234 * its kinda nice to have it seperate, since this is designed for
235 * flush functions, and when comm_close() is implemented correctly
236 * with close functions, we _actually_ don't call comm_close() here ..
238 void
239 comm_setflush(int fd, time_t timeout, PF * callback, void *cbdata)
241 fde_t *F;
242 s_assert(fd >= 0);
243 F = &fd_table[fd];
244 s_assert(F->flags.open);
246 F->flush_timeout = CurrentTime + (timeout / 1000);
247 F->flush_handler = callback;
248 F->flush_data = cbdata;
253 * comm_checktimeouts() - check the socket timeouts
255 * All this routine does is call the given callback/cbdata, without closing
256 * down the file descriptor. When close handlers have been implemented,
257 * this will happen.
259 void
260 comm_checktimeouts(void *notused)
262 int fd;
263 PF *hdl;
264 void *data;
265 fde_t *F;
266 for (fd = 0; fd <= highest_fd; fd++)
268 F = &fd_table[fd];
269 if(!F->flags.open)
270 continue;
271 if(F->flags.closing)
272 continue;
274 /* check flush functions */
275 if(F->flush_handler &&
276 F->flush_timeout > 0 && F->flush_timeout < CurrentTime)
278 hdl = F->flush_handler;
279 data = F->flush_data;
280 comm_setflush(F->fd, 0, NULL, NULL);
281 hdl(F->fd, data);
284 /* check timeouts */
285 if(F->timeout_handler &&
286 F->timeout > 0 && F->timeout < CurrentTime)
288 /* Call timeout handler */
289 hdl = F->timeout_handler;
290 data = F->timeout_data;
291 comm_settimeout(F->fd, 0, NULL, NULL);
292 hdl(F->fd, data);
298 * void comm_connect_tcp(int fd, const char *host, u_short port,
299 * struct sockaddr *clocal, int socklen,
300 * CNCB *callback, void *data, int aftype, int timeout)
301 * Input: An fd to connect with, a host and port to connect to,
302 * a local sockaddr to connect from + length(or NULL to use the
303 * default), a callback, the data to pass into the callback, the
304 * address family.
305 * Output: None.
306 * Side-effects: A non-blocking connection to the host is started, and
307 * if necessary, set up for selection. The callback given
308 * may be called now, or it may be called later.
310 void
311 comm_connect_tcp(int fd, const char *host, u_short port,
312 struct sockaddr *clocal, int socklen, CNCB * callback,
313 void *data, int aftype, int timeout)
315 void *ipptr = NULL;
316 fde_t *F;
317 s_assert(fd >= 0);
318 F = &fd_table[fd];
319 F->flags.called_connect = 1;
320 s_assert(callback);
321 F->connect.callback = callback;
322 F->connect.data = data;
324 memset(&F->connect.hostaddr, 0, sizeof(F->connect.hostaddr));
325 #ifdef IPV6
326 if(aftype == AF_INET6)
328 struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)&F->connect.hostaddr;
329 SET_SS_LEN(F->connect.hostaddr, sizeof(struct sockaddr_in6));
330 in6->sin6_port = htons(port);
331 in6->sin6_family = AF_INET6;
332 ipptr = &in6->sin6_addr;
333 } else
334 #endif
336 struct sockaddr_in *in = (struct sockaddr_in *)&F->connect.hostaddr;
337 SET_SS_LEN(F->connect.hostaddr, sizeof(struct sockaddr_in));
338 in->sin_port = htons(port);
339 in->sin_family = AF_INET;
340 ipptr = &in->sin_addr;
343 /* Note that we're using a passed sockaddr here. This is because
344 * generally you'll be bind()ing to a sockaddr grabbed from
345 * getsockname(), so this makes things easier.
346 * XXX If NULL is passed as local, we should later on bind() to the
347 * virtual host IP, for completeness.
348 * -- adrian
350 if((clocal != NULL) && (bind(F->fd, clocal, socklen) < 0))
352 /* Failure, call the callback with COMM_ERR_BIND */
353 comm_connect_callback(F->fd, COMM_ERR_BIND);
354 /* ... and quit */
355 return;
358 /* Next, if we have been given an IP, get the addr and skip the
359 * DNS check (and head direct to comm_connect_tryconnect().
361 if(inetpton(aftype, host, ipptr) <= 0)
363 /* Send the DNS request, for the next level */
364 F->dns_query = MyMalloc(sizeof(struct DNSQuery));
365 F->dns_query->ptr = F;
366 F->dns_query->callback = comm_connect_dns_callback;
367 #ifdef IPV6
368 if (aftype == AF_INET6)
369 gethost_byname_type(host, F->dns_query, T_AAAA);
370 else
371 #endif
372 gethost_byname_type(host, F->dns_query, T_A);
374 else
376 /* We have a valid IP, so we just call tryconnect */
377 /* Make sure we actually set the timeout here .. */
378 comm_settimeout(F->fd, timeout * 1000, comm_connect_timeout, NULL);
379 comm_connect_tryconnect(F->fd, NULL);
384 * comm_connect_callback() - call the callback, and continue with life
386 static void
387 comm_connect_callback(int fd, int status)
389 CNCB *hdl;
390 fde_t *F = &fd_table[fd];
391 /* This check is gross..but probably necessary */
392 if(F->connect.callback == NULL)
393 return;
394 /* Clear the connect flag + handler */
395 hdl = F->connect.callback;
396 F->connect.callback = NULL;
397 F->flags.called_connect = 0;
399 /* Clear the timeout handler */
400 comm_settimeout(F->fd, 0, NULL, NULL);
402 /* Call the handler */
403 hdl(F->fd, status, F->connect.data);
408 * comm_connect_timeout() - this gets called when the socket connection
409 * times out. This *only* can be called once connect() is initially
410 * called ..
412 static void
413 comm_connect_timeout(int fd, void *notused)
415 /* error! */
416 comm_connect_callback(fd, COMM_ERR_TIMEOUT);
421 * comm_connect_dns_callback() - called at the completion of the DNS request
423 * The DNS request has completed, so if we've got an error, return it,
424 * otherwise we initiate the connect()
426 static void
427 comm_connect_dns_callback(void *vptr, struct DNSReply *reply)
429 fde_t *F = vptr;
431 /* Free dns_query now to avoid double reslist free -- jilles */
432 MyFree(F->dns_query);
433 F->dns_query = NULL;
435 if(!reply)
437 comm_connect_callback(F->fd, COMM_ERR_DNS);
438 return;
441 /* No error, set a 10 second timeout */
442 comm_settimeout(F->fd, 30 * 1000, comm_connect_timeout, NULL);
444 /* Copy over the DNS reply info so we can use it in the connect() */
445 #ifdef IPV6
446 if(reply->addr.ss_family == AF_INET6)
448 struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)&F->connect.hostaddr;
449 memcpy(&in6->sin6_addr, &((struct sockaddr_in6 *)&reply->addr)->sin6_addr, sizeof(struct in6_addr));
451 else
452 #endif
454 struct sockaddr_in *in = (struct sockaddr_in *)&F->connect.hostaddr;
455 in->sin_addr.s_addr = ((struct sockaddr_in *)&reply->addr)->sin_addr.s_addr;
458 /* Now, call the tryconnect() routine to try a connect() */
459 comm_connect_tryconnect(F->fd, NULL);
463 /* static void comm_connect_tryconnect(int fd, void *notused)
464 * Input: The fd, the handler data(unused).
465 * Output: None.
466 * Side-effects: Try and connect with pending connect data for the FD. If
467 * we succeed or get a fatal error, call the callback.
468 * Otherwise, it is still blocking or something, so register
469 * to select for a write event on this FD.
471 static void
472 comm_connect_tryconnect(int fd, void *notused)
474 int retval;
475 fde_t *F = &fd_table[fd];
477 if(F->connect.callback == NULL)
478 return;
479 /* Try the connect() */
480 retval = connect(fd, (struct sockaddr *) &fd_table[fd].connect.hostaddr,
481 GET_SS_LEN(fd_table[fd].connect.hostaddr));
482 /* Error? */
483 if(retval < 0)
486 * If we get EISCONN, then we've already connect()ed the socket,
487 * which is a good thing.
488 * -- adrian
490 if(errno == EISCONN)
491 comm_connect_callback(F->fd, COMM_OK);
492 else if(ignoreErrno(errno))
493 /* Ignore error? Reschedule */
494 comm_setselect(F->fd, FDLIST_SERVER, COMM_SELECT_WRITE|COMM_SELECT_RETRY,
495 comm_connect_tryconnect, NULL, 0);
496 else
497 /* Error? Fail with COMM_ERR_CONNECT */
498 comm_connect_callback(F->fd, COMM_ERR_CONNECT);
499 return;
501 /* If we get here, we've suceeded, so call with COMM_OK */
502 comm_connect_callback(F->fd, COMM_OK);
506 * comm_error_str() - return an error string for the given error condition
508 const char *
509 comm_errstr(int error)
511 if(error < 0 || error >= COMM_ERR_MAX)
512 return "Invalid error number!";
513 return comm_err_str[error];
518 * comm_socket() - open a socket
520 * This is a highly highly cut down version of squid's comm_open() which
521 * for the most part emulates socket(), *EXCEPT* it fails if we're about
522 * to run out of file descriptors.
525 comm_socket(int family, int sock_type, int proto, const char *note)
527 int fd;
528 /* First, make sure we aren't going to run out of file descriptors */
529 if(number_fd >= MASTER_MAX)
531 errno = ENFILE;
532 return -1;
536 * Next, we try to open the socket. We *should* drop the reserved FD
537 * limit if/when we get an error, but we can deal with that later.
538 * XXX !!! -- adrian
540 fd = socket(family, sock_type, proto);
541 comm_fd_hack(&fd);
542 if(fd < 0)
543 return -1; /* errno will be passed through, yay.. */
545 #if defined(IPV6) && defined(IPV6_V6ONLY)
547 * Make sure we can take both IPv4 and IPv6 connections
548 * on an AF_INET6 socket
550 if(family == AF_INET6)
552 int off = 1;
553 if(setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &off, sizeof(off)) == -1)
555 libseven_log("comm_socket: Could not set IPV6_V6ONLY option to 1 on FD %d: %s",
556 fd, strerror(errno));
557 close(fd);
558 return -1;
561 #endif
563 /* Set the socket non-blocking, and other wonderful bits */
564 if(!comm_set_nb(fd))
566 libseven_log("comm_open: Couldn't set FD %d non blocking: %s", fd, strerror(errno));
567 close(fd);
568 return -1;
571 /* Next, update things in our fd tracking */
572 comm_open(fd, FD_SOCKET, note);
573 return fd;
578 * comm_accept() - accept an incoming connection
580 * This is a simple wrapper for accept() which enforces FD limits like
581 * comm_open() does.
584 comm_accept(int fd, struct sockaddr *pn, socklen_t *addrlen)
586 int newfd;
587 if(number_fd >= MASTER_MAX)
589 errno = ENFILE;
590 return -1;
594 * Next, do the accept(). if we get an error, we should drop the
595 * reserved fd limit, but we can deal with that when comm_open()
596 * also does it. XXX -- adrian
598 newfd = accept(fd, (struct sockaddr *) pn, addrlen);
599 comm_fd_hack(&newfd);
601 if(newfd < 0)
602 return -1;
604 /* Set the socket non-blocking, and other wonderful bits */
605 if(!comm_set_nb(newfd))
607 libseven_log("comm_accept: Couldn't set FD %d non blocking!", newfd);
608 close(newfd);
609 return -1;
612 /* Next, tag the FD as an incoming connection */
613 comm_open(newfd, FD_SOCKET, "Incoming connection");
615 /* .. and return */
616 return newfd;
620 * If a sockaddr_storage is AF_INET6 but is a mapped IPv4
621 * socket manged the sockaddr.
623 #ifndef mangle_mapped_sockaddr
624 void
625 mangle_mapped_sockaddr(struct sockaddr *in)
627 struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)in;
629 if(in->sa_family == AF_INET)
630 return;
632 if(in->sa_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&in6->sin6_addr))
634 struct sockaddr_in in4;
635 memset(&in4, 0, sizeof(struct sockaddr_in));
636 in4.sin_family = AF_INET;
637 in4.sin_port = in6->sin6_port;
638 in4.sin_addr.s_addr = ((uint32_t *)&in6->sin6_addr)[3];
639 memcpy(in, &in4, sizeof(struct sockaddr_in));
641 return;
643 #endif
646 static void
647 fdlist_update_biggest(int fd, int opening)
649 if(fd < highest_fd)
650 return;
651 s_assert(fd < MAXCONNECTIONS);
653 if(fd > highest_fd)
656 * s_assert that we are not closing a FD bigger than
657 * our known biggest FD
659 s_assert(opening);
660 highest_fd = fd;
661 return;
663 /* if we are here, then fd == Biggest_FD */
665 * s_assert that we are closing the biggest FD; we can't be
666 * re-opening it
668 s_assert(!opening);
669 while (highest_fd >= 0 && !fd_table[highest_fd].flags.open)
670 highest_fd--;
674 void
675 fdlist_init(void)
677 static int initialized = 0;
679 if(!initialized)
681 /* Since we're doing this once .. */
682 fd_table = MyMalloc((MAXCONNECTIONS + 1) * sizeof(fde_t));
683 initialized = 1;
687 /* Called to open a given filedescriptor */
688 void
689 comm_open(int fd, unsigned int type, const char *desc)
691 fde_t *F = &fd_table[fd];
692 s_assert(fd >= 0);
694 if(F->flags.open)
696 comm_close(fd);
698 s_assert(!F->flags.open);
699 F->fd = fd;
700 F->type = type;
701 F->flags.open = 1;
702 #ifdef NOTYET
703 F->defer.until = 0;
704 F->defer.n = 0;
705 F->defer.handler = NULL;
706 #endif
707 fdlist_update_biggest(fd, 1);
708 F->comm_index = -1;
709 F->list = FDLIST_NONE;
710 if(desc)
711 strlcpy(F->desc, desc, sizeof(F->desc));
712 number_fd++;
716 /* Called to close a given filedescriptor */
717 void
718 comm_close(int fd)
720 fde_t *F = &fd_table[fd];
721 s_assert(F->flags.open);
722 /* All disk fd's MUST go through file_close() ! */
723 s_assert(F->type != FD_FILE);
724 if(F->type == FD_FILE)
726 s_assert(F->read_handler == NULL);
727 s_assert(F->write_handler == NULL);
729 comm_setselect(F->fd, FDLIST_NONE, COMM_SELECT_WRITE | COMM_SELECT_READ, NULL, NULL, 0);
730 comm_setflush(F->fd, 0, NULL, NULL);
732 if (F->dns_query != NULL)
734 delete_resolver_queries(F->dns_query);
735 MyFree(F->dns_query);
736 F->dns_query = NULL;
739 F->flags.open = 0;
740 fdlist_update_biggest(fd, 0);
741 number_fd--;
742 memset(F, '\0', sizeof(fde_t));
743 F->timeout = 0;
744 /* Unlike squid, we're actually closing the FD here! -- adrian */
745 close(fd);
750 * comm_dump() - dump the list of active filedescriptors
752 void
753 comm_dump(struct Client *source_p)
755 int i;
757 for (i = 0; i <= highest_fd; i++)
759 if(!fd_table[i].flags.open)
760 continue;
762 sendto_one_numeric(source_p, RPL_STATSDEBUG,
763 "F :fd %-3d desc '%s'",
764 i, fd_table[i].desc);
769 * comm_note() - set the fd note
771 * Note: must be careful not to overflow fd_table[fd].desc when
772 * calling.
774 void
775 comm_note(int fd, const char *format, ...)
777 va_list args;
779 if(format)
781 va_start(args, format);
782 ircvsnprintf(fd_table[fd].desc, FD_DESC_SZ, format, args);
783 va_end(args);
785 else
786 fd_table[fd].desc[0] = '\0';