changeset 29877:755219afed9f

Implementation of IPv6 support for Bonjour * IPv6 buddy presences are preferred to IPv4 presences * File Transfers currently don't support IPv6 (due to limitations in network.c) Fixes #11290
author Daniel Atallah <daniel.atallah@gmail.com>
date Wed, 24 Feb 2010 05:00:09 +0000
parents 59099ccd141e
children dbbb7df5af7c
files ChangeLog libpurple/protocols/bonjour/bonjour.c libpurple/protocols/bonjour/bonjour_ft.c libpurple/protocols/bonjour/jabber.c libpurple/protocols/bonjour/jabber.h libpurple/protocols/bonjour/mdns_avahi.c
diffstat 6 files changed, 217 insertions(+), 92 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Wed Feb 24 04:39:15 2010 +0000
+++ b/ChangeLog	Wed Feb 24 05:00:09 2010 +0000
@@ -18,6 +18,9 @@
 	* Make the search dialog unobtrusive in the conversation window (by making
 	  it look and behave like the search dialog in Firefox)
 
+	Bonjour:
+	* Added support for IPv6. (Thanks to T_X for testing)
+
 version 2.6.6 (02/18/2010):
 	libpurple:
 	* Fix 'make check' on OS X. (David Fang)
--- a/libpurple/protocols/bonjour/bonjour.c	Wed Feb 24 04:39:15 2010 +0000
+++ b/libpurple/protocols/bonjour/bonjour.c	Wed Feb 24 05:00:09 2010 +0000
@@ -101,6 +101,8 @@
 
 	/* Start waiting for jabber connections (iChat style) */
 	bd->jabber_data = g_new0(BonjourJabber, 1);
+	bd->jabber_data->socket = -1;
+	bd->jabber_data->socket6 = -1;
 	bd->jabber_data->port = purple_account_get_int(account, "port", BONJOUR_DEFAULT_PORT);
 	bd->jabber_data->account = account;
 
--- a/libpurple/protocols/bonjour/bonjour_ft.c	Wed Feb 24 04:39:15 2010 +0000
+++ b/libpurple/protocols/bonjour/bonjour_ft.c	Wed Feb 24 05:00:09 2010 +0000
@@ -747,8 +747,7 @@
 	XepIq *iq;
 	xmlnode *query, *streamhost;
 	gchar *port;
-	const char *next_ip, *local_ip;
-	const char token [] = ";";
+	GSList *local_ips;
 	BonjourData *bd;
 
 	purple_debug_info("bonjour", "Bonjour-bytestreams-listen. sock=%d.\n", sock);
@@ -773,17 +772,16 @@
 
 	xfer->local_port = purple_network_get_port_from_fd(sock);
 
-	local_ip = purple_network_get_my_ip_ext2(sock);
-	/* cheat a little here - the intent of the "const" attribute is to make it clear that the string doesn't need to be freed */
-	next_ip = strtok((char *)local_ip, token);
+	local_ips = bonjour_jabber_get_local_ips(sock);
 
 	port = g_strdup_printf("%hu", xfer->local_port);
-	while(next_ip != NULL) {
+	while(local_ips) {
 		streamhost = xmlnode_new_child(query, "streamhost");
 		xmlnode_set_attrib(streamhost, "jid", xf->sid);
-		xmlnode_set_attrib(streamhost, "host", next_ip);
+		xmlnode_set_attrib(streamhost, "host", local_ips->data);
 		xmlnode_set_attrib(streamhost, "port", port);
-		next_ip = strtok(NULL, token);
+		g_free(local_ips->data);
+		local_ips = g_slist_delete_link(local_ips, local_ips);
 	}
 	g_free(port);
 
@@ -796,15 +794,17 @@
 	XepXfer *xf;
 	if(xfer == NULL)
 		return;
+
 	purple_debug_info("bonjour", "Bonjour-bytestreams-init.\n");
 	xf = xfer->data;
+
 	purple_network_listen_map_external(FALSE);
 	xf->listen_data = purple_network_listen_range(0, 0, SOCK_STREAM,
 						      bonjour_bytestreams_listen, xfer);
 	purple_network_listen_map_external(TRUE);
-	if (xf->listen_data == NULL) {
+	if (xf->listen_data == NULL)
 		purple_xfer_cancel_local(xfer);
-	}
+
 	return;
 }
 
