# HG changeset patch # User Marcus Lundblad # Date 1233697047 0 # Node ID 88f183f7dfc70f01ea927213a445df99141f7857 # Parent 2b843d38d1f2949795ec4afe17aa94b5989ca805 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 diff -r 2b843d38d1f2 -r 88f183f7dfc7 libpurple/protocols/jabber/disco.c --- 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; diff -r 2b843d38d1f2 -r 88f183f7dfc7 libpurple/protocols/jabber/google.c --- 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(¶m.value, 0, sizeof(GValue)); - g_value_init(¶m.value, G_TYPE_UINT); - g_value_set_uint(¶m.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, ¶m) == 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(¶m.value, 0, sizeof(GValue)); - g_value_init(¶m.value, G_TYPE_UINT); - g_value_set_uint(¶m.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, ¶m) == 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); +} diff -r 2b843d38d1f2 -r 88f183f7dfc7 libpurple/protocols/jabber/google.h --- 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_ */ diff -r 2b843d38d1f2 -r 88f183f7dfc7 libpurple/protocols/jabber/iq.c --- 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) diff -r 2b843d38d1f2 -r 88f183f7dfc7 libpurple/protocols/jabber/jabber.c --- 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; diff -r 2b843d38d1f2 -r 88f183f7dfc7 libpurple/protocols/jabber/jabber.h --- 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); diff -r 2b843d38d1f2 -r 88f183f7dfc7 libpurple/protocols/jabber/jingle/jingle.c --- 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(¶ms[0].value, G_TYPE_STRING); + g_value_set_string(¶ms[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(¶ms[1].value, G_TYPE_UINT); + g_value_set_uint(¶ms[1].value, js->stun_port); + } + + *num = num_params; + return params; +} diff -r 2b843d38d1f2 -r 88f183f7dfc7 libpurple/protocols/jabber/jingle/jingle.h --- 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 diff -r 2b843d38d1f2 -r 88f183f7dfc7 libpurple/protocols/jabber/jingle/rtp.c --- 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;