Improve error messages when connecting, do some more sanity checking in state.
authorJelmer Vernooij <jelmer@samba.org>
Wed, 21 Nov 2007 23:40:48 +0000 (00:40 +0100)
committerJelmer Vernooij <jelmer@samba.org>
Wed, 21 Nov 2007 23:40:48 +0000 (00:40 +0100)
NEWS
lib/client.c
lib/network.c
lib/network.h
lib/state.c
src/admin.c
src/ctrlproxy.h
src/internals.h
src/util.c

diff --git a/NEWS b/NEWS
index 9d2c1cd7eb3a687c86c6ccf06fb4417c842e9172..ede5f099958136be34b18b3f639767919e30921f 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -16,6 +16,8 @@ Ctrlproxy 3.0.4 UNRELEASED
 
   BUG FIXES
 
+    * Clearer error messages during disconnect.
+
     * Handle ping timeouts from server. (#158)
 
     * Don't crash when network of listener goes away. (#168)
index faca248597a5bfc2d79f2dad99339065b2ac607c..1ff9f3448fac0ca2317c84e93cbfb3d636e640c6 100644 (file)
@@ -506,6 +506,14 @@ static gboolean handle_pending_client_receive(GIOChannel *c,
        g_assert(client);
        g_assert(c);
 
+       if (cond & G_IO_ERR) {
+               char *tmp = g_strdup_printf("Error reading from client: %s", 
+                                                 g_io_channel_unix_get_sock_error(c));
+               disconnect_client(client, tmp);
+               g_free(tmp);
+               return FALSE;
+       }
+
        if (cond & G_IO_HUP) {
                disconnect_client(client, "Hangup from client");
                return FALSE;
@@ -595,7 +603,7 @@ static gboolean handle_pending_client_receive(GIOChannel *c,
                                }
 
                                client->incoming_id = g_io_add_watch(client->incoming, 
-                                                        G_IO_IN | G_IO_HUP, handle_client_receive, client);
+                                                        G_IO_IN | G_IO_HUP | G_IO_ERR, handle_client_receive, client);
 
                                pending_clients = g_list_remove(pending_clients, client);
                                client->network->clients = g_list_append(client->network->clients, client);
index 79893f6f78e7c37a95deae62174288eb8396cb89..95c7b49b971780a3a5233e6942b4e07ccb1ee111 100644 (file)
@@ -28,6 +28,8 @@ static gboolean connect_server(struct network *s);
 static gboolean close_server(struct network *s);
 static void reconnect(struct network *server);
 static void clients_send_state(GList *clients, struct network_state *s);
+static gboolean server_finish_connect(GIOChannel *ioc, GIOCondition cond, 
+                                                                 void *data);
 
 struct new_network_notify_data {
        new_network_notify_fn fn;
@@ -308,7 +310,8 @@ static gboolean handle_server_receive (GIOChannel *c, GIOCondition cond, void *_
        }
 
        if (cond & G_IO_ERR) {
-               network_report_disconnect(server, "Error from server, scheduling reconnect");
+               network_report_disconnect(server, 
+                                                                 "Error from server: %s, scheduling reconnect", g_io_channel_unix_get_sock_error(c));
                reconnect(server);
                return FALSE;
        }
@@ -684,7 +687,6 @@ static gboolean connect_current_tcp_server(struct network *s)
 {
        struct addrinfo *res;
        int sock = -1;
-       socklen_t size;
        struct tcp_server_config *cs;
        GIOChannel *ioc = NULL;
        struct addrinfo hints;
@@ -758,44 +760,19 @@ static gboolean connect_current_tcp_server(struct network *s)
                return FALSE;
        }
 
-       size = sizeof(struct sockaddr_storage);
-       g_assert(s->connection.data.tcp.local_name == NULL);
-       g_assert(s->connection.data.tcp.remote_name == NULL);
-       s->connection.data.tcp.remote_name = g_malloc(size);
-       s->connection.data.tcp.local_name = g_malloc(size);
-       s->connection.data.tcp.namelen = getsockname(sock, s->connection.data.tcp.local_name, &size);
-       getpeername(sock, s->connection.data.tcp.remote_name, &size);
-
-       if (cs->ssl) {
-#ifdef HAVE_GNUTLS
-               g_io_channel_set_close_on_unref(ioc, TRUE);
-               g_io_channel_set_flags(ioc, G_IO_FLAG_NONBLOCK, NULL);
+       g_io_channel_set_close_on_unref(ioc, TRUE);
 
-               ioc = ssl_wrap_iochannel (ioc, SSL_TYPE_CLIENT, 
-                                                                s->connection.data.tcp.current_server->host,
-                                                                s->ssl_credentials
-                                                                );
-               g_assert(ioc != NULL);
-#else
-               log_network(LOG_WARNING, s, "SSL enabled for %s:%s, but no SSL support loaded", cs->host, cs->port);
-#endif
-       }
+       s->connection.state = NETWORK_CONNECTION_STATE_CONNECTING;
 
-       if (!ioc) {
-               log_network(LOG_ERROR, s, "Couldn't connect via server %s:%s", cs->host, cs->port);
-               return FALSE;
+       if (!connect_finished) {
+               s->connection.outgoing_id = g_io_add_watch(ioc, 
+                                                                                                  G_IO_OUT|G_IO_ERR|G_IO_HUP, 
+                                                                                                  server_finish_connect, s);
+       } else {
+               server_finish_connect(ioc, G_IO_OUT, s);
        }
 
-       if (!connect_finished)
-               s->connection.outgoing_id = g_io_add_watch(ioc, G_IO_OUT, server_send_queue, s);
-
-       network_set_iochannel(s, ioc);
-
-       g_io_channel_unref(s->connection.outgoing);
-
-       s->connection.data.tcp.ping_id = g_timeout_add(5000, 
-                                                                  (GSourceFunc) ping_server, s);
-
+       g_io_channel_unref(ioc);
 
        return TRUE;
 }
@@ -822,6 +799,8 @@ static void reconnect(struct network *server)
                server->config->type == NETWORK_IOCHANNEL ||
                server->config->type == NETWORK_PROGRAM) {
                server->connection.state = NETWORK_CONNECTION_STATE_RECONNECT_PENDING;
+               log_network(LOG_INFO, server, "Reconnecting in %d seconds", 
+                                       server->config->reconnect_interval);
                server->reconnect_id = g_timeout_add(1000 * 
                                                                server->config->reconnect_interval, 
                                                                (GSourceFunc) delayed_connect_server, server);
@@ -897,16 +876,18 @@ static gboolean close_server(struct network *n)
        case NETWORK_TCP: 
        case NETWORK_PROGRAM: 
        case NETWORK_IOCHANNEL:
-               g_assert(n->connection.incoming_id > 0);
-               g_source_remove(n->connection.incoming_id); 
-               n->connection.incoming_id = 0;
-               if (n->connection.outgoing_id > 0)
+               if (n->connection.incoming_id > 0) {
+                       g_source_remove(n->connection.incoming_id); 
+                       n->connection.incoming_id = 0;
+               }
+               if (n->connection.outgoing_id > 0) {
                        g_source_remove(n->connection.outgoing_id); 
+                       n->connection.outgoing_id = 0;
+               }
                if (n->connection.data.tcp.ping_id > 0) {
                        g_source_remove(n->connection.data.tcp.ping_id);
                        n->connection.data.tcp.ping_id = 0;
                }
-               n->connection.outgoing_id = 0;
                g_free(n->connection.data.tcp.local_name);
                g_free(n->connection.data.tcp.remote_name);
                n->connection.data.tcp.local_name = NULL;
@@ -1001,20 +982,95 @@ static pid_t piped_child(char* const command[], int *f_in)
        return pid;
 }
 
+static gboolean server_finish_connect(GIOChannel *ioc, GIOCondition cond, 
+                                                                 void *data)
+{
+       struct network *s = data;
+       socklen_t size;
+       int sock = g_io_channel_unix_get_fd(ioc);
+       struct tcp_server_config *cs;
+
+       if (cond & G_IO_ERR) {
+               network_report_disconnect(s, "Error connecting: %s", 
+                                                                 g_io_channel_unix_get_sock_error(ioc));
+               reconnect(s);
+               return FALSE;
+       }
+
+       if (cond & G_IO_OUT) {
+               s->connection.state = NETWORK_CONNECTION_STATE_CONNECTED;
+
+               size = sizeof(struct sockaddr_storage);
+               g_assert(s->connection.data.tcp.local_name == NULL);
+               g_assert(s->connection.data.tcp.remote_name == NULL);
+               s->connection.data.tcp.remote_name = g_malloc(size);
+               s->connection.data.tcp.local_name = g_malloc(size);
+               s->connection.data.tcp.namelen = getsockname(sock, s->connection.data.tcp.local_name, &size);
+               getpeername(sock, s->connection.data.tcp.remote_name, &size);
+
+               cs = s->connection.data.tcp.current_server;
+               if (cs->ssl) {
+#ifdef HAVE_GNUTLS
+                       g_io_channel_set_close_on_unref(ioc, TRUE);
+                       g_io_channel_set_flags(ioc, G_IO_FLAG_NONBLOCK, NULL);
+
+                       ioc = ssl_wrap_iochannel (ioc, SSL_TYPE_CLIENT, 
+                                                                        s->connection.data.tcp.current_server->host,
+                                                                        s->ssl_credentials
+                                                                        );
+                       g_assert(ioc != NULL);
+#else
+                       log_network(LOG_WARNING, s, "SSL enabled for %s:%s, but no SSL support loaded", cs->host, cs->port);
+#endif
+               }
+
+               if (!ioc) {
+                       network_report_disconnect(s, "Couldn't connect via server %s:%s", cs->host, cs->port);
+                       reconnect(s);
+                       return FALSE;
+               }
+
+               s->connection.outgoing_id = 0; /* Otherwise data will be queued */
+               network_set_iochannel(s, ioc);
+
+               s->connection.last_line_recvd = time(NULL);
+               s->connection.data.tcp.ping_id = g_timeout_add(5000, 
+                                                                          (GSourceFunc) ping_server, s);
+
+               return FALSE;
+       }
+
+       if (cond & G_IO_HUP) {
+               network_report_disconnect(s, "Server closed connection");
+               reconnect(s);
+               return FALSE;
+       }
+
+       return FALSE;
+}
+
 /**
  * Change the IO channel used to communicate with a network.
  * @param s Network to set the IO channel for.
  * @param ioc IO channel to use
  */
-void network_set_iochannel(struct network *s, GIOChannel *ioc)
+gboolean network_set_iochannel(struct network *s, GIOChannel *ioc)
 {
+       GError *error = NULL;
        g_assert(s->config->type != NETWORK_VIRTUAL);
-       g_io_channel_set_encoding(ioc, NULL, NULL);
+       if (g_io_channel_set_encoding(ioc, NULL, &error) != G_IO_STATUS_NORMAL) {
+               log_network(LOG_ERROR, s, "Unable to change encoding: %s", 
+                                       error?error->message:"unknown");
+               return FALSE;
+       }
        g_io_channel_set_close_on_unref(ioc, TRUE);
-       g_io_channel_set_flags(ioc, G_IO_FLAG_NONBLOCK, NULL);
+       if (g_io_channel_set_flags(ioc, G_IO_FLAG_NONBLOCK, &error) != G_IO_STATUS_NORMAL) {
+               log_network(LOG_ERROR, s, "Unable to change flags: %s", 
+                                       error?error->message:"unknown");
+               return FALSE;
+       }
 
        s->connection.outgoing = ioc;
-       g_io_channel_set_close_on_unref(s->connection.outgoing, TRUE);
 
        s->connection.incoming_id = g_io_add_watch(s->connection.outgoing, 
                                                                G_IO_IN | G_IO_HUP | G_IO_ERR, 
@@ -1024,6 +1080,8 @@ void network_set_iochannel(struct network *s, GIOChannel *ioc)
                                  s);
 
        server_send_login(s);
+
+       return TRUE;
 }
 
 static gboolean connect_program(struct network *s)
@@ -1046,7 +1104,6 @@ static gboolean connect_program(struct network *s)
 
        g_io_channel_unref(s->connection.outgoing);
 
-
        if (s->info.name == NULL) {
                if (strchr(s->config->type_settings.program_location, '/')) {
                        s->info.name = g_strdup(strrchr(s->config->type_settings.program_location, '/')+1);
index bf32f6b462dbbdb2cefe8e6a0a09992eb3b16b6c..8fc13519a0f27c91ea0e854620401f3586491e5f 100644 (file)
@@ -44,6 +44,8 @@ struct linestack_context;
 enum network_connection_state { 
                NETWORK_CONNECTION_STATE_NOT_CONNECTED = 0, 
                NETWORK_CONNECTION_STATE_RECONNECT_PENDING,
+               NETWORK_CONNECTION_STATE_CONNECTING,
+               NETWORK_CONNECTION_STATE_CONNECTED,
                NETWORK_CONNECTION_STATE_LOGIN_SENT, 
                NETWORK_CONNECTION_STATE_MOTD_RECVD,
 };
index f021365d699cd7464f2ce8e5198e76c67eb547e3..b85b8c47c533fdc02e8a5e427334af171fccac9d 100644 (file)
@@ -25,6 +25,13 @@ static void free_channel(struct channel_state *c);
 
 enum mode_type { REMOVE = 0, ADD = 1 };
 
+#define CHECK_ORIGIN(s,l,name) \
+       if ((l)->origin == NULL) { \
+               log_network_state(LOG_WARNING, (s), \
+                                                 "Received "name" line without origin"); \
+               return; \
+       }
+
 void network_nick_set_data(struct network_nick *n, const char *nick, 
                                                   const char *username, const char *host)
 {
@@ -372,10 +379,7 @@ static void handle_join(struct network_state *s, const struct line *l)
        char **channels;
        char *nick;
 
-       if ((nick = line_get_nick(l)) == NULL) {
-               log_network_state(LOG_WARNING, s, "No hostmask for JOIN line received from server");
-               return;
-       }
+       CHECK_ORIGIN(s,l,"NICK");
        
        channels = g_strsplit(l->args[1], ",", 0);
 
@@ -405,7 +409,11 @@ static void handle_part(struct network_state *s, const struct line *l)
        struct channel_nick *n;
        char **channels;
        int i;
-       char *nick = line_get_nick(l);
+       char *nick;
+
+       CHECK_ORIGIN(s,l,"PART");
+       
+       nick = line_get_nick(l);
 
        if (nick == NULL) 
                return;
@@ -448,7 +456,11 @@ static void handle_kick(struct network_state *s, const struct line *l)
        struct channel_nick *n;
        char **channels, **nicks;
        int i;
-       char *nick = line_get_nick(l);
+       char *nick;
+
+       CHECK_ORIGIN(s,l,"KICK");
+       
+       nick = line_get_nick(l);
 
        channels = g_strsplit(l->args[1], ",", 0);
        nicks = g_strsplit(l->args[2], ",", 0);
@@ -490,6 +502,9 @@ static void handle_kick(struct network_state *s, const struct line *l)
 static void handle_topic(struct network_state *s, const struct line *l) 
 {
        struct channel_state *c = find_channel(s, l->args[1]);
+       
+       CHECK_ORIGIN(s, l, "TOPIC");
+
        if (c->topic != NULL)
                g_free(c->topic);
        if (c->topic_set_by != NULL)
@@ -740,8 +755,13 @@ static void handle_unaway(struct network_state *s, const struct line *l)
 
 static void handle_quit(struct network_state *s, const struct line *l) 
 {
-       char *nick = line_get_nick(l);
-       struct network_nick *nn = find_network_nick(s, nick);
+       char *nick;
+       struct network_nick *nn;
+
+       CHECK_ORIGIN(s, l, "QUIT");
+
+       nick = line_get_nick(l);
+       nn = find_network_nick(s, nick);
        g_free(nick);
 
        g_assert(nn != &s->me);
@@ -861,6 +881,9 @@ static void handle_privmsg(struct network_state *s, const struct line *l)
 {
        struct network_nick *nn;
        char *nick;
+
+       CHECK_ORIGIN(s,l,"PRIVMSG");
+
        if (irccmp(&s->info, l->args[1], s->me.nick) != 0) return;
 
        nick = line_get_nick(l);
@@ -872,7 +895,11 @@ static void handle_privmsg(struct network_state *s, const struct line *l)
 static void handle_nick(struct network_state *s, const struct line *l)
 {
        struct network_nick *nn;
-       char *nick = line_get_nick(l);
+       char *nick;
+
+       CHECK_ORIGIN(s,l,"NICK");
+       
+       nick = line_get_nick(l);
        nn = find_add_network_nick(s, nick);
        g_free(nick);
        network_nick_set_nick(nn, l->args[1]);
index 211c18b5ad4162f6e8454317e9e43f10a15fc77d..393ef59ebbca9eb9b490b66edf6ea20a4cf7809a 100644 (file)
@@ -250,6 +250,8 @@ static void cmd_connect_network (admin_handle h, char **args, void *userdata)
                        network_select_next_server(s);
                        connect_network(s);
                        break;
+               case NETWORK_CONNECTION_STATE_CONNECTED:
+               case NETWORK_CONNECTION_STATE_CONNECTING:
                case NETWORK_CONNECTION_STATE_LOGIN_SENT:
                        admin_out(h, "Connect to `%s' already in progress", args[1]);
                        break;
@@ -355,6 +357,12 @@ static void list_networks_helper(admin_handle h)
                struct network *n = gl->data;
 
                switch (n->connection.state) {
+               case NETWORK_CONNECTION_STATE_CONNECTING:
+                       admin_out(h, "%s: Connect in progress", n->info.name);
+                       break;
+               case NETWORK_CONNECTION_STATE_CONNECTED:
+                       admin_out(h, "%s: Connected, logging in", n->info.name);
+                       break;
                case NETWORK_CONNECTION_STATE_NOT_CONNECTED:
                        if (n->connection.data.tcp.last_disconnect_reason)
                                admin_out(h, "%s: Not connected: %s", n->info.name, 
index ed501ff09558cb1f655ec3b6ec57f73ed1bcc08a..17074606440111010ec6f55d8d0afd73f9e3b1c8 100644 (file)
@@ -103,6 +103,8 @@ G_MODULE_EXPORT int str_rfc1459cmp(const char *a, const char *b);
 G_MODULE_EXPORT int str_strictrfc1459cmp(const char *a, const char *b);
 G_MODULE_EXPORT int str_asciicmp(const char *a, const char *b);
 G_MODULE_EXPORT char *g_io_channel_ip_get_description(GIOChannel *ch);
+G_MODULE_EXPORT const char *g_io_channel_unix_get_sock_error(GIOChannel *ioc);
+
 
 /* log.c */
 G_GNUC_PRINTF(3, 4) G_MODULE_EXPORT void log_network_state(enum log_level l, const struct network_state *st, const char *fmt, ...);
index a82c644621afec00bf2fbc503eb7f8b7c9fd3454..531e5600bc74604d3518ab6446decc40423164d4 100644 (file)
@@ -51,7 +51,7 @@
 /* server.c */
 void fini_networks(struct global *);
 void kill_pending_clients(const char *reason);
-void network_set_iochannel(struct network *s, GIOChannel *ioc);
+gboolean network_set_iochannel(struct network *s, GIOChannel *ioc);
 
 /* state.c */
 void free_channels(struct network *s);
@@ -136,3 +136,4 @@ void log_custom_load(struct log_file_config *config);
 void free_listeners(struct global *global);
 
 #endif /* __INTERNALS_H__ */
+
index 21300baaeb21691283d9ab0a6751508f1eaa758e..f2431ec8d39bb1ef3d50eada2946437e27bfcae6 100644 (file)
@@ -266,3 +266,14 @@ rep_g_mkdir_with_parents (const gchar *pathname,
 
   return 0;
 }
+
+const char *g_io_channel_unix_get_sock_error(GIOChannel *ioc)
+{
+       int valopt;
+       socklen_t valoptlen = sizeof(valopt);
+       int fd = g_io_channel_unix_get_fd(ioc);
+
+       getsockopt(fd, SOL_SOCKET, SO_ERROR, &valopt, &valoptlen);
+
+       return strerror(valopt);
+}