changeset 26162: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 2b843d38d1f2
children 521366dfd8db
files libpurple/protocols/jabber/disco.c libpurple/protocols/jabber/google.c libpurple/protocols/jabber/google.h libpurple/protocols/jabber/iq.c libpurple/protocols/jabber/jabber.c libpurple/protocols/jabber/jabber.h libpurple/protocols/jabber/jingle/jingle.c libpurple/protocols/jabber/jingle/jingle.h libpurple/protocols/jabber/jingle/rtp.c
diffstat 9 files changed, 223 insertions(+), 24 deletions(-) [+]
line wrap: on
line diff
--- a/libpurple/protocols/jabber/disco.c	Mon Feb 02 11:37:07 2009 +0000
+++ b/libpurple/protocols/jabber/disco.c	Tue Feb 03 21:37:27 2009 +0000
@@ -452,7 +452,12 @@
 		if (!strcmp(name, "Google Talk")) {
 			purple_debug_info("jabber", "Google Talk!\n");
 			js->googletalk = TRUE;
-		}
+
+			/* autodiscover stun and relays */
+			jabber_google_send_jingle_info(js);
+		} else {
+			/* TODO: add external service discovery here... */
+		} 
 	}
 
 	for (child = xmlnode_get_child(query, "feature"); child;
--- 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);
+}
--- a/libpurple/protocols/jabber/google.h	Mon Feb 02 11:37:07 2009 +0000
+++ b/libpurple/protocols/jabber/google.h	Tue Feb 03 21:37:27 2009 +0000
@@ -27,6 +27,8 @@
 #include "jabber.h"
 #include "media.h"
 
+#define GOOGLE_JINGLE_INFO_NAMESPACE "google:jingleinfo"
+
 void jabber_gmail_init(JabberStream *js);
 void jabber_gmail_poke(JabberStream *js, xmlnode *node);
 
@@ -49,5 +51,7 @@
 PurpleMedia *jabber_google_session_initiate(JabberStream *js, const gchar *who, PurpleMediaSessionType type);
 void jabber_google_session_parse(JabberStream *js, xmlnode *node);
 
+void jabber_google_handle_jingle_info(JabberStream *js, xmlnode *packet);
+void jabber_google_send_jingle_info(JabberStream *js);
 
 #endif   /* _PURPLE_GOOGLE_H_ */
--- a/libpurple/protocols/jabber/iq.c	Mon Feb 02 11:37:07 2009 +0000
+++ b/libpurple/protocols/jabber/iq.c	Tue Feb 03 21:37:27 2009 +0000
@@ -448,6 +448,9 @@
 #ifdef USE_VV
 	jabber_iq_register_handler(JINGLE, jingle_parse);
 #endif
+	/* handle Google jingleinfo */
+	jabber_iq_register_handler(GOOGLE_JINGLE_INFO_NAMESPACE, 
+		jabber_google_handle_jingle_info);
 }
 
 void jabber_iq_uninit(void)
--- a/libpurple/protocols/jabber/jabber.c	Mon Feb 02 11:37:07 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Tue Feb 03 21:37:27 2009 +0000
@@ -735,20 +735,24 @@
 	js->sessions = NULL;
 #endif
 
+	js->stun_ip = NULL;
+	js->stun_port = 0;
+	js->stun_query = NULL;
+
 	if(!js->user) {
 		purple_connection_error_reason (gc,
 			PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
 			_("Invalid XMPP ID"));
 		return;
 	}
-	
+
 	if (!js->user->domain || *(js->user->domain) == '\0') {
 		purple_connection_error_reason (gc,
 			PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
 			_("Invalid XMPP ID. Domain must be set."));
 		return;
 	}
-	
+
 	if((my_jb = jabber_buddy_find(js, purple_account_get_username(account), TRUE)))
 		my_jb->subscription |= JABBER_SUB_BOTH;
 
@@ -1222,6 +1226,10 @@
 	server = connect_server[0] ? connect_server : js->user->domain;
 	js->certificate_CN = g_strdup(server);
 
