irssi-import / bugs.irssi.org

bugs.irssi.org archive
https://github.com/irssi/irssi/issues
0 stars 0 forks source link

[PATCH] support SRV records #908

Open irssibot opened 10 years ago

irssibot commented 10 years ago

http://tools.ietf.org/html/rfc2782 specifies SRV records, which (in the case of IRC) look like _irc._tcp.domain, e.g. _irc._tcp.cluenet.org. An SRV record specifies a priority, a weight, a hostname and a port (!), e.g.:

host -t SRV _irc._tcp.cluenet.org                                   

_irc._tcp.cluenet.org has SRV record 0 0 6667 radian.cluenet.org. _irc._tcp.cluenet.org has SRV record 0 0 6667 virgule.cluenet.org.

This is really handy if you want to run IRC on non-standard ports or just don’t want to bother with configuring SSL ports, which often differ from IRC network to IRC network. Furthermore, it clearly defines an order (by priorities), whereas just pointing one DNS record to multiple IP addresses is round-robin.

The attached patch is a work-in-progress which makes irssi use glib’s asynchronous DNS resolver to first resolve an SRV record, and fall back to normal DNS resolution if that is not possible.

I will continue working on this patch, but I’d like some things clarified first please:

1) Will you merge that patch at all, i.e. are you okay with irssi SRV-resolving? 2) Can we ignore the resolve_prefer_ipv6 setting? Then we could use GSocketConnectable and simplify the code even further. 3) Are there any big style guide violations, architectural problems or other problems with my current patch?

Thanks!

irssibot commented 10 years ago

irssi-srv.patch

Index: configure.in
===================================================================
--- configure.in    (revision 5227)
+++ configure.in    (working copy)
@@ -272,14 +272,14 @@

 for try in 1 2; do
   if test $try = 1; then
-    glib_modules=gmodule
+    glib_modules="gmodule gio"
   else
     echo "*** trying without -lgmodule"
-    glib_modules=
+    glib_modules="gio"
   fi
   AM_PATH_GLIB_2_0(2.6.0,,, $glib_modules)
   if test "$GLIB_LIBS"; then
-    if test $glib_modules = gmodule; then
+    if test $try = 1; then
       AC_DEFINE(HAVE_GMODULE)
       have_gmodule=yes
     fi
Index: glib-2.0.m4
===================================================================
--- glib-2.0.m4 (revision 5227)
+++ glib-2.0.m4 (working copy)
@@ -28,6 +28,9 @@
          gthread) 
              pkg_config_args="$pkg_config_args gthread-2.0"
          ;;
+         gio) 
+             pkg_config_args="$pkg_config_args gio-2.0"
+         ;;
       esac
   done

Index: src/core/Makefile.am
===================================================================
--- src/core/Makefile.am    (revision 5227)
+++ src/core/Makefile.am    (working copy)
@@ -29,6 +29,7 @@
    net-disconnect.c \
    net-nonblock.c \
    net-sendbuffer.c \
+   net-resolver.c \
    network.c \
    network-openssl.c \
    nicklist.c \
@@ -78,6 +79,7 @@
    net-disconnect.h \
    net-nonblock.h \
    net-sendbuffer.h \
+   net-resolver.h \
    network.h \
    nick-rec.h \
    nicklist.h \
