changeset 30153:35a1cf247168

Add network listen functions that accept a family argument (AF_INET(6?)). These allow code to portably support IPv6 for listeners (mostly file transfers and Bonjour). Callers should use the purple_socket_speaks_ipv4 to determine whether they need two sockets or just an IPv6 one. I used GIO's g_socket_speaks_ipv4 as the inspiration for that.
author Paul Aurich <paul@darkrain42.org>
date Sat, 17 Apr 2010 01:27:04 +0000
parents 5be4137c4a68
children 933c8251e036
files ChangeLog.API configure.ac libpurple/network.c libpurple/network.h libpurple/util.c libpurple/util.h
diffstat 6 files changed, 161 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog.API	Fri Apr 16 03:44:18 2010 +0000
+++ b/ChangeLog.API	Sat Apr 17 01:27:04 2010 +0000
@@ -19,7 +19,15 @@
 		  IPs on the system.  On systems with the getifaddrs() function,
 		  this will return both IPv4 and IPv6 addresses (excluding link-local
 		  and loopback addresses).  On others, it returns just IPv4 addresses.
+		* purple_network_listen_family and
+		  purple_network_listen_range_family.  These will replace the
+		  versions without _family in 3.0.0 and allow the caller to
+		  specifically request either an IPv4 or IPv6 socket.  IPv6 is
+		  only supported if the getaddrinfo() function is available
+		  at build-time (not the case on Windows, currently).
 		* purple_prpl_got_media_caps
+		* purple_socket_get_family
+		* purple_socket_speaks_ipv4
 		* purple_unescape_text
 		* purple_uuid_random
 		* media_caps to the PurpleBuddy struct