--- a/libpurple/protocols/bonjour/jabber.c	Wed Feb 24 04:39:15 2010 +0000
+++ b/libpurple/protocols/bonjour/jabber.c	Wed Feb 24 05:00:09 2010 +0000
@@ -44,6 +44,11 @@
 #endif
 #include <fcntl.h>
 
+#ifdef HAVE_GETIFADDRS
+#include <ifaddrs.h>
+#endif
+
+
 #include "network.h"
 #include "eventloop.h"
 #include "connection.h"
@@ -623,15 +628,22 @@
 
 }
 
+#ifndef INET6_ADDRSTRLEN
+#define INET6_ADDRSTRLEN 46
+#endif
+
 static void
 _server_socket_handler(gpointer data, int server_socket, PurpleInputCondition condition)
 {
 	BonjourJabber *jdata = data;
-	struct sockaddr_in their_addr; /* connector's address information */
-	socklen_t sin_size = sizeof(struct sockaddr);
+	struct sockaddr_storage their_addr; /* connector's address information */
+	socklen_t sin_size = sizeof(struct sockaddr_storage);
 	int client_socket;
 	int flags;
-	char *address_text = NULL;
+#ifdef HAVE_INET_NTOP
+	char addrstr[INET6_ADDRSTRLEN];
+#endif
+	const char *address_text;
 	struct _match_buddies_by_address_t *mbba;
 	BonjourJabberConversation *bconv;
 	GSList *buddies;
@@ -640,7 +652,9 @@
 	if (condition != PURPLE_INPUT_READ)
 		return;
 
-	if ((client_socket = accept(server_socket, (struct sockaddr *)&their_addr, &sin_size)) == -1)
+	memset(&their_addr, 0, sin_size);
+
+	if ((client_socket = accept(server_socket, (struct sockaddr*)&their_addr, &sin_size)) == -1)
 		return;
 
 	flags = fcntl(client_socket, F_GETFL);
@@ -650,7 +664,16 @@
 #endif
 
 	/* Look for the buddy that has opened the conversation and fill information */
-	address_text = inet_ntoa(their_addr.sin_addr);
+#ifdef HAVE_INET_NTOP
+	if (their_addr.ss_family == AF_INET6)
+		address_text = inet_ntop(their_addr.ss_family, &((struct sockaddr_in6 *)&their_addr)->sin6_addr,
+			addrstr, sizeof(addrstr));
+	else
+		address_text = inet_ntop(their_addr.ss_family, &((struct sockaddr_in *)&their_addr)->sin_addr,
+			addrstr, sizeof(addrstr));
+#else
+	address_text = inet_ntoa(((struct sockaddr_in *)&their_addr)->sin_addr);
+#endif
 	purple_debug_info("bonjour", "Received incoming connection from %s.\n", address_text);
 	mbba = g_new0(struct _match_buddies_by_address_t, 1);
 	mbba->address = address_text;
@@ -680,52 +703,42 @@
 
 }
 
