summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorWill Thompson <will.thompson@collabora.co.uk>2012-10-30 15:52:09 +0000
committerWill Thompson <will.thompson@collabora.co.uk>2012-10-30 16:02:57 +0000
commiteb437c2f0bbc7061570889a825b131969982cc9a (patch)
tree55ac4034da4d112c06cf2376bf358a1c8e48cdf2 /src
parent5fc2d07d025fca9a19abd09caa8b7de2ab8911ed (diff)
Connection: disconnect if we get no PONG for our PING
Previously we'd just send out a PING into the æther every n seconds, and pay no attention to whether we ever got an answer. So it was perfectly possible for the connection to just sit there until the TCP timeout kicks in, which I think is a matter of hours by default. With this patch, if we haven't heard a PONG 3 keepalive-intervals after sending a PING, Idle throws its toys out of the pram. This has been tested as follows: * Put my laptop on a wired and wireless network simultaneously. * Connect to an IRC server. (The wired network is used.) * Pull the network cable out. Idle is too stupid to realise the link it was using is gone, and because we're still ostensibly online, nothing tells it to disconnect. * Wait keepalive-interval * 4, and watch the connection get disconnected. It works both with a direct connection to Freenode, and with a connection over an SSH tunnel to irssi-proxy.
Diffstat (limited to 'src')
-rw-r--r--src/idle-connection.c60
-rw-r--r--src/idle-parser.c1
-rw-r--r--src/idle-parser.h1
3 files changed, 59 insertions, 3 deletions
diff --git a/src/idle-connection.c b/src/idle-connection.c
index 3fd8905..f4cf816 100644
--- a/src/idle-connection.c
+++ b/src/idle-connection.c
@@ -52,6 +52,7 @@
#include "extensions/extensions.h" /* Renaming */
#define DEFAULT_KEEPALIVE_INTERVAL 30 /* sec */
+#define MISSED_KEEPALIVES_BEFORE_DISCONNECTING 3
/* From RFC 2813 :
* This in essence means that the client may send one (1) message every
@@ -146,6 +147,11 @@ struct _IdleConnectionPrivate {
IdleServerConnection *conn;
guint sconn_status;
+ /* When we sent a PING to the server which it hasn't PONGed for yet, or 0 if
+ * there isn't a PING outstanding.
+ */
+ gint64 ping_time;
+
/* IRC connection properties */
char *nickname;
char *server;
@@ -207,6 +213,7 @@ static IdleParserHandlerResult _erroneous_nickname_handler(IdleParser *parser, I
static IdleParserHandlerResult _nick_handler(IdleParser *parser, IdleParserMessageCode code, GValueArray *args, gpointer user_data);
static IdleParserHandlerResult _nickname_in_use_handler(IdleParser *parser, IdleParserMessageCode code, GValueArray *args, gpointer user_data);
static IdleParserHandlerResult _ping_handler(IdleParser *parser, IdleParserMessageCode code, GValueArray *args, gpointer user_data);
+static IdleParserHandlerResult _pong_handler(IdleParser *parser, IdleParserMessageCode code, GValueArray *args, gpointer user_data);
static IdleParserHandlerResult _version_privmsg_handler(IdleParser *parser, IdleParserMessageCode code, GValueArray *args, gpointer user_data);
static IdleParserHandlerResult _welcome_handler(IdleParser *parser, IdleParserMessageCode code, GValueArray *args, gpointer user_data);
static IdleParserHandlerResult _whois_user_handler(IdleParser *parser, IdleParserMessageCode code, GValueArray *args, gpointer user_data);
@@ -703,6 +710,7 @@ static void _connection_connect_ready(GObject *source_object, GAsyncResult *res,
idle_parser_add_handler(conn->parser, IDLE_PARSER_NUMERIC_WHOISUSER, _whois_user_handler, conn);
idle_parser_add_handler(conn->parser, IDLE_PARSER_CMD_PING, _ping_handler, conn);
+ idle_parser_add_handler(conn->parser, IDLE_PARSER_PREFIXCMD_PONG, _pong_handler, conn);
idle_parser_add_handler_with_priority(conn->parser, IDLE_PARSER_PREFIXCMD_NICK, _nick_handler, conn, IDLE_PARSER_HANDLER_PRIORITY_FIRST);
idle_parser_add_handler(conn->parser, IDLE_PARSER_PREFIXCMD_PRIVMSG_USER, _version_privmsg_handler, conn);
@@ -808,15 +816,49 @@ static gboolean keepalive_timeout_cb(gpointer user_data) {
IdleConnection *conn = IDLE_CONNECTION(user_data);
IdleConnectionPrivate *priv = conn->priv;
gchar cmd[IRC_MSG_MAXLEN + 1];
- gint64 ping_time;
+ gint64 now;
if (priv->sconn_status != SERVER_CONNECTION_STATE_CONNECTED) {
priv->keepalive_timeout = 0;
return FALSE;
}
- ping_time = g_get_real_time();
- g_snprintf(cmd, IRC_MSG_MAXLEN + 1, "PING %" G_GINT64_FORMAT, ping_time);
+ now = g_get_real_time();
+
+ if (priv->ping_time != 0) {
+ gint64 seconds_since_ping = (now - priv->ping_time) / G_USEC_PER_SEC;
+
+ if (seconds_since_ping > priv->keepalive_interval * MISSED_KEEPALIVES_BEFORE_DISCONNECTING) {
+ GCancellable *kill_me_now = g_cancellable_new();
+
+ IDLE_DEBUG("haven't heard from the server in %" G_GINT64_FORMAT " seconds "
+ "(more than %u keepalive intervals)",
+ seconds_since_ping, MISSED_KEEPALIVES_BEFORE_DISCONNECTING);
+
+ /* Passing a cancelled cancellable to g_io_stream_close_async() stops us
+ * waiting for a TCP-level reply from the server which we already know
+ * has gone away. Quoth the docs for g_io_stream_close():
+ *
+ * Cancelling a close will still leave the stream closed, but some
+ * streams can use a faster close that doesn't block to e.g. check
+ * errors.
+ */
+ g_cancellable_cancel(kill_me_now);
+ idle_server_connection_disconnect_full_async(priv->conn, SERVER_CONNECTION_STATE_REASON_ERROR, kill_me_now, NULL, NULL);
+ g_object_unref(kill_me_now);
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ if (priv->msg_queue->length > 0) {
+ /* No point in sending a PING if we're sending data anyway. */
+ return TRUE;
+ }
+
+ priv->ping_time = now;
+ g_snprintf(cmd, IRC_MSG_MAXLEN + 1, "PING %" G_GINT64_FORMAT, priv->ping_time);
_send_with_priority(conn, cmd, SERVER_CMD_MIN_PRIORITY);
return TRUE;
@@ -1060,6 +1102,18 @@ static IdleParserHandlerResult _ping_handler(IdleParser *parser, IdleParserMessa
return IDLE_PARSER_HANDLER_RESULT_HANDLED;
}
+static IdleParserHandlerResult _pong_handler(IdleParser *parser, IdleParserMessageCode code, GValueArray *args, gpointer user_data) {
+ IdleConnection *conn = IDLE_CONNECTION(user_data);
+ IdleConnectionPrivate *priv = conn->priv;
+
+ /* We could compare ping_time to g_get_real_time() and give some indication
+ * of lag, if we were feeling enthusiastic.
+ */
+ priv->ping_time = 0;
+
+ return IDLE_PARSER_HANDLER_RESULT_HANDLED;
+}
+
static IdleParserHandlerResult _version_privmsg_handler(IdleParser *parser, IdleParserMessageCode code, GValueArray *args, gpointer user_data) {
IdleConnection *conn = IDLE_CONNECTION(user_data);
const gchar *msg = g_value_get_string(g_value_array_get_nth(args, 2));
diff --git a/src/idle-parser.c b/src/idle-parser.c
index 170f299..47ac0e5 100644
--- a/src/idle-parser.c
+++ b/src/idle-parser.c
@@ -85,6 +85,7 @@ static const MessageSpec message_specs[] = {
{"NOTICE", "cIr:", IDLE_PARSER_PREFIXCMD_NOTICE_CHANNEL},
{"NOTICE", "cIc:", IDLE_PARSER_PREFIXCMD_NOTICE_USER},
{"PART", "cIr.", IDLE_PARSER_PREFIXCMD_PART},
+ {"PONG", "IIs:", IDLE_PARSER_PREFIXCMD_PONG},
{"PRIVMSG", "cIr:", IDLE_PARSER_PREFIXCMD_PRIVMSG_CHANNEL},
{"PRIVMSG", "cIc:", IDLE_PARSER_PREFIXCMD_PRIVMSG_USER},
{"QUIT", "cI.", IDLE_PARSER_PREFIXCMD_QUIT},
diff --git a/src/idle-parser.h b/src/idle-parser.h
index a900b00..1216054 100644
--- a/src/idle-parser.h
+++ b/src/idle-parser.h
@@ -60,6 +60,7 @@ typedef enum {
IDLE_PARSER_PREFIXCMD_NOTICE_CHANNEL,
IDLE_PARSER_PREFIXCMD_NOTICE_USER,
IDLE_PARSER_PREFIXCMD_PART,
+ IDLE_PARSER_PREFIXCMD_PONG,
IDLE_PARSER_PREFIXCMD_PRIVMSG_CHANNEL,
IDLE_PARSER_PREFIXCMD_PRIVMSG_USER,
IDLE_PARSER_PREFIXCMD_QUIT,