Index: src/core/net-resolver.c
===================================================================
--- src/core/net-resolver.c (revision 0)
+++ src/core/net-resolver.c (working copy)
@@ -0,0 +1,160 @@
+#include <glib.h>
+#include <gio/gio.h>
+
+#include "common.h"
+
+#include "servers.h"
+#include "net-resolver.h"
+#include "settings.h"
+
+typedef struct connection_attempt {
+   SERVER_REC *server;
+   gchar *hostname;
+   int port;
+   resolved_callback_t resolved_callback;
+   GCancellable *cancellable;
+} connection_attempt_t;
+
+static void return_inetaddress(connection_attempt_t *attempt, GInetAddress *address) {
+   IPADDR ip;
+   ip.family = g_inet_address_get_family(address);
+   memcpy(&(ip.ip), g_inet_address_to_bytes(address), g_inet_address_get_native_size(address));
+
+   attempt->resolved_callback(attempt->server, &ip, 0, NULL);
+}
+
+/*
+ * Hostname lookup (triggered from srv_record_callback) finished.
+ * If the lookup was successful, chose the first resolved IPv4 or IPv6 address,
+ * depending on the user’s preferences.
+ * If the lookup was unsuccessful, trigger the callback with an error message.
+ *
+ */
+static void host_callback(GObject *obj, GAsyncResult *res, gpointer user_data) {
+   connection_attempt_t *attempt = (connection_attempt_t*)user_data;
+   SERVER_REC *server = attempt->server;
+   GError *err = NULL;
+   GList *records = g_resolver_lookup_by_name_finish((GResolver*)obj, res, &err);
+   if (err != NULL) {
+       attempt->resolved_callback(server, NULL, 0, err->message);
+       g_error_free(err);
+       return;
+   }
+
+   if (g_list_length(records) == 0) {
+       g_error("Got 0 records even though there was no resolving error");
+   }
+
+   int n;
+   /* if an address family was specified, use the first record of that family, if any. */
+   if (server->connrec->family > 0) {
+       for (n = 0; n < g_list_length(records); n++) {
+           GInetAddress *addr = g_list_nth_data(records, n);
+           if (g_inet_address_get_family(addr) == server->connrec->family) {
+               return_inetaddress(attempt, addr);
+               return;
+           }
+       }
+
+       gchar *errormessage = g_strdup_printf("You requested an %s address, but '%s' has none",
+               (server->connrec->family == AF_INET ? "IPv4" : "IPv6"),
+               attempt->hostname);
+       attempt->resolved_callback(server, NULL, 0, errormessage);
+       free(errormessage);
+       return;
+   }
+
+   /* second pass: if IPv6 is preferred, chose the first IPv6 record. */
+   if (settings_get_bool("resolve_prefer_ipv6")) {
+       for (n = 0; n < g_list_length(records); n++) {
+           GInetAddress *addr = g_list_nth_data(records, n);
+           if (g_inet_address_get_family(addr) == AF_INET6) {
+               return_inetaddress(attempt, addr);
+               return;
+           }
+       }
+   }
+
+   /* fallback: legacy IP, then IPv6 */
+   for (n = 0; n < g_list_length(records); n++) {
+       GInetAddress *addr = g_list_nth_data(records, n);
+       if (g_inet_address_get_family(addr) == AF_INET) {
+           return_inetaddress(attempt, addr);
+           return;
+       }
+   }
+
+   for (n = 0; n < g_list_length(records); n++) {
+       GInetAddress *addr = g_list_nth_data(records, n);
+       if (g_inet_address_get_family(addr) == AF_INET6) {
+           return_inetaddress(attempt, addr);
+           return;
+       }
+   }
+
+   /* We cannot arrive here. We checked whether g_list_length(records) > 0
+    * above, and the only possible two address families are AF_INET and
+    * AF_INET6. */
+   g_error("Resolving failed, assumptions violated.");
+}
+
+static void lookup_by_name(connection_attempt_t *attempt) {
+   g_resolver_lookup_by_name_async(
+           g_resolver_get_default(),
+           attempt->hostname,
+           attempt->cancellable,
+           host_callback,
+           attempt);
+}
+
+/*
+ * Called when the SRV lookup finished.
+ * If the lookup was successful, resolve the nth SRV record, where n is the
+ * connection attempt counter.
+ * If the lookup was unsuccessful, resolve the original hostname.
+ *
+ */
+static void srv_record_callback(GObject *obj, GAsyncResult *res, gpointer user_data) {
+   connection_attempt_t *attempt = (connection_attempt_t*)user_data;
+   SERVER_REC *server = attempt->server;
+   GError *err = NULL;
+   GResolver *resolver = (GResolver*)obj;
+   GList *records = g_resolver_lookup_service_finish(resolver, res, &err);
+   if (err != NULL) {
+       const char *connect_address = server->connrec->proxy != NULL ?
+           server->connrec->proxy : server->connrec->address;
+       attempt->hostname = g_strdup(connect_address);
+       attempt->port = server->connrec->port;
+       lookup_by_name(attempt);
+       return;
+   }
+
+   /* The resolver returns a sorted list (ordered by the priority contained in
+    * the SRV record), so we can simply chose one element at each connection
+    * attempt and will have worked our way through the list eventually. */
+   int n = server->connrec->attempt_count++ % g_list_length(records);
+   GSrvTarget *target = g_list_nth_data(records, n);
+   attempt->hostname = g_strdup(g_srv_target_get_hostname(target));
+   attempt->port = g_srv_target_get_port(target);
+   lookup_by_name(attempt);
+}
+
+void net_resolve_server(SERVER_REC *server, GCancellable *cancellable, resolved_callback_t *resolved) {
+   const char *connect_address = server->connrec->proxy != NULL ?
+       server->connrec->proxy : server->connrec->address;
+
+   connection_attempt_t *attempt = malloc(sizeof(connection_attempt_t));
+   attempt->server = server;
+   attempt->resolved_callback = resolved;
+   attempt->cancellable = cancellable;
+
+   /* Look up the SRV record first. */
+   g_resolver_lookup_service_async(
+           g_resolver_get_default(),
+           (server->connrec->use_ssl ? "ircs" : "irc"),
+           "tcp",
+           connect_address,
+           attempt->cancellable,
+           srv_record_callback,
+           attempt);
+}
Index: src/core/net-resolver.h
===================================================================
--- src/core/net-resolver.h (revision 0)
+++ src/core/net-resolver.h (working copy)
@@ -0,0 +1,10 @@
+#ifndef __NET_RESOLVER_H
+#define __NET_RESOLVER_H
+
+#include "network.h"
+
+typedef void (*resolved_callback_t)(SERVER_REC *server, IPADDR *ip, int port, char *errormessage);
+
+void net_resolve_server(SERVER_REC *server, GCancellable *cancellable, resolved_callback_t *resolved);
+
+#endif
Index: src/core/server-connect-rec.h
===================================================================
--- src/core/server-connect-rec.h   (revision 5227)
+++ src/core/server-connect-rec.h   (working copy)
@@ -14,6 +14,9 @@
 char *tag; /* try to keep this tag when connected to server */
 char *address;
 int port;
