diff libpurple/protocols/jabber/google.c @ 26080:88f183f7dfc7

Add automatic discovery of GTalk STUN servers when using a Gtalk account Is used for STUN candidate genration, unless a STUN server is set in prefs Does not handle GTalk relay setup yet
author Marcus Lundblad <ml@update.uu.se>
date Tue, 03 Feb 2009 21:37:27 +0000
parents ac56e5707a72
children a773b465935e
line wrap: on
line diff
--- a/libpurple/protocols/jabber/google.c	Mon Feb 02 11:37:07 2009 +0000
+++ b/libpurple/protocols/jabber/google.c	Tue Feb 03 21:37:27 2009 +0000
@@ -23,6 +23,8 @@
 #include "mediamanager.h"
 #include "util.h"
 #include "privacy.h"
+#include "dnsquery.h"
+#include "network.h"
 
 #include "buddy.h"
 #include "google.h"
@@ -30,6 +32,8 @@
 #include "presence.h"
 #include "iq.h"
 
+#include "jingle/jingle.h"
+
 #ifdef USE_VV
 
 typedef struct {
@@ -124,7 +128,7 @@
 	sess = google_session_create_xmlnode(session, "candidates");
 	xmlnode_insert_child(iq->node, sess);
 	xmlnode_set_attrib(iq->node, "to", session->remote_jid);
-	
+
 	for (;candidates;candidates = candidates->next) {
 		char port[8];
 		char pref[8];
@@ -132,7 +136,7 @@
 
 		if (!strcmp(transport->ip, "127.0.0.1"))
 			continue;
-	
+
 		candidate = xmlnode_new("candidate");
 
 		g_snprintf(port, sizeof(port), "%d", transport->port);
@@ -162,7 +166,6 @@
 		xmlnode_set_attrib(candidate, "generation", "0");
 		xmlnode_set_attrib(candidate, "network", "0");
 		xmlnode_insert_child(sess, candidate);
-		
 	}
 	jabber_iq_send(iq);
 }
@@ -246,6 +249,26 @@
 	}
 }
 
+static GParameter *
+jabber_google_session_get_params(JabberStream *js, guint *num)
+{
+	guint num_params;
+	GParameter *params = jingle_get_params(js, &num_params);
+	GParameter *new_params = g_new0(GParameter, num_params + 1);
+
+	memcpy(new_params, params, sizeof(GParameter) * num_params);
+
+	purple_debug_info("jabber", "setting Google jingle compatibility param\n");
+	new_params[num_params].name = "compatibility-mode";
+	g_value_init(&new_params[num_params].value, G_TYPE_UINT);
+	g_value_set_uint(&new_params[num_params].value, 1); /* NICE_COMPATIBILITY_GOOGLE */
+
+	g_free(params);
+	*num = num_params + 1;
+	return new_params;
+}
+
+
 PurpleMedia*
 jabber_google_session_initiate(JabberStream *js, const gchar *who, PurpleMediaSessionType type)
 {
@@ -253,7 +276,8 @@
 	JabberBuddy *jb;
 	JabberBuddyResource *jbr;
 	gchar *jid;
-	GParameter param;
+	GParameter *params;
+	guint num_params;
 
 	/* construct JID to send to */
 	jb = jabber_buddy_find(js, who, FALSE);
@@ -286,18 +310,15 @@
 			purple_media_manager_get(), js->gc,
 			"fsrtpconference", session->remote_jid, TRUE);
 
-	/* GTalk requires the NICE_COMPATIBILITY_GOOGLE param */
-	param.name = "compatibility-mode";
-	memset(&param.value, 0, sizeof(GValue));
-	g_value_init(&param.value, G_TYPE_UINT);
-	g_value_set_uint(&param.value, 1); /* NICE_COMPATIBILITY_GOOGLE */
+	params = jabber_google_session_get_params(js, &num_params);
 
 	if (purple_media_add_stream(session->media, "google-voice",
 				session->remote_jid, PURPLE_MEDIA_AUDIO,
-				"nice", 1, &param) == FALSE) {
+				"nice", num_params, params) == FALSE) {
 		purple_media_error(session->media, "Error adding stream.");
 		purple_media_hangup(session->media);
 		google_session_destroy(session);
+		g_free(params);
 		return NULL;
 	}
 
@@ -310,6 +331,7 @@
 		sessions = g_hash_table_new(google_session_id_hash,
 				google_session_id_equal);
 	g_hash_table_insert(sessions, &(session->id), session);
+	g_free(params);
 
 	return session->media;
 }
@@ -322,8 +344,9 @@
 	xmlnode *desc_element, *codec_element;
 	PurpleMediaCodec *codec;
 	const char *id, *encoding_name,  *clock_rate;
