1 /************************************************************************
2 * IRC - Internet Relay Chat, src/listener.c
3 * Copyright (C) 1999 Thomas Helvey <tomh@inxpress.net>
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)
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.
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.
26 #include "ircd_alloc.h"
27 #include "ircd_events.h"
28 #include "ircd_features.h"
29 #include "ircd_osdep.h"
30 #include "ircd_reply.h"
31 #include "ircd_snprintf.h"
32 #include "ircd_string.h"
40 #include "sys.h" /* MAXCLIENTS */
49 #include <sys/socket.h>
50 #include <arpa/inet.h>
53 #define INADDR_NONE ((unsigned int) 0xffffffff)
56 struct Listener* ListenerPollList = 0;
58 static void accept_connection(struct Event* ev);
60 static struct Listener* make_listener(int port, const struct irc_in_addr *addr)
62 struct Listener* listener =
63 (struct Listener*) MyMalloc(sizeof(struct Listener));
64 assert(0 != listener);
66 memset(listener, 0, sizeof(struct Listener));
69 listener->addr.port = port;
70 memcpy(&listener->addr.addr, addr, sizeof(listener->addr.addr));
72 #ifdef NULL_POINTER_NOT_ZERO
73 listener->next = NULL;
74 listener->conf = NULL;
79 static void free_listener(struct Listener* listener)
81 assert(0 != listener);
85 #define PORTNAMELEN 10 /* ":31337" */
88 * get_listener_name - return displayable listener name and port
89 * returns "host.foo.org:6667" for a given listener
91 const char* get_listener_name(const struct Listener* listener)
93 static char buf[HOSTLEN + PORTNAMELEN + 4];
94 assert(0 != listener);
95 ircd_snprintf(0, buf, sizeof(buf), "%s:%u", cli_name(&me), listener->addr.port);
100 * count_listener_memory - count memory and listeners
102 void count_listener_memory(int* count_out, size_t* size_out)
106 assert(0 != count_out);
107 assert(0 != size_out);
108 for (l = ListenerPollList; l; l = l->next)
111 *size_out = count * sizeof(struct Listener);
115 * show_ports - send port listing to a client
116 * inputs - pointer to client to show ports to
118 * side effects - show ports
121 void show_ports(struct Client* sptr, struct StatDesc* sd, int stat,
124 struct Listener *listener = 0;
126 int show_hidden = IsOper(sptr);
127 int count = (IsOper(sptr) || MyUser(sptr)) ? 100 : 8;
135 for (listener = ListenerPollList; listener; listener = listener->next) {
136 if (port && port != listener->addr.port)
138 flags[0] = (listener->server) ? 'S' : 'C';
139 if (listener->hidden) {
148 send_reply(sptr, RPL_STATSPLINE, listener->addr.port, listener->ref_count,
149 flags, (listener->active) ? "active" : "disabled");
156 * inetport - create a listener socket in the AF_INET domain,
157 * bind it to the port given in 'port' and listen to it
158 * returns true (1) if successful false (0) on error.
160 * If the operating system has a define for SOMAXCONN, use it, otherwise
161 * use HYBRID_SOMAXCONN -Dianora
162 * NOTE: Do this in os_xxxx.c files
165 #define HYBRID_SOMAXCONN SOMAXCONN
167 #define HYBRID_SOMAXCONN 64
170 static int inetport(struct Listener* listener)
175 * At first, open a new socket
177 fd = os_socket(&listener->addr, SOCK_STREAM, get_listener_name(listener));
181 * Set the buffer sizes for the listener. Accepted connections
182 * inherit the accepting sockets settings for SO_RCVBUF S_SNDBUF
183 * The window size is set during the SYN ACK so setting it anywhere
184 * else has no effect whatsoever on the connection.
185 * NOTE: this must be set before listen is called
187 if (!os_set_sockbufs(fd,
188 (listener->server) ? feature_int(FEAT_SOCKSENDBUF) : CLIENT_TCP_WINDOW,
189 (listener->server) ? feature_int(FEAT_SOCKRECVBUF) : CLIENT_TCP_WINDOW)) {
190 report_error(SETBUFS_ERROR_MSG, get_listener_name(listener), errno);
194 if (!os_set_listen(fd, HYBRID_SOMAXCONN)) {
195 report_error(LISTEN_ERROR_MSG, get_listener_name(listener), errno);
200 * Set the TOS bits - this is nonfatal if it doesn't stick.
202 if (!os_set_tos(fd,feature_int((listener->server)?FEAT_TOS_SERVER : FEAT_TOS_CLIENT))) {
203 report_error(TOS_ERROR_MSG, get_listener_name(listener), errno);
206 if (!socket_add(&listener->socket, accept_connection, (void*) listener,
207 SS_LISTENING, 0, fd)) {
208 /* Error should already have been reported to the logs */
219 * find_listener - find a listener in the list
221 * XXX - this function does N comparisons so if the list is huge
222 * we may want to do something else for this. (rehash and init use this)
224 static struct Listener* find_listener(int port, const struct irc_in_addr *addr)
226 struct Listener* listener;
227 for (listener = ListenerPollList; listener; listener = listener->next) {
228 if (port == listener->addr.port && !memcmp(addr, &listener->addr.addr, sizeof(*addr)))
235 * add_listener- create a new listener
236 * port - the port number to listen on
237 * vhost_ip - if non-null must contain a valid IP address string in
238 * the format "255.255.255.255"
240 void add_listener(int port, const char* vhost_ip, const char* mask,
241 int is_server, int is_hidden)
243 struct Listener* listener;
244 struct irc_in_addr vaddr;
247 * if no port in conf line, don't bother
252 memset(&vaddr, 0, sizeof(vaddr));
254 if (!EmptyString(vhost_ip)
255 && strcmp(vhost_ip, "*")
256 && !ircd_aton(&vaddr, vhost_ip))
259 if ((listener = find_listener(port, &vaddr))) {
261 * set active flag and change connect mask here, it's the only thing
262 * that can change on a rehash
264 listener->active = 1;
266 ipmask_parse(mask, &listener->mask, &listener->mask_bits);
268 listener->mask_bits = 0;
269 listener->hidden = is_hidden;
270 listener->server = is_server;
274 listener = make_listener(port, &vaddr);
276 if (inetport(listener)) {
277 listener->active = 1;
279 ipmask_parse(mask, &listener->mask, &listener->mask_bits);
281 listener->mask_bits = 0;
282 listener->hidden = is_hidden;
283 listener->server = is_server;
284 listener->next = ListenerPollList;
285 ListenerPollList = listener;
288 free_listener(listener);
292 * mark_listeners_closing - iterate through listeners and mark them as
295 void mark_listeners_closing(void)
297 struct Listener* listener;
298 for (listener = ListenerPollList; listener; listener = listener->next)
299 listener->active = 0;
303 * close_listener - close a single listener
305 void close_listener(struct Listener* listener)
307 assert(0 != listener);
309 * remove from listener list
311 if (listener == ListenerPollList)
312 ListenerPollList = listener->next;
314 struct Listener* prev = ListenerPollList;
315 for ( ; prev; prev = prev->next) {
316 if (listener == prev->next) {
317 prev->next = listener->next;
322 if (-1 < listener->fd)
324 socket_del(&listener->socket);
328 * close_listeners - close and free all listeners that are not being used
330 void close_listeners()
332 struct Listener* listener;
333 struct Listener* listener_next = 0;
335 * close all 'extra' listening ports we have
337 for (listener = ListenerPollList; listener; listener = listener_next) {
338 listener_next = listener->next;
339 if (0 == listener->active && 0 == listener->ref_count)
340 close_listener(listener);
344 void release_listener(struct Listener* listener)
346 assert(0 != listener);
347 assert(0 < listener->ref_count);
348 if (0 == --listener->ref_count && !listener->active)
349 close_listener(listener);
353 * accept_connection - accept a connection on a listener
355 static void accept_connection(struct Event* ev)
357 struct Listener* listener;
358 struct irc_sockaddr addr;
361 assert(0 != ev_socket(ev));
362 assert(0 != s_data(ev_socket(ev)));
364 listener = (struct Listener*) s_data(ev_socket(ev));
366 if (ev_type(ev) == ET_DESTROY) /* being destroyed */
367 free_listener(listener);
369 assert(ev_type(ev) == ET_ACCEPT || ev_type(ev) == ET_ERROR);
371 listener->last_accept = CurrentTime;
373 * There may be many reasons for error return, but
374 * in otherwise correctly working environment the
375 * probable cause is running out of file descriptors
376 * (EMFILE, ENFILE or others?). The man pages for
377 * accept don't seem to list these as possible,
378 * although it's obvious that it may happen here.
379 * Thus no specific errors are tested at this
380 * point, just assume that connections cannot
381 * be accepted until some old is closed first.
383 * This piece of code implements multi-accept, based
384 * on the idea that poll/select can only be efficient,
385 * if we succeed in handling all available events,
386 * i.e. accept all pending connections.
388 * http://www.hpl.hp.com/techreports/2000/HPL-2000-174.html
392 if ((fd = os_accept(listener->fd, &addr)) == -1)
394 if (errno == EAGAIN ||
396 errno == EWOULDBLOCK)
399 /* Lotsa admins seem to have problems with not giving enough file
400 * descriptors to their server so we'll add a generic warning mechanism
401 * here. If it turns out too many messages are generated for
402 * meaningless reasons we can filter them back.
404 sendto_opmask_butone(0, SNO_TCPCOMMON,
405 "Unable to accept connection: %m");
409 * check for connection limit. If this fd exceeds the limit,
410 * all further accept()ed connections will also exceed it.
411 * Enable the server to clear out other connections before
412 * continuing to accept() new connections.
414 if (fd > MAXCLIENTS - 1)
416 ++ServerStats->is_ref;
417 send(fd, "ERROR :All connections in use\r\n", 32, 0);
422 * check to see if listener is shutting down. Continue
423 * to accept(), because it makes sense to clear our the
424 * socket's queue as fast as possible.
426 if (!listener->active)
428 ++ServerStats->is_ref;
429 send(fd, "ERROR :Use another port\r\n", 25, 0);
434 * check to see if connection is allowed for this address mask
436 if (!ipmask_check(&addr.addr, &listener->mask, listener->mask_bits))
438 ++ServerStats->is_ref;
439 send(fd, "ERROR :Use another port\r\n", 25, 0);
443 ++ServerStats->is_ac;
444 /* nextping = CurrentTime; */
445 add_connection(listener, fd);