+/* srv_port is either the same as port when there is no SRV record,
+ * or it is the port contained in the SRV record when there is one. */
+int srv_port;
 char *chatnet;

 IPADDR *own_ip4, *own_ip6;
@@ -41,3 +44,5 @@
 unsigned int no_connect:1; /* don't connect() at all, it's done by plugin */
 char *channels;
 char *away_reason;
+
+int attempt_count; /* Necessary to use all SRV records. */
Index: src/core/server-rec.h
===================================================================
--- src/core/server-rec.h   (revision 5227)
+++ src/core/server-rec.h   (working copy)
@@ -22,9 +22,7 @@
 int readtag; /* input tag */

 /* for net_connect_nonblock() */
-GIOChannel *connect_pipe[2];
 int connect_tag;
-int connect_pid;

 RAWLOG_REC *rawlog;
 GHashTable *module_data;
Index: src/core/servers-reconnect.c
===================================================================
--- src/core/servers-reconnect.c    (revision 5227)
+++ src/core/servers-reconnect.c    (working copy)
@@ -198,6 +198,8 @@
    dest->ssl_cafile = g_strdup(src->ssl_cafile);
    dest->ssl_capath = g_strdup(src->ssl_capath);

+   dest->attempt_count = src->attempt_count;
+
    return dest;
 }

Index: src/core/servers.c
===================================================================
--- src/core/servers.c  (revision 5227)
+++ src/core/servers.c  (working copy)
@@ -17,6 +17,8 @@
     with this program; if not, write to the Free Software Foundation, Inc.,
     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
+#include <glib.h>
+#include <gio/gio.h>

 #include "module.h"
 #include "signals.h"
@@ -24,6 +26,7 @@
 #include "net-disconnect.h"
 #include "net-nonblock.h"
 #include "net-sendbuffer.h"
+#include "net-resolver.h"
 #include "misc.h"
 #include "rawlog.h"
 #include "settings.h"
@@ -46,24 +49,17 @@

    signal_emit("server connect failed", 2, server, msg);

+#if 0
    if (server->connect_tag != -1) {
        g_source_remove(server->connect_tag);
        server->connect_tag = -1;
    }
+#endif
    if (server->handle != NULL) {
        net_sendbuffer_destroy(server->handle, TRUE);
        server->handle = NULL;
    }

-   if (server->connect_pipe[0] != NULL) {
-       g_io_channel_close(server->connect_pipe[0]);
-       g_io_channel_unref(server->connect_pipe[0]);
-       g_io_channel_close(server->connect_pipe[1]);
-       g_io_channel_unref(server->connect_pipe[1]);
-       server->connect_pipe[0] = NULL;
-       server->connect_pipe[1] = NULL;
-   }
-
    server_unref(server);
 }

@@ -262,87 +258,6 @@
    }
 }

