From f36d66b0cb3a11341dec25f6b205bfe86c903360 Mon Sep 17 00:00:00 2001 From: Přemysl Janouch Date: Sat, 16 Jan 2016 04:02:52 +0100 Subject: kike: asynchronous address resolution As well as some refactoring and cleanup. --- NEWS | 4 +- kike.c | 190 +++++++++++++++++++++++++++++++++++++++++++++-------------------- 2 files changed, 134 insertions(+), 60 deletions(-) diff --git a/NEWS b/NEWS index e481cac..9f9eca0 100644 --- a/NEWS +++ b/NEWS @@ -6,14 +6,14 @@ * degesch: added autocomplete for /topic - * degesch: resolve remote addresses asynchronously - * degesch: Lua API was improved and extended * degesch: added a basic last.fm "now playing" plugin * degesch: backlog limit was made configurable + * Remote addresses are now resolved asynchronously + * Various bugfixes diff --git a/kike.c b/kike.c index b672339..79e21e5 100644 --- a/kike.c +++ b/kike.c @@ -1,7 +1,7 @@ /* * kike.c: the experimental IRC daemon * - * Copyright (c) 2014 - 2015, Přemysl Janouch + * Copyright (c) 2014 - 2016, Přemysl Janouch * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -330,17 +330,17 @@ struct client struct poller_timer timeout_timer; ///< Connection seems to be dead struct poller_timer kill_timer; ///< Hard kill timeout - bool initialized; ///< Has any data been received yet? - bool cap_negotiating; ///< Negotiating capabilities - bool registered; ///< The user has registered - bool closing_link; ///< Closing link - bool half_closed; ///< Closing link: conn. is half-closed - unsigned long cap_version; ///< CAP protocol version unsigned caps_enabled; ///< Enabled capabilities - bool ssl_rx_want_tx; ///< SSL_read() wants to write - bool ssl_tx_want_rx; ///< SSL_write() wants to read + unsigned initialized : 1; ///< Has any data been received yet? + unsigned cap_negotiating : 1; ///< Negotiating capabilities + unsigned registered : 1; ///< The user has registered + unsigned closing_link : 1; ///< Closing link + unsigned half_closed : 1; ///< Closing link: conn. is half-closed + + unsigned ssl_rx_want_tx : 1; ///< SSL_read() wants to write + unsigned ssl_tx_want_rx : 1; ///< SSL_write() wants to read SSL *ssl; ///< SSL connection char *ssl_cert_fingerprint; ///< Client certificate fingerprint @@ -349,20 +349,23 @@ struct client char *realname; ///< IRC realname (e-mail) char *hostname; ///< Hostname shown to the network - char *address; ///< Full address including port + char *port; ///< Port of the peer as a string + char *address; ///< Full address unsigned mode; ///< User's mode char *away_message; ///< Away message time_t last_active; ///< Last PRIVMSG, to get idle time struct str_map invites; ///< Channel invitations by operators struct flood_detector antiflood; ///< Flood detector + + struct async_getnameinfo *gni; ///< Backwards DNS resolution + struct poller_timer gni_timer; ///< Backwards DNS resolution timeout }; -static void -client_init (struct client *self) +static struct client * +client_new (void) { - memset (self, 0, sizeof *self); - + struct client *self = xcalloc (1, sizeof *self); self->socket_fd = -1; str_init (&self->read_buffer); str_init (&self->write_buffer); @@ -371,10 +374,11 @@ client_init (struct client *self) flood_detector_init (&self->antiflood, 10, 20); str_map_init (&self->invites); self->invites.key_xfrm = irc_strxfrm; + return self; } static void -client_free (struct client *self) +client_destroy (struct client *self) { if (!soft_assert (self->socket_fd == -1)) xclose (self->socket_fd); @@ -389,10 +393,16 @@ client_free (struct client *self) free (self->realname); free (self->hostname); + free (self->port); free (self->address); + free (self->away_message); flood_detector_free (&self->antiflood); str_map_free (&self->invites); + + if (self->gni) + async_cancel (&self->gni->async); + free (self); } static void client_close_link (struct client *c, const char *reason); @@ -891,54 +901,68 @@ client_unregister (struct client *c, const char *reason) c->registered = false; } -static void -client_close_link (struct client *c, const char *reason) -{ - if (!soft_assert (!c->closing_link)) - return; - - // We push an `ERROR' message to the write buffer and let the poller send - // it, with some arbitrary timeout. The `closing_link' state makes sure - // that a/ we ignore any successive messages, and b/ that the connection - // is killed after the write buffer is transferred and emptied. - client_send (c, "ERROR :Closing Link: %s[%s] (%s)", - c->nickname ? c->nickname : "*", - c->hostname /* TODO host IP? */, reason); - c->closing_link = true; - - client_unregister (c, reason); - client_set_kill_timer (c); -} - static void client_kill (struct client *c, const char *reason) { + struct server_context *ctx = c->ctx; client_unregister (c, reason ? reason : "Client exited"); - struct server_context *ctx = c->ctx; + if (c->address) + // Only log the event if address resolution has finished + print_debug ("closed connection to %s (%s)", c->address, + reason ? reason : ""); if (c->ssl) // Note that we might have already called this once, but that is fine (void) SSL_shutdown (c->ssl); xclose (c->socket_fd); + c->socket_fd = -1; c->socket_event.closed = true; poller_fd_reset (&c->socket_event); client_cancel_timers (c); - print_debug ("closed connection to %s (%s)", - c->address, reason ? reason : ""); - - c->socket_fd = -1; - client_free (c); LIST_UNLINK (ctx->clients, c); ctx->n_clients--; - free (c); + client_destroy (c); irc_try_finish_quit (ctx); } +static void +client_close_link (struct client *c, const char *reason) +{ + // Cannot push data to a client whose protocol we don't even know, + // at least not with current code (client_send_str(), on_client_ready()), + // which could possibly be solved by client_update_poller() not setting + // POLLOUT when the protocol hasn't been initialized yet. + // + // We also want to avoid accidentally setting poller events before + // address resolution has finished. + // + // Let's just cut the connection, the client can try again later. + if (!c->initialized) + { + client_kill (c, reason); + return; + } + if (!soft_assert (!c->closing_link)) + return; + + // We push an `ERROR' message to the write buffer and let the poller send + // it, with some arbitrary timeout. The `closing_link' state makes sure + // that a/ we ignore any successive messages, and b/ that the connection + // is killed after the write buffer is transferred and emptied. + client_send (c, "ERROR :Closing Link: %s[%s] (%s)", + c->nickname ? c->nickname : "*", + c->hostname /* TODO host IP? */, reason); + c->closing_link = true; + + client_unregister (c, reason); + client_set_kill_timer (c); +} + static bool client_in_mask_list (const struct client *c, const struct str_vector *mask) { @@ -991,6 +1015,7 @@ client_cancel_timers (struct client *c) poller_timer_reset (&c->kill_timer); poller_timer_reset (&c->timeout_timer); poller_timer_reset (&c->ping_timer); + poller_timer_reset (&c->gni_timer); } static void @@ -3211,6 +3236,8 @@ irc_try_write_tls (struct client *c) return true; } +// ----------------------------------------------------------------------------- + static bool irc_autodetect_tls (struct client *c) { @@ -3281,6 +3308,8 @@ error_ssl_1: return false; } +// ----------------------------------------------------------------------------- + static void on_client_ready (const struct pollfd *pfd, void *user_data) { @@ -3354,6 +3383,44 @@ client_update_poller (struct client *c, const struct pollfd *pfd) poller_fd_set (&c->socket_event, new_events); } +static void +client_finish_connection (struct client *c) +{ + c->gni = NULL; + + c->address = format_host_port_pair (c->hostname, c->port); + print_debug ("accepted connection from %s", c->address); + + client_update_poller (c, NULL); + client_set_kill_timer (c); +} + +static void +on_client_gni_resolved (int result, char *host, char *port, void *user_data) +{ + struct client *c = user_data; + + if (result) + print_debug ("%s: %s", "getnameinfo", gai_strerror (result)); + else + { + free (c->hostname); + c->hostname = xstrdup (host); + (void) port; + } + + poller_timer_reset (&c->gni_timer); + client_finish_connection (c); +} + +static void +on_client_gni_timer (void *user_data) +{ + struct client *c = user_data; + async_cancel (&c->gni->async); + client_finish_connection (c); +} + static bool irc_try_fetch_client (struct server_context *ctx, int listen_fd) { @@ -3378,6 +3445,15 @@ irc_try_fetch_client (struct server_context *ctx, int listen_fd) return false; } + set_blocking (fd, false); + + // A little bit questionable once the traffic gets high enough (IMO), + // but it reduces silly latencies that we don't need because we already + // do buffer our output + int yes = 1; + soft_assert (setsockopt (fd, IPPROTO_TCP, TCP_NODELAY, + &yes, sizeof yes) != -1); + if (ctx->max_connections != 0 && ctx->n_clients >= ctx->max_connections) { print_debug ("connection limit reached, refusing connection"); @@ -3385,23 +3461,18 @@ irc_try_fetch_client (struct server_context *ctx, int listen_fd) return true; } - // FIXME: use async_getnameinfo() so that we never ever block here char host[NI_MAXHOST] = "unknown", port[NI_MAXSERV] = "unknown"; int err = getnameinfo ((struct sockaddr *) &peer, peer_len, - host, sizeof host, port, sizeof port, NI_NUMERICSERV); + host, sizeof host, port, sizeof port, NI_NUMERICHOST | NI_NUMERICSERV); if (err) print_debug ("%s: %s", "getnameinfo", gai_strerror (err)); - char *address = format_host_port_pair (host, port); - print_debug ("accepted connection from %s", address); - - struct client *c = xmalloc (sizeof *c); - client_init (c); + struct client *c = client_new (); c->ctx = ctx; c->opened = time (NULL); c->socket_fd = fd; c->hostname = xstrdup (host); - c->address = address; + c->port = xstrdup (port); c->last_active = time (NULL); LIST_PREPEND (ctx->clients, c); ctx->n_clients++; @@ -3422,16 +3493,19 @@ irc_try_fetch_client (struct server_context *ctx, int listen_fd) c->ping_timer.dispatcher = on_client_ping_timer; c->ping_timer.user_data = c; - // A little bit questionable once the traffic gets high enough (IMO), - // but it reduces silly latencies that we don't need because we already - // do buffer our output - int yes = 1; - soft_assert (setsockopt (fd, IPPROTO_TCP, TCP_NODELAY, - &yes, sizeof yes) != -1); + // Resolve the client's hostname first; this is a blocking operation that + // depends on the network, so run it asynchronously with some timeout + // FIXME: we can run out of threads when there's a lot of connections + c->gni = async_getnameinfo (&ctx->poller.common.async, + (const struct sockaddr *) &peer, peer_len, NI_NUMERICSERV); + c->gni->dispatcher = on_client_gni_resolved; + c->gni->user_data = c; - set_blocking (fd, false); - client_update_poller (c, NULL); - client_set_kill_timer (c); + poller_timer_init (&c->gni_timer, &c->ctx->poller); + c->gni_timer.dispatcher = on_client_gni_timer; + c->gni_timer.user_data = c; + + poller_timer_set (&c->gni_timer, 5000); return true; } -- cgit v1.2.3-70-g09d2