--- a/configure.ac	Fri Apr 16 03:44:18 2010 +0000
+++ b/configure.ac	Sat Apr 17 01:27:04 2010 +0000
@@ -184,6 +184,12 @@
 	[Define if struct sockaddr has an sa_len member])],[:],
 	[#include <sys/socket.h>])
 
+dnl Check for v6-only sockets
+AC_CHECK_DECL([IPV6_V6ONLY],
+	[AC_DEFINE([HAVE_IPV6_V6ONLY],[1],
+	[Define if the IPV6_V6ONLY setsockopt option exists])],
+	[], [#include <netinet/in.h>])
+
 dnl to prevent the g_stat()/g_unlink() crash,
 dnl (09:50:07) Robot101: LSchiere2: it's easy. +LC_SYS_LARGEFILE somewhere in configure.ac
 AC_SYS_LARGEFILE
--- a/libpurple/network.c	Fri Apr 16 03:44:18 2010 +0000
+++ b/libpurple/network.c	Sat Apr 17 01:27:04 2010 +0000
@@ -394,7 +394,7 @@
 }
 
 static PurpleNetworkListenData *
-purple_network_do_listen(unsigned short port, int socket_type, PurpleNetworkListenCallback cb, gpointer cb_data)
+purple_network_do_listen(unsigned short port, int socket_family, int socket_type, PurpleNetworkListenCallback cb, gpointer cb_data)
 {
 	int listenfd = -1;
 	int flags;
@@ -412,7 +412,7 @@
 	g_snprintf(serv, sizeof(serv), "%hu", port);
 	memset(&hints, 0, sizeof(struct addrinfo));
 	hints.ai_flags = AI_PASSIVE;
-	hints.ai_family = AF_UNSPEC;
+	hints.ai_family = socket_family;
 	hints.ai_socktype = socket_type;
 	errnum = getaddrinfo(NULL /* any IP */, serv, &hints, &res);
 	if (errnum != 0) {
@@ -436,7 +436,7 @@
 		if (listenfd < 0)
 			continue;
 		if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0)
-			purple_debug_warning("network", "setsockopt: %s\n", g_strerror(errno));
+			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
@@ -451,6 +451,13 @@
 #else
 	struct sockaddr_in sockin;
 
+	if (socket_family != AF_INET && socket_family != AF_UNSPEC) {
+		purple_debug_warning("network", "Address family %d only "
+		                     "supported when built with getaddrinfo() "
+		                     "support\n", socket_family);
+		return NULL;
+	}
+
 	if ((listenfd = socket(AF_INET, socket_type, 0)) < 0) {
 		purple_debug_warning("network", "socket: %s\n", g_strerror(errno));
 		return NULL;
@@ -492,7 +499,8 @@
 	listen_data->cb_data = cb_data;
 	listen_data->socket_type = socket_type;
 
-	if (!listen_map_external || !purple_prefs_get_bool("/purple/network/map_ports"))
+	if (!purple_socket_speaks_ipv4(listenfd) || !listen_map_external ||
+			!purple_prefs_get_bool("/purple/network/map_ports"))
 	{
 		purple_debug_info("network", "Skipping external port mapping.\n");
 		/* The pmp_map_cb does what we want to do */
@@ -519,17 +527,29 @@
 }
 
 PurpleNetworkListenData *
-purple_network_listen(unsigned short port, int socket_type,
-		PurpleNetworkListenCallback cb, gpointer cb_data)
+purple_network_listen_family(unsigned short port, int socket_family,
+                             int socket_type, PurpleNetworkListenCallback cb,
+                             gpointer cb_data)
 {
 	g_return_val_if_fail(port != 0, NULL);
 
-	return purple_network_do_listen(port, socket_type, cb, cb_data);
+	return purple_network_do_listen(port, socket_family, socket_type,
+	                                cb, cb_data);
 }
 
 PurpleNetworkListenData *
-purple_network_listen_range(unsigned short start, unsigned short end,
-		int socket_type, PurpleNetworkListenCallback cb, gpointer cb_data)
+purple_network_listen(unsigned short port, int socket_type,
+		PurpleNetworkListenCallback cb, gpointer cb_data)
+{
+	return purple_network_listen_family(port, AF_UNSPEC, socket_type,
+	                                    cb, cb_data);
+}
+
+PurpleNetworkListenData *
+purple_network_listen_range_family(unsigned short start, unsigned short end,
+                                   int socket_family, int socket_type,
+                                   PurpleNetworkListenCallback cb,
+                                   gpointer cb_data)
 {
 	PurpleNetworkListenData *ret = NULL;
 
@@ -542,7 +562,7 @@
 	}
 
 	for (; start <= end; start++) {
-		ret = purple_network_do_listen(start, socket_type, cb, cb_data);
+		ret = purple_network_do_listen(start, AF_UNSPEC, socket_type, cb, cb_data);
 		if (ret != NULL)
 			break;
 	}
@@ -550,6 +570,15 @@
 	return ret;
 }
 
+PurpleNetworkListenData *
+purple_network_listen_range(unsigned short start, unsigned short end,
+                            int socket_type, PurpleNetworkListenCallback cb,
+                            gpointer cb_data)
+{
+	return purple_network_listen_range_family(start, end, AF_UNSPEC,
+	                                          socket_type, cb, cb_data);
+}
+
 void purple_network_listen_cancel(PurpleNetworkListenData *listen_data)
 {
 	if (listen_data->mapping_data != NULL)
--- a/libpurple/network.h	Fri Apr 16 03:44:18 2010 +0000
+++ b/libpurple/network.h	Sat Apr 17 01:27:04 2010 +0000
@@ -138,8 +138,8 @@
  *
  * This opens a listening port. The caller will want to set up a watcher
  * of type PURPLE_INPUT_READ on the fd returned in cb. It will probably call
- * accept in the watcher callback, and then possibly remove the watcher and close
- * the listening socket, and add a new watcher on the new socket accept
+ * accept in the watcher callback, and then possibly remove the watcher and
+ * close the listening socket, and add a new watcher on the new socket accept
  * returned.
  *
  * @param port The port number to bind to.  Must be greater than 0.
@@ -158,6 +158,27 @@
 		int socket_type, PurpleNetworkListenCallback cb, gpointer cb_data);
 
 /**
+ * \copydoc purple_network_listen
+ *
+ * Libpurple does not currently do any port mapping (stateful firewall hole
+ * poking) for IPv6-only listeners (if an IPv6 socket supports v4-mapped
+ * addresses, a mapping is done).
+ * 
+ * @param socket_family The protocol family of the socket.  This should be
+ *                      AF_INET for IPv4 or AF_INET6 for IPv6.  IPv6 sockets
+ *                      may or may not be able to accept IPv4 connections
+ *                      based on the system configuration (use
+ *                      purple_socket_speaks_ipv4 to check).  If an IPv6
+ *                      socket doesn't accept V4-mapped addresses, you will
+ *                      need a second listener to support both v4 and v6.
+ * @since 2.7.0
+ * @deprecated This function will be renamed to purple_network_listen in 3.0.0.
+ */
+PurpleNetworkListenData *purple_network_listen_family(unsigned short port,
+	int socket_family, int socket_type, PurpleNetworkListenCallback cb,
+	gpointer cb_data);
+
+/**
  * Opens a listening port selected from a range of ports.  The range of
  * ports used is chosen in the following manner:
  * If a range is specified in preferences, these values are used.
@@ -192,6 +213,28 @@
 		PurpleNetworkListenCallback cb, gpointer cb_data);
 
 /**
+ * \copydoc purple_network_listen_range
+ * 
+ * Libpurple does not currently do any port mapping (stateful firewall hole
+ * poking) for IPv6-only listeners (if an IPv6 socket supports v4-mapped
+ * addresses, a mapping is done).
+ * 
+ * @param socket_family The protocol family of the socket.  This should be
+ *                      AF_INET for IPv4 or AF_INET6 for IPv6.  IPv6 sockets
+ *                      may or may not be able to accept IPv4 connections
+ *                      based on the system configuration (use
+ *                      purple_socket_speaks_ipv4 to check).  If an IPv6
+ *                      socket doesn't accept V4-mapped addresses, you will
+ *                      need a second listener to support both v4 and v6.
+ * @since 2.7.0
+ * @deprecated This function will be renamed to purple_network_listen_range
+ *             in 3.0.0.
+ */
+PurpleNetworkListenData *purple_network_listen_range_family(
+	unsigned short start, unsigned short end, int socket_family,
+	int socket_type, PurpleNetworkListenCallback cb, gpointer cb_data);
+
+/**
  * This can be used to cancel any in-progress listener connection
  * by passing in the return value from either purple_network_listen()
  * or purple_network_listen_range().
--- a/libpurple/util.c	Fri Apr 16 03:44:18 2010 +0000
+++ b/libpurple/util.c	Sat Apr 17 01:27:04 2010 +0000
@@ -3002,6 +3002,47 @@
 	return NULL;
 }
 
+int
+purple_socket_get_family(int fd)
+{
+	struct sockaddr_storage addr;
+	socklen_t len = sizeof(addr);
+
+	g_return_val_if_fail(fd >= 0, -1);
+
+	if (getsockname(fd, (struct sockaddr *)&addr, &len))
+		return -1;
+
+	return ((struct sockaddr *)&addr)->sa_family;
+}
+
+gboolean
+purple_socket_speaks_ipv4(int fd)
+{
+	int family;
+	
+	g_return_val_if_fail(fd >= 0, FALSE);
+
+	family = purple_socket_get_family(fd);
+
+	switch (family) {
+	case AF_INET:
+		return TRUE;
+#if defined(IPV6_V6ONLY)
+	case AF_INET6:
+	{
+		int val = 0;
+		guint len = sizeof(val);
+
+		if (getsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, &len) != 0)
+			return FALSE;
+		return !val;
+	}
+#endif
+	default:
+		return FALSE;
+	}
+}
 
 /**************************************************************************
  * String Functions
--- a/libpurple/util.h	Fri Apr 16 03:44:18 2010 +0000
+++ b/libpurple/util.h	Sat Apr 17 01:27:04 2010 +0000
@@ -819,6 +819,28 @@
  */
 char *purple_fd_get_ip(int fd);
 
+/**
+ * Returns the address family of a socket.
+ *
+ * @param fd The socket file descriptor.
+ *
+ * @return The address family of the socket (AF_INET, AF_INET6, etc)
+ * @since 2.7.0
+ */
+int purple_socket_get_family(int fd);
+
+/**
+ * Returns TRUE if a socket is capable of speaking IPv4.
+ *
+ * This is the case for IPv4 sockets and, on some systems, IPv6 sockets
+ * (due to the IPv4-mapped address functionality).
+ *
+ * @param fd The socket file descriptor
+ * @return TRUE if a socket can speak IPv4.
+ * @since 2.7.0
+ */
+gboolean purple_socket_speaks_ipv4(int fd);
+
 /*@}*/