[PATCH 2/4] libpurple/network (+ bonjour): Fix socket listen address

Linus Lüssing linus.luessing at web.de
Fri Nov 25 06:20:08 EST 2011


The order of addresses returned by getaddrinfo() is undefined. Therefore
potentially a purple_network_do_listen() call might create either an
IPv4 or IPv6 socket. Furthermore if it were going to create an IPv6
listening socket it would depend on the operating systems configuration
and capabilities whether this IPv6 socket were able to receive IPv4
connections as well due to IPV6_V6ONLY not being explicitly set or unset
on the according socket.

Most operating systems (at least the Debian system I was testing on) seem
to implement getaddrinfo() to return the IPv4 addresses first which
leads to creating a socket capable of IPv4 only. However in the case of
bonjour, this led to not being able to transmit files if IPv6 was
available and activated in avahi: Peer A created an IPv4 socket but
announced all of its available IP addresses to peer B, including IPv6
addresses. During my tests peer B then tried to connect to A via IPv6
but failed due to A listening on IPv4 only and therefore not accepting
IPv6 connections, leading to a cancelled file transfer.

With this patch purple_network_do_listen() will try to create one
dualstack socket to allow incoming connections via both IPv4 and IPv6.
If dualstack sockets are not supported by the OS (e.g. Windows XP)
libpurple will fall back to create just an IPv4 socket.

Furthermore with this patch bonjour will not announce IPv6 addresses if
dualstack socket support is not available (e.g. Windows XP) to avoid
that the other peer tries to connect to an IP address which this peer is
actually not listening on.

This shall fix ticket #14755.

Signed-off-by: Linus Lüssing <linus.luessing at web.de>
---
 libpurple/network.c                  |  121 +++++++++++++++++++++++++++------
 libpurple/protocols/bonjour/jabber.c |    6 ++-
 2 files changed, 104 insertions(+), 23 deletions(-)

diff --git a/libpurple/network.c b/libpurple/network.c
index 54e8b38..8587fb9 100644
--- a/libpurple/network.c
+++ b/libpurple/network.c
@@ -397,17 +397,108 @@ void purple_network_listen_map_external(gboolean map_external)
 	listen_map_external = map_external;
 }
 