+	js->stun_ip = NULL;
+	js->stun_port = 0;
+	js->stun_query = NULL;
+
 	jabber_stream_set_state(js, JABBER_STREAM_CONNECTING);
 
 	if(purple_account_get_bool(account, "old_ssl", FALSE)) {
@@ -1425,6 +1433,15 @@
 	g_free(js->srv_rec);
 	js->srv_rec = NULL;
 
+	g_free(js->stun_ip);
+	js->stun_ip = NULL;
+
+	/* cancel DNS query for STUN, if one is ongoing */
+	if (js->stun_query) {
+		purple_dnsquery_destroy(js->stun_query);
+		js->stun_query = NULL;
+	}
+		
 	g_free(js);
 
 	gc->proto_data = NULL;
--- a/libpurple/protocols/jabber/jabber.h	Mon Feb 02 11:37:07 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Tue Feb 03 21:37:27 2009 +0000
@@ -58,6 +58,7 @@
 #include "mediamanager.h"
 #include "roomlist.h"
 #include "sslconn.h"
+#include "dnsquery.h"
 
 #include "jutil.h"
 #include "xmlnode.h"
@@ -250,6 +251,12 @@
 #ifdef USE_VV
 	GHashTable *medias;
 #endif
+
+	/* maybe this should only be present when USE_VV? */
+	gchar *stun_ip;
+	int stun_port;
+	PurpleDnsQueryData *stun_query;
+	/* later add stuff to handle TURN relays... */
 };
 
 typedef gboolean (JabberFeatureEnabled)(JabberStream *js, const gchar *shortname, const gchar *namespace);
--- a/libpurple/protocols/jabber/jingle/jingle.c	Mon Feb 02 11:37:07 2009 +0000
+++ b/libpurple/protocols/jabber/jingle/jingle.c	Tue Feb 03 21:37:27 2009 +0000
@@ -20,6 +20,7 @@
  */
 
 #include "internal.h"
+#include "network.h"
 
 #include "content.h"
 #include "debug.h"
@@ -438,3 +439,32 @@
 				jingle_terminate_sessions_gh, NULL);
 }
 
+GParameter *
+jingle_get_params(JabberStream *js, guint *num)
+{
+	/* don't set a STUN server if one is set globally in prefs, in that case
+	 this will be handled in media.c */
+	gboolean has_account_stun = js->stun_ip && !purple_network_get_stun_ip();
+	guint num_params = has_account_stun ? 2 : 0;
+	GParameter *params = NULL;
+
+	if (num_params > 0) {
+		params = g_new0(GParameter, num_params);
+
+		purple_debug_info("jabber", 
+						  "setting param stun-ip for stream using Google auto-config: %s\n",
+						  js->stun_ip);
+		params[0].name = "stun-ip";
+		g_value_init(&params[0].value, G_TYPE_STRING);
+		g_value_set_string(&params[0].value, js->stun_ip);
+		purple_debug_info("jabber", 
+						  "setting param stun-port for stream using Google auto-config: %d\n",
+						  js->stun_port);
+		params[1].name = "stun-port";
+		g_value_init(&params[1].value, G_TYPE_UINT);
+		g_value_set_uint(&params[1].value, js->stun_port);
+	}
+
+	*num = num_params;
+	return params;
+}
--- a/libpurple/protocols/jabber/jingle/jingle.h	Mon Feb 02 11:37:07 2009 +0000
+++ b/libpurple/protocols/jabber/jingle/jingle.h	Tue Feb 03 21:37:27 2009 +0000
@@ -72,6 +72,10 @@
 
 void jingle_terminate_sessions(JabberStream *js);
 
+/* create a GParam array given autoconfigured STUN (and later perhaps TURN).
+	if google_talk is TRUE, set compatability mode to GOOGLE_TALK */
+GParameter *jingle_get_params(JabberStream *js, guint *num_params);
+
 #ifdef __cplusplus
 }
 #endif
--- a/libpurple/protocols/jabber/jingle/rtp.c	Mon Feb 02 11:37:07 2009 +0000
+++ b/libpurple/protocols/jabber/jingle/rtp.c	Tue Feb 03 21:37:27 2009 +0000
@@ -402,6 +402,8 @@
 	gboolean is_audio;
 	PurpleMediaSessionType type;
 	JingleTransport *transport;
+	GParameter *params = NULL;
+	guint num_params;
 
 	/* maybe this create ought to just be in initiate and handle initiate */
 	if (media == NULL)
@@ -436,13 +438,16 @@
 		type = is_audio == TRUE ? PURPLE_MEDIA_RECV_AUDIO
 				: PURPLE_MEDIA_RECV_VIDEO;
 
+	params = 
+		jingle_get_params(jingle_session_get_js(session), &num_params);
 	purple_media_add_stream(media, name, remote_jid,
-			type, transmitter, 0, NULL);
+			type, transmitter, num_params, params);
 
 	g_free(name);
 	g_free(media_type);
 	g_free(remote_jid);
 	g_free(senders);
+	g_free(params);
 	g_object_unref(session);
 
 	return TRUE;