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: 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
196
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);
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user