S5B: Add functions to connect to a candidate
authorNicolas Cornu <nicolas.cornu@ensi-bourges.fr>
Mon, 30 Aug 2010 18:03:48 +0200
changeset 164 6866328b34bd
parent 163 a4c75fe75869
child 165 763c26abd99d
S5B: Add functions to connect to a candidate
jingle-s5b/socks5.c
jingle-s5b/socks5.h
jingle/register.c
jingle/sessions.c
--- a/jingle-s5b/socks5.c	Fri Aug 27 11:19:32 2010 +0200
+++ b/jingle-s5b/socks5.c	Mon Aug 30 18:03:48 2010 +0200
@@ -48,13 +48,17 @@
                                  LmMessageNode *node, GError **err);
 static void tomessage(gconstpointer data, LmMessageNode *node);
 static gconstpointer new(void);
-// static void _send(session_content *sc, gconstpointer data, gchar *buf, gsize size);
+static void _send(session_content *sc, gconstpointer data, gchar *buf, gsize size);
 static void init(session_content *sc, gconstpointer data);
 static void end(session_content *sc, gconstpointer data);
 static gchar *info(gconstpointer data);
 
+static void connect_candidate(JingleS5B *js5b, S5BCandidate *cand);
+static void connect_next_candidate(JingleS5B *js5b, S5BCandidate *cand);
 static void
-handle_listener_accept(GObject *_listener, GAsyncResult *res, gpointer userdata);
+handle_listener_accept(GObject *_listener, GAsyncResult *res, gpointer data);
+static void
+handle_client_connect(GObject *_client, GAsyncResult *res, gpointer data);
 static void handle_sock_io(GSocket *sock, GIOCondition cond, gpointer data);
 static GSList *get_all_local_ips();
 static gchar *gen_random_sid(void);
@@ -70,7 +74,7 @@
   .handle         = handle,
   .tomessage      = tomessage,
   .new            = new,
-  .send           = NULL,
+  .send           = _send,
   .init           = init,
   .end            = end,
   .info           = info
@@ -205,7 +209,7 @@
   // TODO: find a way to make sure the port is not already used
   guint64 portstart, portend;
   guint16 port;