-static void server_connect_callback_readpipe(SERVER_REC *server)
-{
-   RESOLVED_IP_REC iprec;
-        IPADDR *ip;
-   const char *errormsg;
-   char *servername = NULL;
-
-   g_source_remove(server->connect_tag);
-   server->connect_tag = -1;
-
-   net_gethostbyname_return(server->connect_pipe[0], &iprec);
-
-   g_io_channel_close(server->connect_pipe[0]);
-   g_io_channel_unref(server->connect_pipe[0]);
-   g_io_channel_close(server->connect_pipe[1]);
-   g_io_channel_unref(server->connect_pipe[1]);
-
-   server->connect_pipe[0] = NULL;
-   server->connect_pipe[1] = NULL;
-
-   /* figure out if we should use IPv4 or v6 address */
-   if (iprec.error != 0) {
-                /* error */
-       ip = NULL;
-   } else if (server->connrec->family == AF_INET) {
-       /* force IPv4 connection */
-       ip = iprec.ip4.family == 0 ? NULL : &iprec.ip4;
-       servername = iprec.host4;
-   } else if (server->connrec->family == AF_INET6) {
-       /* force IPv6 connection */
-       ip = iprec.ip6.family == 0 ? NULL : &iprec.ip6;
-       servername = iprec.host6;
-   } else {
-       /* pick the one that was found, or if both do it like
-          /SET resolve_prefer_ipv6 says. */
-       if (iprec.ip4.family == 0 ||
-           (iprec.ip6.family != 0 &&
-            settings_get_bool("resolve_prefer_ipv6"))) {
-           ip = &iprec.ip6;
-           servername = iprec.host6;
-       } else {
-           ip = &iprec.ip4;
-           servername = iprec.host4;
-       }
-   }
-
-   if (ip != NULL) {
-       /* host lookup ok */
-       if (servername) {
-           g_free(server->connrec->address);
-           server->connrec->address = g_strdup(servername);
-       }
-       server_real_connect(server, ip, NULL);
-       errormsg = NULL;
-   } else {
-       if (iprec.error == 0 || net_hosterror_notfound(iprec.error)) {
-           /* IP wasn't found for the host, don't try to
-              reconnect back to this server */
-           server->dns_error = TRUE;
-       }
-
-       if (iprec.error == 0) {
-           /* forced IPv4 or IPv6 address but it wasn't found */
-           errormsg = server->connrec->family == AF_INET ?
-               "IPv4 address not found for host" :
-               "IPv6 address not found for host";
-       } else {
-           /* gethostbyname() failed */
-           errormsg = iprec.errorstr != NULL ? iprec.errorstr :
-               "Host lookup failed";
-       }
-
-       server->connection_lost = TRUE;
-       server_connect_failed(server, errormsg);
-   }
-
-   g_free(iprec.errorstr);
-   g_free(iprec.host4);
-   g_free(iprec.host6);
-}
-
 SERVER_REC *server_connect(SERVER_CONNECT_REC *conn)
 {
    CHAT_PROTOCOL_REC *proto;
@@ -386,11 +301,19 @@
    server->connect_tag = -1;
 }

+static void resolved_callback(SERVER_REC *server, IPADDR *ip, int port, char *errormessage) {
+    if (errormessage != NULL) {
+        server->connection_lost = TRUE;
+        server_connect_failed(server, errormessage);
+    } else {
+        server_real_connect(server, ip, NULL); // TODO: port
+    }
+}
+
 /* starts connecting to server */
 int server_start_connect(SERVER_REC *server)
 {
    const char *connect_address;
-        int fd[2];

    g_return_val_if_fail(server != NULL, FALSE);
    if (!server->connrec->unix_socket && server->connrec->port <= 0)
@@ -410,27 +333,18 @@
        server_real_connect(server, NULL, server->connrec->address);
    } else {
        /* resolve host name */
-       if (pipe(fd) != 0) {
-           g_warning("server_connect(): pipe() failed.");
-           g_free(server->tag);
-           g_free(server->nick);
-           return FALSE;
-       }
+       connect_address = server->connrec->proxy != NULL ?
+           server->connrec->proxy : server->connrec->address;

-       server->connect_pipe[0] = g_io_channel_new(fd[0]);
-       server->connect_pipe[1] = g_io_channel_new(fd[1]);
+       net_resolve_server(server, NULL, resolved_callback);

-       connect_address = server->connrec->proxy != NULL ?
-           server->connrec->proxy : server->connrec->address;
-       server->connect_pid =
-           net_gethostbyname_nonblock(connect_address,
-                          server->connect_pipe[1],
-                          settings_get_bool("resolve_reverse_lookup"));
+#if 0
        server->connect_tag =
            g_input_add(server->connect_pipe[0], G_INPUT_READ,
                    (GInputFunction)
                    server_connect_callback_readpipe,
                    server);
+#endif

        lookup_servers = g_slist_append(lookup_servers, server);

@@ -473,13 +387,13 @@
    if (server->disconnected)
        return;

+#if 0
    if (server->connect_tag != -1) {
        /* still connecting to server.. */
-       if (server->connect_pid != -1)
-           net_disconnect_nonblock(server->connect_pid);
        server_connect_failed(server, NULL);
        return;
    }
+#endif

    servers = g_slist_remove(servers, server);