-gint
-bonjour_jabber_start(BonjourJabber *jdata)
+static int
+start_serversocket_listening(int port, int socket, struct sockaddr *addr, size_t addr_size, gboolean ip6, gboolean allow_port_fallback)
 {
-	struct sockaddr_in my_addr;
+	int ret_port = port;
 
-	/* Open a listening socket for incoming conversations */
-	jdata->socket = socket(PF_INET, SOCK_STREAM, 0);
-	if (jdata->socket < 0) {
-		gchar *buf = g_strdup_printf(_("Unable to create socket: %s"),
-				g_strerror(errno));
-		purple_connection_error_reason(jdata->account->gc,
-			PURPLE_CONNECTION_ERROR_NETWORK_ERROR, buf);
-		g_free(buf);
-		return -1;
-	}
-
-	memset(&my_addr, 0, sizeof(struct sockaddr_in));
-	my_addr.sin_family = AF_INET;
+	purple_debug_info("bonjour", "Attempting to bind IPv%d socket to port %d.\n", ip6 ? 6 : 4, port);
 
 	/* Try to use the specified port - if it isn't available, use a random port */
-	my_addr.sin_port = htons(jdata->port);
-	if (bind(jdata->socket, (struct sockaddr*)&my_addr, sizeof(struct sockaddr)) != 0)
-	{
+	if (bind(socket, addr, addr_size) != 0) {
+
 		purple_debug_info("bonjour", "Unable to bind to specified "
-				"port %i: %s\n", jdata->port, g_strerror(errno));
-		my_addr.sin_port = 0;
-		if (bind(jdata->socket, (struct sockaddr*)&my_addr, sizeof(struct sockaddr)) != 0)
-		{
-			gchar *buf = g_strdup_printf(_("Unable to bind socket "
-					"to port: %s"), g_strerror(errno));
-			purple_connection_error_reason(jdata->account->gc,
-				PURPLE_CONNECTION_ERROR_NETWORK_ERROR, buf);
-			g_free(buf);
+				"port %i: %s\n", port, g_strerror(errno));
+
+		if (!allow_port_fallback) {
+			purple_debug_warning("bonjour", "Not attempting random port assignment.\n");
 			return -1;
 		}
-		jdata->port = purple_network_get_port_from_fd(jdata->socket);
+#ifdef PF_INET6
+		if (ip6)
+			((struct sockaddr_in6 *) addr)->sin6_port = 0;
+		else
+#endif
+		((struct sockaddr_in *) addr)->sin_port = 0;
+
+		if (bind(socket, addr, addr_size) != 0) {
+			purple_debug_error("bonjour", "Unable to bind IPv%d socket to port: %s\n", ip6 ? 6 : 4, g_strerror(errno));
+			return -1;
+		}
+		ret_port = purple_network_get_port_from_fd(socket);
 	}
 
+	purple_debug_info("bonjour", "Bound IPv%d socket to port %d.\n", ip6 ? 6 : 4, ret_port);
+
 	/* Attempt to listen on the bound socket */
-	if (listen(jdata->socket, 10) != 0)
-	{
-		gchar *buf = g_strdup_printf(_("Unable to listen on socket: %s"),
-				g_strerror(errno));
-		purple_connection_error_reason(jdata->account->gc,
-			PURPLE_CONNECTION_ERROR_NETWORK_ERROR, buf);
-		g_free(buf);
+	if (listen(socket, 10) != 0) {
+		purple_debug_error("bonjour", "Unable to listen on IPv%d socket: %s\n", ip6 ? 6 : 4, g_strerror(errno));
 		return -1;
 	}
 
@@ -739,8 +752,66 @@
 	}
 #endif
 
-	/* Open a watcher in the socket we have just opened */
-	jdata->watcher_id = purple_input_add(jdata->socket, PURPLE_INPUT_READ, _server_socket_handler, jdata);
+	return ret_port;
+}
+
+gint
+bonjour_jabber_start(BonjourJabber *jdata)
+{
+	int ipv6_port = -1, ipv4_port = -1;
+
+	/* Open a listening socket for incoming conversations */
+#ifdef PF_INET6
+	jdata->socket6 = socket(PF_INET6, SOCK_STREAM, 0);
+#endif
+	jdata->socket = socket(PF_INET, SOCK_STREAM, 0);
+	if (jdata->socket == -1 && jdata->socket6 == -1) {
+		purple_debug_error("bonjour", "Unable to create socket: %s",
+				g_strerror(errno));
+		return -1;
+	}
+
+#ifdef PF_INET6
+	if (jdata->socket6 != -1) {
+		struct sockaddr_in6 addr6;
+	        memset(&addr6, 0, sizeof(addr6));
+		addr6.sin6_family = AF_INET6;
+		addr6.sin6_port = htons(jdata->port);
+      		addr6.sin6_addr = in6addr_any;
+		ipv6_port = start_serversocket_listening(jdata->port, jdata->socket6, (struct sockaddr *) &addr6, sizeof(addr6), TRUE, TRUE);
+		/* Open a watcher in the socket we have just opened */
+		if (ipv6_port > 0) {
+			jdata->watcher_id6 = purple_input_add(jdata->socket6, PURPLE_INPUT_READ, _server_socket_handler, jdata);
+			jdata->port = ipv6_port;
+		} else {
+			purple_debug_error("bonjour", "Failed to start listening on IPv6 socket.\n");
+			close(jdata->socket6);
+			jdata->socket6 = -1;
+		}
+	}
+#endif
+	if (jdata->socket != -1) {
+		struct sockaddr_in addr4;
+		memset(&addr4, 0, sizeof(addr4));
+		addr4.sin_family = AF_INET;
+		addr4.sin_port = htons(jdata->port);
+		ipv4_port = start_serversocket_listening(jdata->port, jdata->socket, (struct sockaddr *) &addr4, sizeof(addr4), FALSE, ipv6_port != -1);
+		/* Open a watcher in the socket we have just opened */
+		if (ipv4_port > 0) {
+			jdata->watcher_id = purple_input_add(jdata->socket, PURPLE_INPUT_READ, _server_socket_handler, jdata);
+			jdata->port = ipv4_port;
+		} else {
+			purple_debug_error("bonjour", "Failed to start listening on IPv4 socket.\n");
+			close(jdata->socket);
+			jdata->socket = -1;
+		}
+	}
+
+	if (!(ipv6_port > 0 || ipv4_port > 0)) {
+		purple_debug_error("bonjour", "Unable to listen on socket: %s",
+				g_strerror(errno));
+		return -1;
+	}
 
 	return jdata->port;
 }
@@ -1101,6 +1172,10 @@
 		close(jdata->socket);
 	if (jdata->watcher_id > 0)
 		purple_input_remove(jdata->watcher_id);
+	if (jdata->socket6 >= 0)
+		close(jdata->socket6);
+	if (jdata->watcher_id6 > 0)
+		purple_input_remove(jdata->watcher_id6);
 
 	/* Close all the conversation sockets and remove all the watchers after sending end streams */
 	if (jdata->account->gc != NULL) {
@@ -1234,58 +1309,97 @@
 	return (ret >= 0) ? 0 : -1;
 }
 
-/* This returns a ';' delimited string containing all non-localhost IPs */
-const char *
-purple_network_get_my_ip_ext2(int fd)
+/* This returns a list containing all non-localhost IPs */
+GSList *
+bonjour_jabber_get_local_ips(int fd)
 {
-	char buffer[1024];
-	static char ip_ext[17 * 10];
+	GSList *ips = NULL;
+	const char *address_text;
+	int ret;
+
+#ifdef HAVE_GETIFADDRS /* This is required for IPv6 */
+	{
+	struct ifaddrs *ifap, *ifa;
+	struct sockaddr *addr;
+	char addrstr[INET6_ADDRSTRLEN];
+
+	ret = getifaddrs(&ifap);
+	if (ret != 0) {
+		const char *error = g_strerror(errno);
+		purple_debug_error("bonjour", "getifaddrs() error: %s\n", error ? error : "(null)");
+		return NULL;
+	}
+
+	for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
+		if (!(ifa->ifa_flags & IFF_RUNNING) || (ifa->ifa_flags & IFF_LOOPBACK) || ifa->ifa_addr == NULL)
+			continue;
+
+		addr = ifa->ifa_addr;
+		address_text = NULL;
+		switch (addr->sa_family) {
+			case AF_INET:
+				address_text = inet_ntop(addr->sa_family, &((struct sockaddr_in *)addr)->sin_addr,
+					addrstr, sizeof(addrstr));
+				break;
+#ifdef PF_INET6
+			case AF_INET6:
+				address_text = inet_ntop(addr->sa_family, &((struct sockaddr_in6 *)addr)->sin6_addr,
+					addrstr, sizeof(addrstr));
+				break;
+#endif
+		}
+
+		if (address_text != NULL) {
+			if (addr->sa_family == AF_INET)
+				ips = g_slist_append(ips, g_strdup(address_text));
+			else
+				ips = g_slist_prepend(ips, g_strdup(address_text));
+		}
+	}
+
+	freeifaddrs(ifap);
+
+	}
+#else
+	{
 	char *tmp;
-	char *tip;
 	struct ifconf ifc;
 	struct ifreq *ifr;
+	char buffer[1024];
 	struct sockaddr_in *sinptr;
-	guint32 lhost = htonl(127 * 256 * 256 * 256 + 1);
-	long unsigned int add;
 	int source = fd;
-	int len, count = 0;
 
 	if (fd < 0)
 		source = socket(PF_INET, SOCK_STREAM, 0);
 
 	ifc.ifc_len = sizeof(buffer);
 	ifc.ifc_req = (struct ifreq *)buffer;
-	ioctl(source, SIOCGIFCONF, &ifc);
+	ret = ioctl(source, SIOCGIFCONF, &ifc);
 
 	if (fd < 0)
 		close(source);
 
-	memset(ip_ext, 0, sizeof(ip_ext));
-	memcpy(ip_ext, "0.0.0.0", 7);
+	if (ret < 0) {
+		const char *error = g_strerror(errno);
+		purple_debug_error("bonjour", "ioctl(SIOCGIFCONF) error: %s\n", error ? error : "(null)");
+		return NULL;
+	}
+
 	tmp = buffer;
-	tip = ip_ext;
-	while (tmp < buffer + ifc.ifc_len && count < 10)
-	{
+	while (tmp < buffer + ifc.ifc_len) {
 		ifr = (struct ifreq *)tmp;
 		tmp += HX_SIZE_OF_IFREQ(*ifr);
 
-		if (ifr->ifr_addr.sa_family == AF_INET)
-		{
+		if (ifr->ifr_addr.sa_family == AF_INET) {
 			sinptr = (struct sockaddr_in *)&ifr->ifr_addr;
-			if (sinptr->sin_addr.s_addr != lhost)
-			{
-				add = ntohl(sinptr->sin_addr.s_addr);
-				len = g_snprintf(tip, 17, "%lu.%lu.%lu.%lu;",
-					((add >> 24) & 255),
-					((add >> 16) & 255),
-					((add >> 8) & 255),
-					add & 255);
-				tip = &tip[len];
-				count++;
-				continue;
+			if ((ntohl(sinptr->sin_addr.s_addr) >> 24) != 127) {
+				address_text = inet_ntoa(sinptr->sin_addr);
+				ips = g_slist_prepend(ips, g_strdup(address_text));
 			}
-		}
+ 		}
 	}
+	}
+#endif
 
-	return ip_ext;
+	return ips;
 }
--- a/libpurple/protocols/bonjour/jabber.h	Wed Feb 24 04:39:15 2010 +0000
+++ b/libpurple/protocols/bonjour/jabber.h	Wed Feb 24 05:00:09 2010 +0000
@@ -37,7 +37,9 @@
 {
 	gint port;
 	gint socket;
+	gint socket6;
 	gint watcher_id;
+	gint watcher_id6;
 	PurpleAccount *account;
 	GSList *pending_conversations;
 } BonjourJabber;
@@ -105,6 +107,6 @@
 
 XepIq *xep_iq_new(void *data, XepIqType type, const char *to, const char *from, const char *id);
 int xep_iq_send_and_free(XepIq *iq);
-const char *purple_network_get_my_ip_ext2(int fd);
+GSList * bonjour_jabber_get_local_ips(int fd);
 
 #endif /* _BONJOUR_JABBER_H_ */
--- a/libpurple/protocols/bonjour/mdns_avahi.c	Wed Feb 24 04:39:15 2010 +0000
+++ b/libpurple/protocols/bonjour/mdns_avahi.c	Wed Feb 24 05:00:09 2010 +0000
@@ -189,8 +189,12 @@
 					bb->ips = g_slist_remove(bb->ips, rd->ip);
 					g_free((gchar *) rd->ip);
 				}
-				bb->ips = g_slist_prepend(bb->ips, g_strdup(ip));
-				rd->ip = bb->ips->data;
+				rd->ip = g_strdup(ip);
+				/* IPv6 goes at the front of the list and IPv4 at the end so that we "prefer" IPv6, if present */
+				if (protocol == AVAHI_PROTO_INET6)
+					bb->ips = g_slist_prepend(bb->ips, (gchar *) rd->ip);
+				else
+					bb->ips = g_slist_append(bb->ips, (gchar *) rd->ip);
 			}
 
 			bb->port_p2pj = port;
@@ -249,7 +253,7 @@
 			/* Make sure it isn't us */
 			if (purple_utf8_strcasecmp(name, account->username) != 0) {
 				if (!avahi_service_resolver_new(avahi_service_browser_get_client(b),
-						interface, protocol, name, type, domain, AVAHI_PROTO_INET,
+						interface, protocol, name, type, domain, protocol,
 						0, _resolver_callback, account)) {
 					purple_debug_warning("bonjour", "_browser_callback -- Error initiating resolver: %s\n",
 						avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b))));
@@ -448,14 +452,14 @@
 		case PUBLISH_START:
 			publish_result = avahi_entry_group_add_service_strlst(
 				idata->group, AVAHI_IF_UNSPEC,
-				AVAHI_PROTO_INET, 0,
+				AVAHI_PROTO_UNSPEC, 0,
 				purple_account_get_username(data->account),
 				LINK_LOCAL_RECORD_NAME, NULL, NULL, data->port_p2pj, lst);
 			break;
 		case PUBLISH_UPDATE:
 			publish_result = avahi_entry_group_update_service_txt_strlst(
 				idata->group, AVAHI_IF_UNSPEC,
-				AVAHI_PROTO_INET, 0,
+				AVAHI_PROTO_UNSPEC, 0,
 				purple_account_get_username(data->account),
 				LINK_LOCAL_RECORD_NAME, NULL, lst);
 			break;