-  const gchar *port_range = settings_opt_get("jingle_s5b_portrange");
+  const gchar *port_range = settings_opt_get("js5b_portrange");
 
   if (port_range != NULL) {
     sscanf(port_range, "%" G_GUINT64_FORMAT "-%" G_GUINT64_FORMAT, &portstart, &portend);
@@ -320,47 +324,47 @@
 {
   JingleS5B *js5b = (JingleS5B *)data;
   GSocketAddress *saddr;
-  //GSource *socksource;
   guint numlistening = 0; // number of addresses we are listening to
   GSList *entry;
   GError *err = NULL;
+  GSocket *sock;
 
   // First, we listen on all ips
   js5b->listener = g_socket_listener_new();
   for (entry = js5b->ourcandidates; entry; entry = entry->next) {
     S5BCandidate *cand = (S5BCandidate *)entry->data;
 
-    cand->sock = g_socket_new(g_inet_address_get_family(cand->host),
-                              G_SOCKET_TYPE_STREAM,
-                              G_SOCKET_PROTOCOL_TCP, &err);
-    if (cand->sock == NULL) {
+    sock = g_socket_new(g_inet_address_get_family(cand->host),
+                        G_SOCKET_TYPE_STREAM,
+                        G_SOCKET_PROTOCOL_TCP, &err);
+    if (sock == NULL) {
       scr_LogPrint(LPRINT_LOGNORM, "Jingle S5B: Error while creating a new socket: %s",
-                   err->message != NULL ? err->message : "(no message)");
+                   err->message ? err->message : "(no message)");
       continue;
     }
-    g_socket_set_blocking(cand->sock, FALSE);
+    g_socket_set_blocking(sock, FALSE);
 
     saddr = g_inet_socket_address_new(cand->host, cand->port);
-    if (!g_socket_bind(cand->sock, saddr, TRUE, &err)) {
+    if (!g_socket_bind(sock, saddr, TRUE, &err)) {
       scr_LogPrint(LPRINT_LOGNORM, "Jingle S5B: Unable to bind a socket on %s port %u: %s",
                    g_inet_address_to_string(cand->host),
                    cand->port,
-                   err->message != NULL ? err->message : "(no message)");
+                   err->message ? err->message : "(no message)");
       goto cleancontinue;
     }
 
-    if (!g_socket_listen(cand->sock, &err)) {
+    if (!g_socket_listen(sock, &err)) {
       scr_LogPrint(LPRINT_LOGNORM, "Jingle S5B: Unable to listen on %s port %u: %s",
                    g_inet_address_to_string(cand->host),
                    cand->port,
-                   err->message != NULL ? err->message : "(no message)");
+                   err->message ? err->message : "(no message)");
       goto cleancontinue;
     }
 
-    if (!g_socket_listener_add_socket(js5b->listener, cand->sock, NULL, &err)) {
+    if (!g_socket_listener_add_socket(js5b->listener, sock, NULL, &err)) {
       scr_LogPrint(LPRINT_LOGNORM, "Jingle S5B: Unable to add our socket to the"
                    " GSocketListener: %s",
-                   err->message != NULL ? err->message : "(no message)");
+                   err->message ? err->message : "(no message)");
       goto cleancontinue;
 	}
 
@@ -370,8 +374,9 @@
 	++numlistening;
 
 cleancontinue:
+      if (err != NULL) g_clear_error(&err);
       g_object_unref(saddr);
-      g_object_unref(cand->sock);
+      g_object_unref(sock);
   }
 
   if (numlistening > 0) {
@@ -381,6 +386,67 @@
   }
 
   // Then, we start connecting to the other entity's candidates, if any.
+  if (js5b->candidates) {
+    js5b->client = g_socket_client_new();
+    S5BCandidate *cand = (S5BCandidate *)js5b->candidates->data;
+    connect_candidate(js5b, cand);
+  }
+}
+
+/**
+ * @brief Cancel an ongoing connection after 5 seconds
+ * @param data  A GPtrArray
+ * 
+ * "A client SHOULD NOT wait for a TCP timeout on connect.
+ * If it is unable to connect to any candidate within 5 seconds
+ * it SHOULD send a candidate-error to the other party."
+ */
+static gboolean connect_cancel_timeout(gpointer data)
+{
+  GPtrArray *args = (GPtrArray *)data;
+  JingleS5B *js5b = g_ptr_array_index(args, 0);
+  //S5BCandidate *cand = g_ptr_array_index(args, 1);
+  g_ptr_array_unref(args);
+
+  g_cancellable_cancel(js5b->cancelconnect);
+  return FALSE;
+}
+
+static void connect_candidate(JingleS5B *js5b, S5BCandidate *cand)
+{
+  GSocketAddress *saddr;
+  GPtrArray *args;
+  guint eventid;
+
+  args = g_ptr_array_sized_new(2);
+  g_ptr_array_add(args, js5b);
+  g_ptr_array_add(args, cand);
+
+  saddr = g_inet_socket_address_new(cand->host, cand->port);
+  js5b->cancelconnect = g_cancellable_new();
+
+  eventid = g_timeout_add_seconds(5, connect_cancel_timeout, g_ptr_array_ref(args));
+  g_ptr_array_add(args, GUINT_TO_POINTER(eventid));
+  g_socket_client_connect_async(js5b->client, G_SOCKET_CONNECTABLE(saddr),
+                                js5b->cancelconnect, handle_client_connect, args);
+  g_object_unref(saddr);
+}
+
+/**
+ * Convenience function that find the next candidate to try and
+ * call connect_candidate
+ */
+static void connect_next_candidate(JingleS5B *js5b, S5BCandidate *cand)
+{
+  GSList *link = g_slist_find(js5b->candidates, cand);
+  if (js5b->cancelconnect != NULL)
+    g_object_unref(js5b->cancelconnect);
+  g_assert(link != NULL);
+  if (link->next == NULL) {
+    // there is no next candidate to try.
+  }
+  connect_candidate(js5b, (S5BCandidate *)link->next->data);
+  return;
 }
 
 static gchar *info(gconstpointer data)
@@ -394,16 +460,65 @@
   return;
 }
 
-
 /**
  * @brief Handle incoming connections
  */
 static void
-handle_listener_accept(GObject *_listener, GAsyncResult *res, gpointer userdata)
+handle_listener_accept(GObject *_listener, GAsyncResult *res, gpointer data)
+{
+  GError *err = NULL;
+  GSocketConnection *conn;
+  //scr_LogPrint(LPRINT_LOGNORM, "Jingle S5B: Got Incoming Connection");
+  conn = g_socket_listener_accept_finish(G_SOCKET_LISTENER(_listener), res, NULL, &err);
+}
+
+/**
+ * @brief Handle outgoing connections
+ */
+static void
+handle_client_connect(GObject *_client, GAsyncResult *res, gpointer data)
 {
   GError *err = NULL;
   GSocketConnection *conn;
-  conn = g_socket_listener_accept_finish((GSocketListener *) _listener, res, NULL, &err);
+  GPtrArray *args;
+  JingleS5B *js5b;
+  S5BCandidate *cand;
+  //scr_LogPrint(LPRINT_LOGNORM, "Jingle S5B: Got Outgoing Connection");
+  
+  args = (GPtrArray *)data;
+  js5b = g_ptr_array_index(args, 0);
+  cand = g_ptr_array_index(args, 1);
+  g_ptr_array_unref(args);
+
+  conn = g_socket_client_connect_finish(G_SOCKET_CLIENT(_client), res, &err);
+
+  if (!g_error_matches(err, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+    // if we did not received a CANCELLED error, then the time limit was not
+    // reached and we need to clean up the GSource and unref args a second time.
+    guint eventid = GPOINTER_TO_UINT(g_ptr_array_index(args, 2));
+    GSource *s = g_main_context_find_source_by_id(NULL, eventid);
+    g_source_destroy(s);
+    g_ptr_array_unref(args);
+    connect_next_candidate(js5b, cand);
+    // we need to send a candidate-error in case we cannot connect.
+    return;
+  }
+
+  if (err != NULL) {
+    if (err->domain == G_IO_ERROR)
+      scr_LogPrint(LPRINT_DEBUG, "Jingle S5B: IO Error (%s)",
+                   err->message ? err->message : "no message");
+    else
+      scr_LogPrint(LPRINT_DEBUG, "Jingle S5B: %s Error (%s)",
+                   g_quark_to_string (err->domain),
+                   err->message ? err->message : "no message");
+
+    g_error_free(err);
+    connect_next_candidate(js5b, cand);
+    return;
+  }
+  js5b->connection = conn;
+  // we have a valid connection
 }
 
 /**
@@ -426,6 +541,11 @@
   }
 }
 
+static void _send(session_content *sc, gconstpointer data, gchar *buf, gsize size)
+{
+  return;
+}
+
 /**
  * @brief Discover all IPs of this computer
  * @return A linked list of GInetAddress
@@ -440,15 +560,34 @@
   const guint8 *addrdata;
   guint16 ifacecounter = 0; // for lack of a better method
   LocalIP *candidate;
+  gchar **ifblacklist;
+  guint ifblkcnt;
 
   gint rval = getifaddrs(&first);
-  if (rval != 0)
+  if (rval != 0) {
+    scr_LogPrint(LPRINT_LOGNORM, "Jingle S5B: Unable to retreive local ip addresses");
     return NULL;
+  }
+
+  if (settings_opt_get("js5b_iface_blacklist") != NULL) {
+    ifblacklist = g_strsplit(settings_opt_get("js5b_iface_blacklist"), ",", 0);
+  } else {
+    ifblacklist = (gchar*[]){NULL};
+  }
 
   for (ifaddr = first; ifaddr; ifaddr = ifaddr->ifa_next) {
+    gboolean continueloop = FALSE;
     if (!(ifaddr->ifa_flags & IFF_UP) || ifaddr->ifa_flags & IFF_LOOPBACK)
       continue;
 
+    for (ifblkcnt = 0; ifblacklist[ifblkcnt]; ifblkcnt++)
+      if (!g_strcmp0(ifaddr->ifa_name, ifblacklist[ifblkcnt])) {
+        continueloop = TRUE;
+        break;
+      }
+    
+    if (continueloop) continue;
+
     if (ifaddr->ifa_addr->sa_family == AF_INET) {
       native = (struct sockaddr_in *)ifaddr->ifa_addr;
       addrdata = (const guint8 *)&native->sin_addr.s_addr;
--- a/jingle-s5b/socks5.h	Fri Aug 27 11:19:32 2010 +0200
+++ b/jingle-s5b/socks5.h	Mon Aug 30 18:03:48 2010 +0200
@@ -24,18 +24,23 @@
 
   const gchar *sid;
 
-  GSocketConnection *sock;
+  GSocketConnection *connection;
+
+  GCancellable *cancelconnect;
 
   GSocketListener *listener;
 
+  GSocketClient *client;
+
   /**
-   * This is the list of the other client's candidates.
+   * @brief This is the list of the other client's candidates
+   * 
+   * It should always be priority ordered.
    */
   GSList *candidates;
 
   /**
-   * This is our list of candidates, the one we sent during a
-   * session-initiate or session-accept.
+   * @brief This is our list of candidates
    */
   GSList *ourcandidates;
 } JingleS5B;
@@ -52,8 +57,6 @@
   guint64 priority;
 
   JingleS5BType type;
-
-  GSocket *sock;
 } S5BCandidate;
 
 #endif
--- a/jingle/register.c	Fri Aug 27 11:19:32 2010 +0200
+++ b/jingle/register.c	Mon Aug 30 18:03:48 2010 +0200
@@ -109,7 +109,7 @@
  * Determine which transport is better suited for a given app.
  */
 const gchar *jingle_transport_for_app(const gchar *appxmlns,
-                                               GSList **forbid)
+                                      GSList **forbid)
 {
   AppHandlerEntry *app = jingle_find_app(appxmlns);
   GSList *entry;
--- a/jingle/sessions.c	Fri Aug 27 11:19:32 2010 +0200
+++ b/jingle/sessions.c	Mon Aug 30 18:03:48 2010 +0200
@@ -405,7 +405,7 @@
   JingleSession *sess = session_new(sid, myjid, recipientjid, JINGLE_SESSION_OUTGOING);
   const gchar **el1 = ns;
   gconstpointer *data1 = datas;
-  int i;
+  gint i;
   
   for (i = 0; names[i]; ++i) {
     session_add_content(sess, names[i], JINGLE_SESSION_STATE_PENDING);