-	GParameter param;
-		
+	GParameter *params;
+	guint num_params;	
+
 	if (session->state != UNINIT) {
 		purple_debug_error("jabber", "Received initiate for active session.\n");
 		return;
@@ -332,22 +355,21 @@
 	session->media = purple_media_manager_create_media(purple_media_manager_get(), js->gc,
 							   "fsrtpconference", session->remote_jid, FALSE);
 
-	/* GTalk requires the NICE_COMPATIBILITY_GOOGLE param */
-	param.name = "compatibility-mode";
-	memset(&param.value, 0, sizeof(GValue));
-	g_value_init(&param.value, G_TYPE_UINT);
-	g_value_set_uint(&param.value, 1); /* NICE_COMPATIBILITY_GOOGLE */
+	params = jabber_google_session_get_params(js, &num_params);
 
 	if (purple_media_add_stream(session->media, "google-voice", session->remote_jid, 
-				PURPLE_MEDIA_AUDIO, "nice", 1, &param) == FALSE) {
+				PURPLE_MEDIA_AUDIO, "nice", num_params, params) == FALSE) {
 		purple_media_error(session->media, "Error adding stream.");
 		purple_media_hangup(session->media);
 		google_session_send_terminate(session);
+		g_free(params);
 		return;
 	}
 
+	g_free(params);
+
 	desc_element = xmlnode_get_child(sess, "description");
-	
+
 	for (codec_element = xmlnode_get_child(desc_element, "payload-type"); 
 	     codec_element; 
 	     codec_element = xmlnode_get_next_twin(codec_element)) {
@@ -368,7 +390,7 @@
 			G_CALLBACK(google_session_state_changed_cb), session);
 
 	purple_media_codec_list_free(codecs);
-	
+
 	result = jabber_iq_new(js, JABBER_IQ_RESULT);
 	jabber_iq_set_id(result, xmlnode_get_attrib(packet, "id"));
 	xmlnode_set_attrib(result->node, "to", session->remote_jid);
@@ -1025,3 +1047,105 @@
 	const char *attr = purple_status_get_attr_string(tune, PURPLE_TUNE_TITLE);
 	return attr ? g_strdup_printf("♫ %s", attr) : g_strdup("");
 }
+
+static void
+jabber_google_stun_lookup_cb(GSList *hosts, gpointer data, 
+	const char *error_message)
+{
+	JabberStream *js = (JabberStream *) data;
+
+	if (error_message) {
+		purple_debug_error("jabber", "Google STUN lookup failed: %s\n",
+			error_message);
+		g_slist_free(hosts);
+		return;
+	}
+
+	if (hosts && g_slist_next(hosts)) {
+		struct sockaddr *addr = g_slist_next(hosts)->data; 
+		char dst[INET6_ADDRSTRLEN];
+		int port;
+
+		if (addr->sa_family == AF_INET6) {
+			inet_ntop(addr->sa_family, &((struct sockaddr_in6 *) addr)->sin6_addr, 
+				dst, sizeof(dst));
+			port = ntohs(((struct sockaddr_in6 *) addr)->sin6_port);
+		} else {
+			inet_ntop(addr->sa_family, &((struct sockaddr_in *) addr)->sin_addr, 
+				dst, sizeof(dst));
+			port = ntohs(((struct sockaddr_in *) addr)->sin_port);
+		}
+
+		if (js) {
+			if (js->stun_ip) {
+				g_free(js->stun_ip);
+			}
+			js->stun_ip = g_strdup(dst);
+			purple_debug_info("jabber", "set Google STUN IP address: %s\n", dst);
+			js->stun_port = port;
+			purple_debug_info("jabber", "set Google STUN port: %d\n", port);
+			purple_debug_info("jabber", "set Google STUN port: %d\n", port);
+			/* unmark ongoing query */
+			js->stun_query = NULL;
+		}
+	}
+
+	g_slist_free(hosts);
+}
+
+static void
+jabber_google_jingle_info_cb(JabberStream *js, xmlnode *result,
+	gpointer nullus)
+{	
+	if (result) {
+		const xmlnode *query = 
+			xmlnode_get_child_with_namespace(result, "query", 
+				GOOGLE_JINGLE_INFO_NAMESPACE);
+
+		if (query) {
+			const xmlnode *stun = xmlnode_get_child(query, "stun");
+
+			purple_debug_info("jabber", "got google:jingleinfo\n");
+
+			if (stun) {
+				xmlnode *server = xmlnode_get_child(stun, "server");
+
+				if (server) {
+					const gchar *host = xmlnode_get_attrib(server, "host");
+					const gchar *udp = xmlnode_get_attrib(server, "udp");
+
+					if (host && udp) {
+						int port = atoi(udp);
+						/* if there, would already be an ongoing query, 
+						 cancel it */
+						if (js->stun_query)
+							purple_dnsquery_destroy(js->stun_query);
+
+						js->stun_query = purple_dnsquery_a(host, port, 
+							jabber_google_stun_lookup_cb, js);
+					}
+				}
+			}
+			/* should perhaps handle relays later on, or maybe wait until
+			 Google supports a common standard... */
+		}
+	}
+}
+
+void
+jabber_google_handle_jingle_info(JabberStream *js, xmlnode *packet)
+{
+	jabber_google_jingle_info_cb(js, packet, NULL);
+}
+
+void
+jabber_google_send_jingle_info(JabberStream *js)
+{
+	JabberIq *jingle_info = 
+		jabber_iq_new_query(js, JABBER_IQ_GET, GOOGLE_JINGLE_INFO_NAMESPACE);
+
+	jabber_iq_set_callback(jingle_info, jabber_google_jingle_info_cb,
+		NULL);
+	purple_debug_info("jabber", "sending google:jingleinfo query\n");
+	jabber_iq_send(jingle_info);
+}