kike: asynchronous address resolution
As well as some refactoring and cleanup.
This commit is contained in:
parent
fdeb550ee0
commit
f36d66b0cb
4
NEWS
4
NEWS
|
@ -6,14 +6,14 @@
|
||||||
|
|
||||||
* degesch: added autocomplete for /topic
|
* degesch: added autocomplete for /topic
|
||||||
|
|
||||||
* degesch: resolve remote addresses asynchronously
|
|
||||||
|
|
||||||
* degesch: Lua API was improved and extended
|
* degesch: Lua API was improved and extended
|
||||||
|
|
||||||
* degesch: added a basic last.fm "now playing" plugin
|
* degesch: added a basic last.fm "now playing" plugin
|
||||||
|
|
||||||
* degesch: backlog limit was made configurable
|
* degesch: backlog limit was made configurable
|
||||||
|
|
||||||
|
* Remote addresses are now resolved asynchronously
|
||||||
|
|
||||||
* Various bugfixes
|
* Various bugfixes
|
||||||
|
|
||||||
|
|
||||||
|
|
196
kike.c
196
kike.c
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* kike.c: the experimental IRC daemon
|
* 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
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
* 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 timeout_timer; ///< Connection seems to be dead
|
||||||
struct poller_timer kill_timer; ///< Hard kill timeout
|
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 long cap_version; ///< CAP protocol version
|
||||||
unsigned caps_enabled; ///< Enabled capabilities
|
unsigned caps_enabled; ///< Enabled capabilities
|
||||||
|
|
||||||
bool ssl_rx_want_tx; ///< SSL_read() wants to write
|
unsigned initialized : 1; ///< Has any data been received yet?
|
||||||
bool ssl_tx_want_rx; ///< SSL_write() wants to read
|
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
|
SSL *ssl; ///< SSL connection
|
||||||
char *ssl_cert_fingerprint; ///< Client certificate fingerprint
|
char *ssl_cert_fingerprint; ///< Client certificate fingerprint
|
||||||
|
|
||||||
|
@ -349,20 +349,23 @@ struct client
|
||||||
char *realname; ///< IRC realname (e-mail)
|
char *realname; ///< IRC realname (e-mail)
|
||||||
|
|
||||||
char *hostname; ///< Hostname shown to the network
|
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
|
unsigned mode; ///< User's mode
|
||||||
char *away_message; ///< Away message
|
char *away_message; ///< Away message
|
||||||
time_t last_active; ///< Last PRIVMSG, to get idle time
|
time_t last_active; ///< Last PRIVMSG, to get idle time
|
||||||
struct str_map invites; ///< Channel invitations by operators
|
struct str_map invites; ///< Channel invitations by operators
|
||||||
struct flood_detector antiflood; ///< Flood detector
|
struct flood_detector antiflood; ///< Flood detector
|
||||||
|
|
||||||
|
struct async_getnameinfo *gni; ///< Backwards DNS resolution
|
||||||
|
struct poller_timer gni_timer; ///< Backwards DNS resolution timeout
|
||||||
};
|
};
|
||||||
|
|
||||||
static void
|
static struct client *
|
||||||
client_init (struct client *self)
|
client_new (void)
|
||||||
{
|
{
|
||||||
memset (self, 0, sizeof *self);
|
struct client *self = xcalloc (1, sizeof *self);
|
||||||
|
|
||||||
self->socket_fd = -1;
|
self->socket_fd = -1;
|
||||||
str_init (&self->read_buffer);
|
str_init (&self->read_buffer);
|
||||||
str_init (&self->write_buffer);
|
str_init (&self->write_buffer);
|
||||||
|
@ -371,10 +374,11 @@ client_init (struct client *self)
|
||||||
flood_detector_init (&self->antiflood, 10, 20);
|
flood_detector_init (&self->antiflood, 10, 20);
|
||||||
str_map_init (&self->invites);
|
str_map_init (&self->invites);
|
||||||
self->invites.key_xfrm = irc_strxfrm;
|
self->invites.key_xfrm = irc_strxfrm;
|
||||||
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
client_free (struct client *self)
|
client_destroy (struct client *self)
|
||||||
{
|
{
|
||||||
if (!soft_assert (self->socket_fd == -1))
|
if (!soft_assert (self->socket_fd == -1))
|
||||||
xclose (self->socket_fd);
|
xclose (self->socket_fd);
|
||||||
|
@ -389,10 +393,16 @@ client_free (struct client *self)
|
||||||
free (self->realname);
|
free (self->realname);
|
||||||
|
|
||||||
free (self->hostname);
|
free (self->hostname);
|
||||||
|
free (self->port);
|
||||||
free (self->address);
|
free (self->address);
|
||||||
|
|
||||||
free (self->away_message);
|
free (self->away_message);
|
||||||
flood_detector_free (&self->antiflood);
|
flood_detector_free (&self->antiflood);
|
||||||
str_map_free (&self->invites);
|
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);
|
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;
|
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
|
static void
|
||||||
client_close_link (struct client *c, const char *reason)
|
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))
|
if (!soft_assert (!c->closing_link))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -910,35 +963,6 @@ client_close_link (struct client *c, const char *reason)
|
||||||
client_set_kill_timer (c);
|
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
|
static bool
|
||||||
client_in_mask_list (const struct client *c, const struct str_vector *mask)
|
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->kill_timer);
|
||||||
poller_timer_reset (&c->timeout_timer);
|
poller_timer_reset (&c->timeout_timer);
|
||||||
poller_timer_reset (&c->ping_timer);
|
poller_timer_reset (&c->ping_timer);
|
||||||
|
poller_timer_reset (&c->gni_timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -3211,6 +3236,8 @@ irc_try_write_tls (struct client *c)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
irc_autodetect_tls (struct client *c)
|
irc_autodetect_tls (struct client *c)
|
||||||
{
|
{
|
||||||
|
@ -3281,6 +3308,8 @@ error_ssl_1:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
static void
|
static void
|
||||||
on_client_ready (const struct pollfd *pfd, void *user_data)
|
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);
|
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
|
static bool
|
||||||
irc_try_fetch_client (struct server_context *ctx, int listen_fd)
|
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;
|
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)
|
if (ctx->max_connections != 0 && ctx->n_clients >= ctx->max_connections)
|
||||||
{
|
{
|
||||||
print_debug ("connection limit reached, refusing connection");
|
print_debug ("connection limit reached, refusing connection");
|
||||||
|
@ -3385,23 +3461,18 @@ irc_try_fetch_client (struct server_context *ctx, int listen_fd)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: use async_getnameinfo() so that we never ever block here
|
|
||||||
char host[NI_MAXHOST] = "unknown", port[NI_MAXSERV] = "unknown";
|
char host[NI_MAXHOST] = "unknown", port[NI_MAXSERV] = "unknown";
|
||||||
int err = getnameinfo ((struct sockaddr *) &peer, peer_len,
|
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)
|
if (err)
|
||||||
print_debug ("%s: %s", "getnameinfo", gai_strerror (err));
|
print_debug ("%s: %s", "getnameinfo", gai_strerror (err));
|
||||||
|
|
||||||
char *address = format_host_port_pair (host, port);
|
struct client *c = client_new ();
|
||||||
print_debug ("accepted connection from %s", address);
|
|
||||||
|
|
||||||
struct client *c = xmalloc (sizeof *c);
|
|
||||||
client_init (c);
|
|
||||||
c->ctx = ctx;
|
c->ctx = ctx;
|
||||||
c->opened = time (NULL);
|
c->opened = time (NULL);
|
||||||
c->socket_fd = fd;
|
c->socket_fd = fd;
|
||||||
c->hostname = xstrdup (host);
|
c->hostname = xstrdup (host);
|
||||||
c->address = address;
|
c->port = xstrdup (port);
|
||||||
c->last_active = time (NULL);
|
c->last_active = time (NULL);
|
||||||
LIST_PREPEND (ctx->clients, c);
|
LIST_PREPEND (ctx->clients, c);
|
||||||
ctx->n_clients++;
|
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.dispatcher = on_client_ping_timer;
|
||||||
c->ping_timer.user_data = c;
|
c->ping_timer.user_data = c;
|
||||||
|
|
||||||
// A little bit questionable once the traffic gets high enough (IMO),
|
// Resolve the client's hostname first; this is a blocking operation that
|
||||||
// but it reduces silly latencies that we don't need because we already
|
// depends on the network, so run it asynchronously with some timeout
|
||||||
// do buffer our output
|
// FIXME: we can run out of threads when there's a lot of connections
|
||||||
int yes = 1;
|
c->gni = async_getnameinfo (&ctx->poller.common.async,
|
||||||
soft_assert (setsockopt (fd, IPPROTO_TCP, TCP_NODELAY,
|
(const struct sockaddr *) &peer, peer_len, NI_NUMERICSERV);
|
||||||
&yes, sizeof yes) != -1);
|
c->gni->dispatcher = on_client_gni_resolved;
|
||||||
|
c->gni->user_data = c;
|
||||||
|
|
||||||
set_blocking (fd, false);
|
poller_timer_init (&c->gni_timer, &c->ctx->poller);
|
||||||
client_update_poller (c, NULL);
|
c->gni_timer.dispatcher = on_client_gni_timer;
|
||||||
client_set_kill_timer (c);
|
c->gni_timer.user_data = c;
|
||||||
|
|
||||||
|
poller_timer_set (&c->gni_timer, 5000);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue