/pidgin/main: 9e41dd502922: Bonjour: Retry alternative address o...

Linus L?ssing linus.luessing at web.de
Sat Jan 19 12:52:12 EST 2013


Changeset: 9e41dd502922b6b552f2b61d30e3bbb1fec53345
Author:	 Linus L?ssing <linus.luessing at web.de>
Date:	 2012-10-25 17:58 +0200
Branch:	 release-2.x.y
URL: http://hg.pidgin.im/pidgin/main/rev/9e41dd502922

Description:

Bonjour: Retry alternative address offers for file transfers too

So far for a Bonjour file transfer libpurple only tried to connect to and fetch
a file via the offered address matching that hosts 'primary' ip address.

With this patch libpurple will retry with the next offered address(es) in case
of connection failures like XEP-0065 suggests.

It will also accept and try any offered address, even if it is not announced
via mDNS.

For the special case of link local IPv6 addresses, we will try to guess the
according interface name from our Bonjour IP address list.

This patch makes the underlying issue of ticket #14755 (the missing IPv6
listening socket) less critical in that activating IPv6 does not break bonjour
file transfer in general anymore.

Fixes #14755

diffstat:

 libpurple/protocols/bonjour/bonjour_ft.c |  162 +++++++++++++++++++++++++++---
 libpurple/protocols/bonjour/bonjour_ft.h |    2 +
 2 files changed, 144 insertions(+), 20 deletions(-)

diffs (277 lines):

diff --git a/libpurple/protocols/bonjour/bonjour_ft.c b/libpurple/protocols/bonjour/bonjour_ft.c
--- a/libpurple/protocols/bonjour/bonjour_ft.c
+++ b/libpurple/protocols/bonjour/bonjour_ft.c
@@ -33,7 +33,7 @@
 static void
 bonjour_bytestreams_init(PurpleXfer *xfer);
 static void
-bonjour_bytestreams_connect(PurpleXfer *xfer, PurpleBuddy *pb);
+bonjour_bytestreams_connect(PurpleXfer *xfer);
 static void
 bonjour_xfer_init(PurpleXfer *xfer);
 static void
@@ -280,6 +280,25 @@ xep_ft_si_result(PurpleXfer *xfer, char 
 	xep_iq_send_and_free(iq);
 }
 
+/**
+ * Frees the whole tree of an xml node
+ *
+ * First determines the root of the xml tree and then frees the whole tree
+ * from there.
+ *
+ * @param node	The node to free the tree from
+ */
+static void
+xmlnode_free_tree(xmlnode *node)
+{
+	g_return_if_fail(node != NULL);
+
+	while(xmlnode_get_parent(node))
+		node = xmlnode_get_parent(node);
+
+	xmlnode_free(node);
+}
+
 static void
 bonjour_free_xfer(PurpleXfer *xfer)
 {
@@ -310,6 +329,9 @@ bonjour_free_xfer(PurpleXfer *xfer)
 		g_free(xf->proxy_host);
 		g_free(xf->buddy_ip);
 		g_free(xf->sid);
+
+		xmlnode_free_tree(xf->streamhost);
+
 		g_free(xf);
 		xfer->data = NULL;
 	}
@@ -546,20 +568,98 @@ out:
 	return !strcmp(host, buddy_ip);
 }
 
+static inline gint
+xep_addr_differ(const char *buddy_ip, const char *host)
+{
+	return !xep_cmp_addr(host, buddy_ip);
+}
+
+/**
+ * Create and insert an identical twin
+ *
+ * Creates a copy of the specified node and inserts it right after
+ * this original node.
+ *
+ * @param node	The node to clone
+ * @return	A pointer to the new, cloned twin if successful
+ *		or NULL otherwise.
+ */
+static xmlnode *
+xmlnode_insert_twin_copy(xmlnode *node) {
+	xmlnode *copy;
+
+	g_return_val_if_fail(node != NULL, NULL);
+
+	copy = xmlnode_copy(node);
+	g_return_val_if_fail(copy != NULL, NULL);
+
+	copy->next = node->next;
+	node->next = copy;
+
+	return copy;
+}
+
+/**
+ * Tries to append an interface scope to an IPv6 link local address.
+ *
+ * If the given address is a link local IPv6 address (with no
+ * interface scope) then we try to determine all fitting interfaces
+ * from our Bonjour IP address list.
+ *
+ * For any such found matches we insert a copy of our current xml
+ * streamhost entry right after this streamhost entry and append
+ * the determined interface to the host address of this copy.
+ *
+ * @param cur_streamhost	The XML streamhost node we examine
+ * @param host	The host address to examine in text form
+ * @param pb	Buddy to get the list of link local IPv6 addresses
+ *		and their interface from
+ * @return	Returns TRUE if the specified 'host' address is a
+ *		link local IPv6 address with no interface scope.
+ *		Otherwise returns FALSE.
+ */
 static gboolean
-__xep_bytestreams_parse(PurpleBuddy *pb, PurpleXfer *xfer, xmlnode *query,
+add_ipv6_link_local_ifaces(xmlnode *cur_streamhost, const char *host,
+			   const PurpleBuddy *pb) {
+	xmlnode *new_streamhost = NULL;
+	struct in6_addr in6_addr;
+	BonjourBuddy *bb;
+	GSList *ip_elem;
+
+	if (inet_pton(AF_INET6, host, &in6_addr) != 1 ||
+	    !IN6_IS_ADDR_LINKLOCAL(&in6_addr) ||
+	    strchr(host, '%'))
+		return FALSE;
+
+	bb = purple_buddy_get_protocol_data(pb);
+
+	for (ip_elem = bb->ips;
+	     (ip_elem = g_slist_find_custom(ip_elem, host, (GCompareFunc)&xep_addr_differ));
+	     ip_elem = ip_elem->next) {
+		purple_debug_info("bonjour", "Inserting an xmlnode twin copy for %s with new host address %s\n",
+				  host, (char*)ip_elem->data);
+		new_streamhost = xmlnode_insert_twin_copy(cur_streamhost);
+		xmlnode_set_attrib(new_streamhost, "host", ip_elem->data);
+	}
+
+	if (!new_streamhost)
+		purple_debug_info("bonjour", "No interface for this IPv6 link local address found: %s\n",
+				  host);
+
+	return TRUE;
+}
+
+static gboolean
+__xep_bytestreams_parse(PurpleBuddy *pb, PurpleXfer *xfer, xmlnode *streamhost,
 			const char *iq_id)
 {
+	char *tmp_iq_id;
 	const char *jid, *host, *port;
 	int portnum;
-	xmlnode *streamhost;
 	XepXfer *xf = NULL;
 
 	xf = (XepXfer*)xfer->data;
-	for(streamhost = xmlnode_get_child(query, "streamhost");
-			streamhost;
-			streamhost = xmlnode_get_next_twin(streamhost)) {
-
+	for(; streamhost; streamhost = xmlnode_get_next_twin(streamhost)) {
 		if(!(jid = xmlnode_get_attrib(streamhost, "jid")) ||
 		   !(host = xmlnode_get_attrib(streamhost, "host")) ||
 		   !(port = xmlnode_get_attrib(streamhost, "port")) ||
@@ -568,29 +668,36 @@ static gboolean
 			continue;
 		}
 
-		if(!xep_cmp_addr(host, xf->buddy_ip))
+		/* skip IPv6 link local addresses with no interface scope
+		 * (but try to add a new one with an interface scope then) */
+		if(add_ipv6_link_local_ifaces(streamhost, host, pb))
 			continue;
 
+		tmp_iq_id = g_strdup(iq_id);
 		g_free(xf->iq_id);
-		xf->iq_id = g_strdup(iq_id);
+		g_free(xf->jid);
+		g_free(xf->proxy_host);
+
+		xf->iq_id = tmp_iq_id;
 		xf->jid = g_strdup(jid);
-		xf->proxy_host = g_strdup(xf->buddy_ip);
+		xf->proxy_host = g_strdup(host);
 		xf->proxy_port = portnum;
+		xf->streamhost = streamhost;
+		xf->pb = pb;
 		purple_debug_info("bonjour", "bytestream offer parse"
 				  "jid=%s host=%s port=%d.\n", jid, host, portnum);
-		bonjour_bytestreams_connect(xfer, pb);
+		bonjour_bytestreams_connect(xfer);
 		return TRUE;
 	}
 
 	return FALSE;
 }
 
-
 void
 xep_bytestreams_parse(PurpleConnection *pc, xmlnode *packet, PurpleBuddy *pb)
 {
 	const char *type, *from, *iq_id, *sid;
-	xmlnode *query;
+	xmlnode *query, *streamhost;
 	BonjourData *bd;
 	PurpleXfer *xfer;
 
@@ -610,6 +717,10 @@ xep_bytestreams_parse(PurpleConnection *
 	if(!type)
 		return;
 
+	query = xmlnode_copy(query);
+	if (!query)
+		return;
+
 	if(strcmp(type, "set")) {
 		purple_debug_info("bonjour", "bytestream offer Message type - Unknown-%s.\n", type);
 		return;
@@ -621,7 +732,9 @@ xep_bytestreams_parse(PurpleConnection *
 
 	sid = xmlnode_get_attrib(query, "sid");
 	xfer = bonjour_si_xfer_find(bd, sid, from);
-	if(xfer && __xep_bytestreams_parse(pb, xfer, query, iq_id))
+	streamhost = xmlnode_get_child(query, "streamhost");
+
+	if(xfer && streamhost && __xep_bytestreams_parse(pb, xfer, streamhost, iq_id))
 		return; /* success */
 
 	purple_debug_error("bonjour", "Didn't find an acceptable streamhost.\n");
@@ -874,15 +987,22 @@ bonjour_bytestreams_connect_cb(gpointer 
 	XepIq *iq;
 	xmlnode *q_node, *tmp_node;
 	BonjourData *bd;
+	gboolean ret = FALSE;
 
 	xf->proxy_connection = NULL;
 
 	if(source < 0) {
-		purple_debug_error("bonjour", "Error connecting via SOCKS5 - %s\n",
-			error_message ? error_message : "(null)");
-		xep_ft_si_reject(xf->data, xf->iq_id, xfer->who, "404", "cancel");
-		/* Cancel the connection */
-		purple_xfer_cancel_local(xfer);
+		purple_debug_error("bonjour", "Error connecting via SOCKS5 to %s - %s\n",
+			xf->proxy_host, error_message ? error_message : "(null)");
+
+		tmp_node = xmlnode_get_next_twin(xf->streamhost);
+		ret = __xep_bytestreams_parse(xf->pb, xfer, tmp_node, xf->iq_id);
+
+		if (!ret) {
+			xep_ft_si_reject(xf->data, xf->iq_id, purple_xfer_get_remote_user(xfer), "404", "cancel");
+			/* Cancel the connection */
+			purple_xfer_cancel_local(xfer);
+		}
 		return;
 	}
 
@@ -904,8 +1024,9 @@ bonjour_bytestreams_connect_cb(gpointer 
 }
 
 static void
-bonjour_bytestreams_connect(PurpleXfer *xfer, PurpleBuddy *pb)
+bonjour_bytestreams_connect(PurpleXfer *xfer)
 {
+	PurpleBuddy *pb;
 	PurpleAccount *account = NULL;
 	XepXfer *xf;
 	char dstaddr[41];
@@ -923,6 +1044,7 @@ bonjour_bytestreams_connect(PurpleXfer *
 	if(!xf)
 		return;
 
+	pb = xf->pb;
 	name = purple_buddy_get_name(pb);
 	account = purple_buddy_get_account(pb);
 
diff --git a/libpurple/protocols/bonjour/bonjour_ft.h b/libpurple/protocols/bonjour/bonjour_ft.h
--- a/libpurple/protocols/bonjour/bonjour_ft.h
+++ b/libpurple/protocols/bonjour/bonjour_ft.h
@@ -50,6 +50,8 @@ struct _XepXfer
 	char *jid;
 	char *proxy_host;
 	int proxy_port;
+	xmlnode *streamhost;
+	PurpleBuddy *pb;
 };
 
 /**



More information about the Commits mailing list