aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS4
-rw-r--r--kike.c190
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 <p.janouch@gmail.com>
+ * Copyright (c) 2014 - 2016, Přemysl Janouch <p.janouch@gmail.com>
*
* 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);
@@ -892,53 +902,67 @@ client_unregister (struct client *c, const char *reason)
}
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;
}