@@ -487,7 +491,7 @@
 
 	g_return_val_if_fail(idata != NULL, FALSE);
 
-	idata->sb = avahi_service_browser_new(idata->client, AVAHI_IF_UNSPEC, AVAHI_PROTO_INET, LINK_LOCAL_RECORD_NAME, NULL, 0, _browser_callback, data->account);
+	idata->sb = avahi_service_browser_new(idata->client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, LINK_LOCAL_RECORD_NAME, NULL, 0, _browser_callback, data->account);
 	if (!idata->sb) {
 
 		purple_debug_error("bonjour",
@@ -533,7 +537,7 @@
 				purple_account_get_username(data->account));
 
 		ret = avahi_entry_group_add_record(idata->buddy_icon_group, AVAHI_IF_UNSPEC,
-			AVAHI_PROTO_INET, flags, svc_name,
+			AVAHI_PROTO_UNSPEC, flags, svc_name,
 			AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_NULL, 120, avatar_data, avatar_len);
 
 		g_free(svc_name);
@@ -622,7 +626,7 @@
 
 	name = g_strdup_printf("%s." LINK_LOCAL_RECORD_NAME "local", buddy->name);
 	idata->buddy_icon_rec_browser = avahi_record_browser_new(session_idata->client, AVAHI_IF_UNSPEC,
-		AVAHI_PROTO_INET, name, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_NULL, 0,
+		AVAHI_PROTO_UNSPEC, name, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_NULL, 0,
 		_buddy_icon_record_cb, buddy);
 	g_free(name);