kike: asynchronous address resolution

As well as some refactoring and cleanup.
This commit is contained in:
Přemysl Eric Janouch 2016-01-16 04:02:52 +01:00
parent fdeb550ee0
commit f36d66b0cb
2 changed files with 137 additions and 63 deletions

4
NEWS
View File

@ -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

196
kike.c
View File

@ -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);
@ -891,9 +901,52 @@ client_unregister (struct client *c, const char *reason)
c->registered = false;
}
static void
client_kill (struct client *c, const char *reason)
{
struct server_context *ctx = c->ctx;
client_unregister (c, reason ? reason : "Client exited");
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);
LIST_UNLINK (ctx->clients, c);
ctx->n_clients--;
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;
@ -910,35 +963,6 @@ client_close_link (struct client *c, const char *reason)
client_set_kill_timer (c);
}
static void
client_kill (struct client *c, const char *reason)
{
client_unregister (c, reason ? reason : "Client exited");
struct server_context *ctx = c->ctx;
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_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);
irc_try_finish_quit (ctx);
}
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;
}