]>
Commit | Line | Data |
---|---|---|
189935b1 | 1 | /************************************************************************ |
2 | * IRC - Internet Relay Chat, src/listener.c | |
3 | * Copyright (C) 1999 Thomas Helvey <tomh@inxpress.net> | |
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 1, 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 Implementation for handling listening sockets. | |
c02e87ad | 21 | * @version $Id: listener.c,v 1.28.2.6 2007/05/20 14:10:30 entrope Exp $ |
189935b1 | 22 | */ |
23 | #include "config.h" | |
24 | ||
25 | #include "listener.h" | |
26 | #include "client.h" | |
27 | #include "ircd.h" | |
28 | #include "ircd_alloc.h" | |
29 | #include "ircd_events.h" | |
30 | #include "ircd_features.h" | |
31 | #include "ircd_log.h" | |
32 | #include "ircd_osdep.h" | |
33 | #include "ircd_reply.h" | |
34 | #include "ircd_snprintf.h" | |
35 | #include "ircd_string.h" | |
36 | #include "match.h" | |
37 | #include "numeric.h" | |
38 | #include "s_bsd.h" | |
39 | #include "s_conf.h" | |
40 | #include "s_misc.h" | |
41 | #include "s_stats.h" | |
42 | #include "send.h" | |
43 | #include "sys.h" /* MAXCLIENTS */ | |
44 | ||
45 | /* #include <assert.h> -- Now using assert in ircd_log.h */ | |
46 | #include <stdio.h> | |
47 | #include <string.h> | |
48 | #include <errno.h> | |
49 | #include <stdlib.h> | |
50 | #include <unistd.h> | |
51 | #include <netdb.h> | |
52 | #include <sys/socket.h> | |
53 | ||
54 | /** List of listening sockets. */ | |
55 | struct Listener* ListenerPollList = 0; | |
56 | ||
57 | static void accept_connection(struct Event* ev); | |
58 | ||
59 | /** Allocate and initialize a new Listener structure for a particular | |
60 | * socket address. | |
61 | * @param[in] port Port number to listen on. | |
62 | * @param[in] addr Local address to listen on. | |
63 | * @return Newly allocated and initialized Listener. | |
64 | */ | |
65 | static struct Listener* make_listener(int port, const struct irc_in_addr *addr) | |
66 | { | |
67 | struct Listener* listener = | |
68 | (struct Listener*) MyMalloc(sizeof(struct Listener)); | |
69 | assert(0 != listener); | |
70 | ||
71 | memset(listener, 0, sizeof(struct Listener)); | |
72 | ||
0c466275 | 73 | listener->fd_v4 = -1; |
74 | listener->fd_v6 = -1; | |
189935b1 | 75 | listener->addr.port = port; |
76 | memcpy(&listener->addr.addr, addr, sizeof(listener->addr.addr)); | |
77 | ||
78 | #ifdef NULL_POINTER_NOT_ZERO | |
79 | listener->next = NULL; | |
80 | listener->conf = NULL; | |
81 | #endif | |
82 | return listener; | |
83 | } | |
84 | ||
85 | /** Deallocate a Listener structure. | |
86 | * @param[in] listener Listener to be freed. | |
87 | */ | |
88 | static void free_listener(struct Listener* listener) | |
89 | { | |
90 | assert(0 != listener); | |
91 | MyFree(listener); | |
92 | } | |
93 | ||
94 | /** Maximum length for a port number. */ | |
95 | #define PORTNAMELEN 10 /* ":31337" */ | |
96 | ||
97 | /** Return displayable listener name and port. | |
98 | * @param[in] listener %Listener to format as a text string. | |
99 | * @return Pointer to a static buffer that contains "server.name:6667". | |
100 | */ | |
101 | const char* get_listener_name(const struct Listener* listener) | |
102 | { | |
103 | static char buf[HOSTLEN + PORTNAMELEN + 4]; | |
104 | assert(0 != listener); | |
105 | ircd_snprintf(0, buf, sizeof(buf), "%s:%u", cli_name(&me), listener->addr.port); | |
106 | return buf; | |
107 | } | |
108 | ||
109 | /** Count allocated listeners and the memory they use. | |
110 | * @param[out] count_out Receives number of allocated listeners. | |
111 | * @param[out] size_out Receives bytes used by listeners. | |
112 | */ | |
113 | void count_listener_memory(int* count_out, size_t* size_out) | |
114 | { | |
115 | struct Listener* l; | |
116 | int count = 0; | |
117 | assert(0 != count_out); | |
118 | assert(0 != size_out); | |
119 | for (l = ListenerPollList; l; l = l->next) | |
120 | ++count; | |
121 | *count_out = count; | |
122 | *size_out = count * sizeof(struct Listener); | |
123 | } | |
124 | ||
125 | /** Report listening ports to a client. | |
126 | * @param[in] sptr Client requesting statistics. | |
127 | * @param[in] sd Stats descriptor for request (ignored). | |
128 | * @param[in] param Extra parameter from user (port number to search for). | |
129 | */ | |
130 | void show_ports(struct Client* sptr, const struct StatDesc* sd, | |
131 | char* param) | |
132 | { | |
133 | struct Listener *listener = 0; | |
134 | char flags[8]; | |
135 | int show_hidden = IsOper(sptr); | |
136 | int count = (IsOper(sptr) || MyUser(sptr)) ? 100 : 8; | |
137 | int port = 0; | |
0c466275 | 138 | int len; |
189935b1 | 139 | |
140 | assert(0 != sptr); | |
141 | ||
142 | if (param) | |
143 | port = atoi(param); | |
144 | ||
145 | for (listener = ListenerPollList; listener; listener = listener->next) { | |
146 | if (port && port != listener->addr.port) | |
147 | continue; | |
0c466275 | 148 | len = 0; |
149 | flags[len++] = listener_server(listener) ? 'S' : 'C'; | |
c02e87ad | 150 | if (FlagHas(&listener->flags, LISTEN_HIDDEN)) |
151 | { | |
152 | if (!show_hidden) | |
153 | continue; | |
0c466275 | 154 | flags[len++] = 'H'; |
c02e87ad | 155 | } |
0c466275 | 156 | if (FlagHas(&listener->flags, LISTEN_IPV4)) |
157 | { | |
158 | flags[len++] = '4'; | |
159 | if (listener->fd_v4 < 0) | |
160 | flags[len++] = '-'; | |
161 | } | |
162 | if (FlagHas(&listener->flags, LISTEN_IPV6)) | |
163 | { | |
164 | flags[len++] = '6'; | |
165 | if (listener->fd_v6 < 0) | |
166 | flags[len++] = '-'; | |
189935b1 | 167 | } |
0c466275 | 168 | flags[len] = '\0'; |
189935b1 | 169 | |
170 | send_reply(sptr, RPL_STATSPLINE, listener->addr.port, listener->ref_count, | |
0c466275 | 171 | flags, listener_active(listener) ? "active" : "disabled"); |
189935b1 | 172 | if (--count == 0) |
173 | break; | |
174 | } | |
175 | } | |
176 | ||
177 | /* | |
178 | * inetport - create a listener socket in the AF_INET domain, | |
179 | * bind it to the port given in 'port' and listen to it | |
180 | * returns true (1) if successful false (0) on error. | |
181 | * | |
182 | * If the operating system has a define for SOMAXCONN, use it, otherwise | |
183 | * use HYBRID_SOMAXCONN -Dianora | |
184 | * NOTE: Do this in os_xxxx.c files | |
185 | */ | |
186 | #ifdef SOMAXCONN | |
187 | #define HYBRID_SOMAXCONN SOMAXCONN | |
188 | #else | |
189 | /** Maximum length of socket connection backlog. */ | |
190 | #define HYBRID_SOMAXCONN 64 | |
191 | #endif | |
192 | ||
0c466275 | 193 | /** Set or update socket options for \a listener. |
194 | * @param[in] listener Listener to determine socket option values. | |
195 | * @param[in] fd File descriptor being updated. | |
189935b1 | 196 | * @return Non-zero on success, zero on failure. |
197 | */ | |
0c466275 | 198 | static int set_listener_options(struct Listener *listener, int fd) |
189935b1 | 199 | { |
0c466275 | 200 | int is_server; |
189935b1 | 201 | |
0c466275 | 202 | is_server = listener_server(listener); |
189935b1 | 203 | /* |
204 | * Set the buffer sizes for the listener. Accepted connections | |
205 | * inherit the accepting sockets settings for SO_RCVBUF S_SNDBUF | |
206 | * The window size is set during the SYN ACK so setting it anywhere | |
207 | * else has no effect whatsoever on the connection. | |
208 | * NOTE: this must be set before listen is called | |
209 | */ | |
210 | if (!os_set_sockbufs(fd, | |
0c466275 | 211 | is_server ? feature_int(FEAT_SOCKSENDBUF) : CLIENT_TCP_WINDOW, |
212 | is_server ? feature_int(FEAT_SOCKRECVBUF) : CLIENT_TCP_WINDOW)) { | |
189935b1 | 213 | report_error(SETBUFS_ERROR_MSG, get_listener_name(listener), errno); |
214 | close(fd); | |
215 | return 0; | |
216 | } | |
0c466275 | 217 | |
189935b1 | 218 | /* |
219 | * Set the TOS bits - this is nonfatal if it doesn't stick. | |
220 | */ | |
0c466275 | 221 | if (!os_set_tos(fd,feature_int(is_server ? FEAT_TOS_SERVER : FEAT_TOS_CLIENT))) { |
189935b1 | 222 | report_error(TOS_ERROR_MSG, get_listener_name(listener), errno); |
223 | } | |
224 | ||
0c466275 | 225 | return 1; |
226 | } | |
227 | ||
228 | /** Open listening socket for \a listener. | |
229 | * @param[in,out] listener Listener to make a socket for. | |
230 | * @param[in] family Socket address family to use. | |
231 | * @return Negative on failure, file descriptor on success. | |
232 | */ | |
233 | static int inetport(struct Listener* listener, int family) | |
234 | { | |
235 | struct Socket *sock; | |
236 | int fd; | |
237 | ||
238 | /* | |
239 | * At first, open a new socket | |
240 | */ | |
241 | fd = os_socket(&listener->addr, SOCK_STREAM, get_listener_name(listener), family); | |
242 | if (fd < 0) | |
243 | return -1; | |
244 | if (!os_set_listen(fd, HYBRID_SOMAXCONN)) { | |
245 | report_error(LISTEN_ERROR_MSG, get_listener_name(listener), errno); | |
246 | close(fd); | |
247 | return -1; | |
248 | } | |
249 | if (!set_listener_options(listener, fd)) | |
250 | return -1; | |
251 | sock = (family == AF_INET) ? &listener->socket_v4 : &listener->socket_v6; | |
252 | if (!socket_add(sock, accept_connection, (void*) listener, | |
189935b1 | 253 | SS_LISTENING, 0, fd)) { |
254 | /* Error should already have been reported to the logs */ | |
255 | close(fd); | |
0c466275 | 256 | return -1; |
189935b1 | 257 | } |
258 | ||
0c466275 | 259 | return fd; |
189935b1 | 260 | } |
261 | ||
262 | /** Find the listener (if any) for a particular port and address. | |
263 | * @param[in] port Port number to search for. | |
264 | * @param[in] addr Local address to search for. | |
265 | * @return Listener that matches (or NULL if none match). | |
266 | */ | |
267 | static struct Listener* find_listener(int port, const struct irc_in_addr *addr) | |
268 | { | |
269 | struct Listener* listener; | |
270 | for (listener = ListenerPollList; listener; listener = listener->next) { | |
271 | if (port == listener->addr.port && !memcmp(addr, &listener->addr.addr, sizeof(*addr))) | |
272 | return listener; | |
273 | } | |
274 | return 0; | |
275 | } | |
276 | ||
277 | /** Make sure we have a listener for \a port on \a vhost_ip. | |
278 | * If one does not exist, create it. Then mark it as active and set | |
279 | * the peer mask, server, and hidden flags according to the other | |
280 | * arguments. | |
281 | * @param[in] port Port number to listen on. | |
282 | * @param[in] vhost_ip Local address to listen on. | |
283 | * @param[in] mask Address mask to accept connections from. | |
0c466275 | 284 | * @param[in] flags Flags describing listener options. |
189935b1 | 285 | */ |
286 | void add_listener(int port, const char* vhost_ip, const char* mask, | |
0c466275 | 287 | const struct ListenerFlags *flags) |
189935b1 | 288 | { |
289 | struct Listener* listener; | |
290 | struct irc_in_addr vaddr; | |
0c466275 | 291 | int okay = 0; |
292 | int new_listener = 0; | |
293 | int fd; | |
189935b1 | 294 | |
295 | /* | |
296 | * if no port in conf line, don't bother | |
297 | */ | |
298 | if (0 == port) | |
299 | return; | |
300 | ||
301 | memset(&vaddr, 0, sizeof(vaddr)); | |
302 | ||
303 | if (!EmptyString(vhost_ip) | |
304 | && strcmp(vhost_ip, "*") | |
305 | && !ircd_aton(&vaddr, vhost_ip)) | |
306 | return; | |
307 | ||
308 | listener = find_listener(port, &vaddr); | |
309 | if (!listener) | |
0c466275 | 310 | { |
311 | new_listener = 1; | |
189935b1 | 312 | listener = make_listener(port, &vaddr); |
0c466275 | 313 | } |
314 | memcpy(&listener->flags, flags, sizeof(listener->flags)); | |
315 | FlagSet(&listener->flags, LISTEN_ACTIVE); | |
189935b1 | 316 | if (mask) |
317 | ipmask_parse(mask, &listener->mask, &listener->mask_bits); | |
318 | else | |
319 | listener->mask_bits = 0; | |
320 | ||
0c466275 | 321 | #ifdef IPV6 |
a53f2ff1 | 322 | if (FlagHas(&listener->flags, LISTEN_IPV6) |
323 | && (irc_in_addr_unspec(&vaddr) || !irc_in_addr_is_ipv4(&vaddr))) { | |
0c466275 | 324 | if (listener->fd_v6 >= 0) { |
325 | set_listener_options(listener, listener->fd_v6); | |
326 | okay = 1; | |
327 | } else if ((fd = inetport(listener, AF_INET6)) >= 0) { | |
328 | listener->fd_v6 = fd; | |
329 | okay = 1; | |
330 | } | |
331 | } else if (-1 < listener->fd_v6) { | |
332 | close(listener->fd_v6); | |
333 | socket_del(&listener->socket_v6); | |
334 | listener->fd_v6 = -1; | |
189935b1 | 335 | } |
0c466275 | 336 | #endif |
337 | ||
a53f2ff1 | 338 | if (FlagHas(&listener->flags, LISTEN_IPV4) |
339 | && (irc_in_addr_unspec(&vaddr) || irc_in_addr_is_ipv4(&vaddr))) { | |
0c466275 | 340 | if (listener->fd_v4 >= 0) { |
341 | set_listener_options(listener, listener->fd_v4); | |
342 | okay = 1; | |
343 | } else if ((fd = inetport(listener, AF_INET)) >= 0) { | |
344 | listener->fd_v4 = fd; | |
345 | okay = 1; | |
346 | } | |
347 | } else if (-1 < listener->fd_v4) { | |
348 | close(listener->fd_v4); | |
349 | socket_del(&listener->socket_v4); | |
350 | listener->fd_v4 = -1; | |
351 | } | |
352 | ||
353 | if (!okay) | |
354 | free_listener(listener); | |
355 | else if (new_listener) { | |
189935b1 | 356 | listener->next = ListenerPollList; |
357 | ListenerPollList = listener; | |
358 | } | |
189935b1 | 359 | } |
360 | ||
361 | /** Mark all listeners as closing (inactive). | |
362 | * This is done so unused listeners are closed after a rehash. | |
363 | */ | |
364 | void mark_listeners_closing(void) | |
365 | { | |
366 | struct Listener* listener; | |
367 | for (listener = ListenerPollList; listener; listener = listener->next) | |
0c466275 | 368 | FlagClr(&listener->flags, LISTEN_ACTIVE); |
189935b1 | 369 | } |
370 | ||
371 | /** Close a single listener. | |
372 | * @param[in] listener Listener to close. | |
373 | */ | |
374 | void close_listener(struct Listener* listener) | |
375 | { | |
376 | assert(0 != listener); | |
377 | /* | |
378 | * remove from listener list | |
379 | */ | |
380 | if (listener == ListenerPollList) | |
381 | ListenerPollList = listener->next; | |
382 | else { | |
383 | struct Listener* prev = ListenerPollList; | |
384 | for ( ; prev; prev = prev->next) { | |
385 | if (listener == prev->next) { | |
386 | prev->next = listener->next; | |
387 | break; | |
388 | } | |
389 | } | |
390 | } | |
0c466275 | 391 | if (-1 < listener->fd_v4) { |
392 | close(listener->fd_v4); | |
393 | socket_del(&listener->socket_v4); | |
394 | listener->fd_v4 = -1; | |
395 | } | |
396 | if (-1 < listener->fd_v6) { | |
397 | close(listener->fd_v6); | |
398 | socket_del(&listener->socket_v6); | |
399 | listener->fd_v6 = -1; | |
400 | } | |
401 | free_listener(listener); | |
189935b1 | 402 | } |
403 | ||
404 | /** Close all inactive listeners. */ | |
405 | void close_listeners(void) | |
406 | { | |
407 | struct Listener* listener; | |
408 | struct Listener* listener_next = 0; | |
409 | /* | |
410 | * close all 'extra' listening ports we have | |
411 | */ | |
412 | for (listener = ListenerPollList; listener; listener = listener_next) { | |
413 | listener_next = listener->next; | |
0c466275 | 414 | if (!listener_active(listener) && 0 == listener->ref_count) |
189935b1 | 415 | close_listener(listener); |
416 | } | |
417 | } | |
418 | ||
419 | /** Dereference the listener previously associated with a client. | |
420 | * @param[in] listener Listener to dereference. | |
421 | */ | |
422 | void release_listener(struct Listener* listener) | |
423 | { | |
424 | assert(0 != listener); | |
425 | assert(0 < listener->ref_count); | |
0c466275 | 426 | if (0 == --listener->ref_count && !listener_active(listener)) |
189935b1 | 427 | close_listener(listener); |
428 | } | |
429 | ||
430 | /** Accept a connection on a listener. | |
431 | * @param[in] ev Socket callback structure. | |
432 | */ | |
433 | static void accept_connection(struct Event* ev) | |
434 | { | |
435 | struct Listener* listener; | |
436 | struct irc_sockaddr addr; | |
437 | int fd; | |
438 | ||
439 | assert(0 != ev_socket(ev)); | |
440 | assert(0 != s_data(ev_socket(ev))); | |
441 | ||
442 | listener = (struct Listener*) s_data(ev_socket(ev)); | |
443 | ||
444 | if (ev_type(ev) == ET_DESTROY) /* being destroyed */ | |
0c466275 | 445 | return; |
189935b1 | 446 | else { |
447 | assert(ev_type(ev) == ET_ACCEPT || ev_type(ev) == ET_ERROR); | |
448 | ||
449 | listener->last_accept = CurrentTime; | |
450 | /* | |
451 | * There may be many reasons for error return, but | |
452 | * in otherwise correctly working environment the | |
453 | * probable cause is running out of file descriptors | |
454 | * (EMFILE, ENFILE or others?). The man pages for | |
455 | * accept don't seem to list these as possible, | |
456 | * although it's obvious that it may happen here. | |
457 | * Thus no specific errors are tested at this | |
458 | * point, just assume that connections cannot | |
459 | * be accepted until some old is closed first. | |
460 | * | |
461 | * This piece of code implements multi-accept, based | |
462 | * on the idea that poll/select can only be efficient, | |
463 | * if we succeed in handling all available events, | |
464 | * i.e. accept all pending connections. | |
465 | * | |
466 | * http://www.hpl.hp.com/techreports/2000/HPL-2000-174.html | |
467 | */ | |
468 | while (1) | |
469 | { | |
0c466275 | 470 | if ((fd = os_accept(s_fd(ev_socket(ev)), &addr)) == -1) |
189935b1 | 471 | { |
472 | if (errno == EAGAIN || | |
473 | #ifdef EWOULDBLOCK | |
474 | errno == EWOULDBLOCK) | |
475 | #endif | |
476 | return; | |
477 | /* Lotsa admins seem to have problems with not giving enough file | |
478 | * descriptors to their server so we'll add a generic warning mechanism | |
479 | * here. If it turns out too many messages are generated for | |
480 | * meaningless reasons we can filter them back. | |
481 | */ | |
482 | sendto_opmask_butone(0, SNO_TCPCOMMON, | |
483 | "Unable to accept connection: %m"); | |
484 | return; | |
485 | } | |
486 | /* | |
487 | * check for connection limit. If this fd exceeds the limit, | |
488 | * all further accept()ed connections will also exceed it. | |
489 | * Enable the server to clear out other connections before | |
490 | * continuing to accept() new connections. | |
491 | */ | |
492 | if (fd > MAXCLIENTS - 1) | |
493 | { | |
494 | ++ServerStats->is_ref; | |
495 | send(fd, "ERROR :All connections in use\r\n", 32, 0); | |
496 | close(fd); | |
497 | return; | |
498 | } | |
499 | /* | |
500 | * check to see if listener is shutting down. Continue | |
501 | * to accept(), because it makes sense to clear our the | |
502 | * socket's queue as fast as possible. | |
503 | */ | |
0c466275 | 504 | if (!listener_active(listener)) |
189935b1 | 505 | { |
506 | ++ServerStats->is_ref; | |
507 | send(fd, "ERROR :Use another port\r\n", 25, 0); | |
508 | close(fd); | |
509 | continue; | |
510 | } | |
511 | /* | |
512 | * check to see if connection is allowed for this address mask | |
513 | */ | |
514 | if (!ipmask_check(&addr.addr, &listener->mask, listener->mask_bits)) | |
515 | { | |
516 | ++ServerStats->is_ref; | |
517 | send(fd, "ERROR :Use another port\r\n", 25, 0); | |
518 | close(fd); | |
519 | continue; | |
520 | } | |
521 | ++ServerStats->is_ac; | |
522 | /* nextping = CurrentTime; */ | |
523 | add_connection(listener, fd); | |
524 | } | |
525 | } | |
526 | } |