+static void
+purple_network_set_dualstack_sd(int listenfd)
+{
+	const int off = 0;
+
+#if defined(SOL_IPV6) && defined(IPV6_V6ONLY)
+	if (setsockopt(listenfd, SOL_IPV6, IPV6_V6ONLY, &off, sizeof(off)))
+		purple_debug_warning("network", "setsockopt(IPV6_V6ONLY): %s\n", g_strerror(errno));
+#else
+	purple_debug_warning("network", "dualstack socket not supported!\n");
+#endif
+}
+
+static int
+____purple_network_create_socket(struct addrinfo *addr, int af)
+{
+	const int on = 1;
+	int listenfd = -1;
+
+	listenfd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
+	if (listenfd < 0)
+		goto out;
+
+	if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)))
+		purple_debug_warning("network", "setsockopt(SO_REUSEADDR): %s\n",
+				     g_strerror(errno));
+
+#ifdef AF_INET6
+	if (af == AF_INET6)
+		purple_network_set_dualstack_sd(listenfd);
+#endif
+
+	if (bind(listenfd, addr->ai_addr, addr->ai_addrlen)) {
+		purple_debug_warning("network", "bind(): %s\n", g_strerror(errno));
+		close(listenfd);
+	}
+
+out:
+	return listenfd;
+}
+
+static int
+__purple_network_create_socket(struct addrinfo *res, int af)
+{
+	int listenfd;
+	struct addrinfo *next;
+
+	for (next = res; next; next = next->ai_next) {
+		if (next->ai_family != af)
+			continue;
+
+		listenfd = ____purple_network_create_socket(next, af);
+		if (listenfd >= 0)
+			return listenfd;
+	}
+
+	return -1;
+}
+
+/*
+ * Go through the list of addresses and attempt to listen on one of them,
+ * ipv6 dualstack socket prefered.
+ *
+ * If dualstack sockets are not supported (e.g. Windows XP),
+ * then we try IPv4 addresses only.
+ *
+ * @param addr	list of addresses
+ * @return	socket descriptor or -1 if unsuccessful
+ */
+static int
+purple_network_create_socket(struct addrinfo *addr)
+{
+	/* TODO: Try to listen to all returned addresses in the list with
+	 * IPV6_V6ONLY _set_ instead of unset to remove the dualstack
+	 * socket requirement and to allow IPv6 sockets on e.g. Windows XP.
+	 * If you remove this todo, then also remove the requirement in
+	 * libpurple/bonjour/jabber.c -> bonjour_jabber_get_local_ips().
+	 */
+#if defined(SOL_IPV6) && defined(IPV6_V6ONLY)
+	int listenfd;
+
+	listenfd = __purple_network_create_socket(addr, AF_INET6);
+	if (listenfd >= 0)
+		return listenfd;
+#endif
+
+	/*
+	 * Try IPv4 if no successful binding yet.
+	 */
+	return __purple_network_create_socket(addr, AF_INET);
+}
+
 static PurpleNetworkListenData *
 purple_network_do_listen(unsigned short port, int socket_family, int socket_type, PurpleNetworkListenCallback cb, gpointer cb_data)
 {
-	int listenfd = -1;
+	int listenfd;
 	int flags;
-	const int on = 1;
 	PurpleNetworkListenData *listen_data;
 	unsigned short actual_port;
 #ifdef HAVE_GETADDRINFO
 	int errnum;
-	struct addrinfo hints, *res, *next;
+	struct addrinfo hints, *res;
 	char serv[6];
 
 	/*
@@ -430,29 +521,15 @@ purple_network_do_listen(unsigned short port, int socket_family, int socket_type
 		return NULL;
 	}
 
-	/*
-	 * Go through the list of addresses and attempt to listen on
-	 * one of them.
-	 * XXX - Try IPv6 addresses first?
-	 */
-	for (next = res; next != NULL; next = next->ai_next) {
-		listenfd = socket(next->ai_family, next->ai_socktype, next->ai_protocol);
-		if (listenfd < 0)
-			continue;
-		if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0)
-			purple_debug_warning("network", "setsockopt(SO_REUSEADDR): %s\n", g_strerror(errno));
-		if (bind(listenfd, next->ai_addr, next->ai_addrlen) == 0)
-			break; /* success */
-		/* XXX - It is unclear to me (datallah) whether we need to be
-		   using a new socket each time */
-		close(listenfd);
+	listenfd = purple_network_create_socket(res);
+	if (listenfd < 0) {
+		freeaddrinfo(res);
+		return NULL;
 	}
-
 	freeaddrinfo(res);
 
-	if (next == NULL)
-		return NULL;
 #else
+	const int on = 1;
 	struct sockaddr_in sockin;
 
 	if (socket_family != AF_INET && socket_family != AF_UNSPEC) {
diff --git a/libpurple/protocols/bonjour/jabber.c b/libpurple/protocols/bonjour/jabber.c
index 3e40165..0f50b68 100644
--- a/libpurple/protocols/bonjour/jabber.c
+++ b/libpurple/protocols/bonjour/jabber.c
@@ -1384,7 +1384,11 @@ bonjour_jabber_get_local_ips(int fd)
 				address_text = inet_ntop(addr->sa_family, &((struct sockaddr_in *)addr)->sin_addr,
 					addrstr, sizeof(addrstr));
 				break;
-#ifdef PF_INET6
+/*
+ * We do not support non-dualstack sockets (sorry Windows XP!)
+ * - see libpurple/network.c -> purple_network_create_socket()
+ */
+#if defined(SOL_IPV6) && defined(IPV6_V6ONLY)
 			case AF_INET6:
 				address_text = inet_ntop(addr->sa_family, &((struct sockaddr_in6 *)addr)->sin6_addr,
 					addrstr, sizeof(addrstr));
-- 
1.7.7.1




More information about the Devel mailing list