1 /*
2  * GUPnP Simple IGD abstraction
3  *
4  * Copyright 2008 Collabora Ltd.
5  *  @author: Olivier Crete <olivier.crete@collabora.co.uk>
6  * Copyright 2008 Nokia Corp.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
21  */
22 
23 
24 /**
25  * SECTION:gupnp-simple-igd-thread
26  * @short_description: Threaded wrapper for GUPnPSimpleIgd
27  *
28  * This wraps a #GUPnPSimpleIgd into a thread so that it can be used without
29  * having a #GMainLoop running.
30  */
31 
32 
33 #ifdef HAVE_CONFIG_H
34 # include <config.h>
35 #endif
36 
37 #include "gupnp-simple-igd-thread.h"
38 #include "gupnp-simple-igd-priv.h"
39 
40 
41 /**
42  * GUPnPSimpleIgdThreadClass:
43  *
44  * The Raw UDP component transmitter class
45  */
46 
47 struct _GUPnPSimpleIgdThreadClass
48 {
49   GUPnPSimpleIgdClass parent_class;
50 
51   /*virtual functions */
52   /*< private >*/
53 };
54 
55 struct thread_data
56 {
57   gint refcount;
58 
59   GMutex mutex;
60 
61   GMainContext *context;
62   GMainLoop *loop;
63   gboolean all_mappings_deleted;
64 
65   GUPnPSimpleIgdThread *self;
66 };
67 
68 struct _GUPnPSimpleIgdThreadPrivate
69 {
70   GThread *thread;
71   GMainContext *context;
72 
73   /* Protected by mutex  inside thread_data*/
74   gboolean can_dispose;
75   GCond can_dispose_cond;
76 
77   struct thread_data *thread_data;
78 
79   GPtrArray *add_remove_port_datas;
80 };
81 
82 
83 #define GUPNP_SIMPLE_IGD_THREAD_GET_PRIVATE(o)                        \
84   (G_TYPE_INSTANCE_GET_PRIVATE ((o), GUPNP_TYPE_SIMPLE_IGD_THREAD,    \
85    GUPnPSimpleIgdThreadPrivate))
86 
87 #define GUPNP_SIMPLE_IGD_THREAD_LOCK(o) \
88   g_mutex_lock (&(o)->priv->thread_data->mutex)
89 #define GUPNP_SIMPLE_IGD_THREAD_UNLOCK(o) \
90   g_mutex_unlock (&(o)->priv->thread_data->mutex)
91 
92 
93 G_DEFINE_TYPE_WITH_CODE (GUPnPSimpleIgdThread, gupnp_simple_igd_thread,
94     GUPNP_TYPE_SIMPLE_IGD, G_ADD_PRIVATE (GUPnPSimpleIgdThread));
95 
96 static void gupnp_simple_igd_thread_constructed (GObject *object);
97 static GObject *gupnp_simple_igd_thread_constructor (GType type,
98     guint n_props,
99     GObjectConstructParam *props);
100 static void gupnp_simple_igd_thread_dispose (GObject *object);
101 static void gupnp_simple_igd_thread_finalize (GObject *object);
102 
103 static void gupnp_simple_igd_thread_add_port (GUPnPSimpleIgd *self,
104     const gchar *protocol,
105     guint16 external_port,
106     const gchar *local_ip,
107     guint16 local_port,
108     guint32 lease_duration,
109     const gchar *description);
110 static void gupnp_simple_igd_thread_remove_port (GUPnPSimpleIgd *self,
111     const gchar *protocol,
112     guint external_port);
113 static void gupnp_simple_igd_thread_remove_port_local (GUPnPSimpleIgd *self,
114     const gchar *protocol,
115     const gchar *local_ip,
116     guint16 local_port);
117 
118 
119 struct AddRemovePortData {
120   GMutex mutex;
121   GUPnPSimpleIgdThread *self  G_GNUC_MAY_ALIAS; /* protected by mutex */
122   gchar *protocol;
123   guint16 external_port;
124   gchar *local_ip;
125   guint16 local_port;
126   guint32 lease_duration;
127   gchar *description;
128 };
129 
130 
131 static void
gupnp_simple_igd_thread_class_init(GUPnPSimpleIgdThreadClass * klass)132 gupnp_simple_igd_thread_class_init (GUPnPSimpleIgdThreadClass *klass)
133 {
134   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
135   GUPnPSimpleIgdClass *simple_igd_class = GUPNP_SIMPLE_IGD_CLASS (klass);
136 
137   gobject_class->constructed = gupnp_simple_igd_thread_constructed;
138   gobject_class->constructor = gupnp_simple_igd_thread_constructor;
139   gobject_class->dispose = gupnp_simple_igd_thread_dispose;
140   gobject_class->finalize = gupnp_simple_igd_thread_finalize;
141 
142   simple_igd_class->add_port = gupnp_simple_igd_thread_add_port;
143   simple_igd_class->remove_port = gupnp_simple_igd_thread_remove_port;
144   simple_igd_class->remove_port_local =
145       gupnp_simple_igd_thread_remove_port_local;
146 }
147 
148 
149 static void
gupnp_simple_igd_thread_init(GUPnPSimpleIgdThread * self)150 gupnp_simple_igd_thread_init (GUPnPSimpleIgdThread *self)
151 {
152   self->priv = gupnp_simple_igd_thread_get_instance_private (self);
153 
154   self->priv->context = g_main_context_new ();
155   g_cond_init (&self->priv->can_dispose_cond);
156 
157   self->priv->add_remove_port_datas = g_ptr_array_new ();
158 }
159 
160 static gboolean
delete_all_mappings(gpointer user_data)161 delete_all_mappings (gpointer user_data)
162 {
163   GUPnPSimpleIgdThread *self = user_data;
164   gboolean can_dispose;
165 
166   can_dispose = gupnp_simple_igd_delete_all_mappings (GUPNP_SIMPLE_IGD (self));
167 
168   GUPNP_SIMPLE_IGD_THREAD_LOCK (self);
169   self->priv->can_dispose |= can_dispose;
170   self->priv->thread_data->all_mappings_deleted = TRUE;
171   GUPNP_SIMPLE_IGD_THREAD_UNLOCK (self);
172 
173   g_cond_broadcast (&self->priv->can_dispose_cond);
174 
175   return FALSE;
176 }
177 
178 static gboolean
stop_loop(GUPnPSimpleIgdThread * self)179 stop_loop (GUPnPSimpleIgdThread *self)
180 {
181   GUPNP_SIMPLE_IGD_THREAD_LOCK (self);
182   if (self->priv->thread_data->loop)
183     g_main_loop_quit (self->priv->thread_data->loop);
184   GUPNP_SIMPLE_IGD_THREAD_UNLOCK (self);
185 
186   return FALSE;
187 }
188 
189 static void
gupnp_simple_igd_thread_dispose(GObject * object)190 gupnp_simple_igd_thread_dispose (GObject *object)
191 {
192   GUPnPSimpleIgdThread *self = GUPNP_SIMPLE_IGD_THREAD_CAST (object);
193 
194   GUPNP_SIMPLE_IGD_THREAD_LOCK (self);
195   while (self->priv->add_remove_port_datas->len)
196     {
197       struct AddRemovePortData *data =
198           g_ptr_array_remove_index (self->priv->add_remove_port_datas, 0);
199       g_mutex_lock (&data->mutex);
200       data->self = NULL;
201       g_mutex_unlock (&data->mutex);
202     }
203 
204   if (g_thread_self () == self->priv->thread)
205   {
206     GUPNP_SIMPLE_IGD_THREAD_UNLOCK (self);
207 
208     if (!gupnp_simple_igd_delete_all_mappings (GUPNP_SIMPLE_IGD (self)))
209       return;
210 
211     GUPNP_SIMPLE_IGD_THREAD_LOCK (self);
212     if (self->priv->thread_data->loop)
213       g_main_loop_quit (self->priv->thread_data->loop);
214     GUPNP_SIMPLE_IGD_THREAD_UNLOCK (self);
215 
216     if (self->priv->thread_data->loop != NULL) {
217       self->priv->thread_data->self = g_object_ref (self);
218       return;
219     }
220   }
221   else if (self->priv->thread)
222   {
223     GSource *delete_all_src;
224 
225     delete_all_src = g_idle_source_new ();
226     g_source_set_priority (delete_all_src, G_PRIORITY_HIGH);
227     g_source_set_callback (delete_all_src, delete_all_mappings,
228         g_object_ref (self),
229 	g_object_unref);
230     g_source_attach (delete_all_src, self->priv->context);
231     g_source_unref (delete_all_src);
232 
233     while (!self->priv->thread_data->all_mappings_deleted)
234       g_cond_wait (&self->priv->can_dispose_cond,
235           &self->priv->thread_data->mutex);
236 
237     if (!self->priv->can_dispose)
238     {
239       GUPNP_SIMPLE_IGD_THREAD_UNLOCK (self);
240       return;
241     }
242 
243     if (self->priv->thread_data->loop)
244     {
245       GSource *src = g_idle_source_new ();
246 
247       g_source_set_callback (src, (GSourceFunc) stop_loop, self, NULL);
248       g_source_attach (src, self->priv->context);
249       g_source_unref (src);
250 
251       if (self->priv->thread_data->loop)
252         g_main_loop_quit (self->priv->thread_data->loop);
253     }
254     GUPNP_SIMPLE_IGD_THREAD_UNLOCK (self);
255 
256     g_thread_join (self->priv->thread);
257     self->priv->thread = NULL;
258   }
259 
260   G_OBJECT_CLASS (gupnp_simple_igd_thread_parent_class)->dispose (object);
261 }
262 
263 static void
thread_data_dec(struct thread_data * data)264 thread_data_dec (struct thread_data *data)
265 {
266   if (g_atomic_int_dec_and_test (&data->refcount))
267   {
268     g_mutex_clear (&data->mutex);
269     g_main_context_unref (data->context);
270     g_slice_free (struct thread_data, data);
271   }
272 }
273 
274 static void
gupnp_simple_igd_thread_finalize(GObject * object)275 gupnp_simple_igd_thread_finalize (GObject *object)
276 {
277   GUPnPSimpleIgdThread *self = GUPNP_SIMPLE_IGD_THREAD_CAST (object);
278 
279   g_main_context_unref (self->priv->context);
280   g_cond_clear (&self->priv->can_dispose_cond);
281 
282   g_ptr_array_free (self->priv->add_remove_port_datas, TRUE);
283 
284   thread_data_dec (self->priv->thread_data);
285 
286   G_OBJECT_CLASS (gupnp_simple_igd_thread_parent_class)->finalize (object);
287 }
288 
289 static gpointer
thread_func(gpointer dat)290 thread_func (gpointer dat)
291 {
292   struct thread_data *data = dat;
293   GMainLoop *loop = g_main_loop_new (data->context, FALSE);
294 
295   g_main_context_push_thread_default (data->context);
296 
297   g_mutex_lock (&data->mutex);
298   data->loop = loop;
299   g_mutex_unlock (&data->mutex);
300 
301   g_main_loop_run (loop);
302 
303   g_mutex_lock (&data->mutex);
304   data->loop = NULL;
305   data->all_mappings_deleted = TRUE;
306   g_mutex_unlock (&data->mutex);
307 
308   g_main_loop_unref (loop);
309 
310   if (data->self)
311     g_object_unref (data->self);
312 
313   g_main_context_pop_thread_default (data->context);
314   thread_data_dec (data);
315 
316   return NULL;
317 }
318 
319 static GObject *
gupnp_simple_igd_thread_constructor(GType type,guint n_props,GObjectConstructParam * props)320 gupnp_simple_igd_thread_constructor (GType type,
321     guint n_props,
322     GObjectConstructParam *props)
323 {
324   GObject *obj;
325 
326   return G_OBJECT_CLASS (gupnp_simple_igd_thread_parent_class)->constructor (
327       type, n_props, props);
328 
329   return obj;
330 }
331 
332 static void
gupnp_simple_igd_thread_constructed(GObject * object)333 gupnp_simple_igd_thread_constructed (GObject *object)
334 {
335   GUPnPSimpleIgdThread *self = GUPNP_SIMPLE_IGD_THREAD_CAST (object);
336   struct thread_data *data = g_slice_new0 (struct thread_data);
337 
338   g_main_context_push_thread_default (self->priv->context);
339   if (G_OBJECT_CLASS (gupnp_simple_igd_thread_parent_class)->constructed)
340     G_OBJECT_CLASS (gupnp_simple_igd_thread_parent_class)->constructed (object);
341   g_main_context_pop_thread_default (self->priv->context);
342 
343   g_atomic_int_set (&data->refcount, 2);
344 
345   self->priv->thread_data = data;
346 
347   g_mutex_init (&data->mutex);
348   g_main_context_ref (self->priv->context);
349   data->context = self->priv->context;
350 
351   self->priv->thread = g_thread_new ("gupnp-igd-thread", thread_func, data);
352   g_return_if_fail (self->priv->thread);
353 }
354 
355 static gboolean
add_port_idle_func(gpointer user_data)356 add_port_idle_func (gpointer user_data)
357 {
358   struct AddRemovePortData *data = user_data;
359   GUPnPSimpleIgdClass *klass =
360       GUPNP_SIMPLE_IGD_CLASS (gupnp_simple_igd_thread_parent_class);
361   GUPnPSimpleIgdThread *self;
362 
363   g_mutex_lock (&data->mutex);
364   self = data->self;
365   if (self)
366     g_object_ref (self);
367   g_mutex_unlock (&data->mutex);
368   if (!self)
369     return FALSE;
370 
371   if (klass->add_port)
372     klass->add_port (GUPNP_SIMPLE_IGD (self), data->protocol,
373         data->external_port, data->local_ip, data->local_port,
374         data->lease_duration,
375         data->description);
376 
377   g_object_unref (self);
378 
379   return FALSE;
380 }
381 
382 
383 static gboolean
remove_port_idle_func(gpointer user_data)384 remove_port_idle_func (gpointer user_data)
385 {
386   struct AddRemovePortData *data = user_data;
387   GUPnPSimpleIgdClass *klass =
388       GUPNP_SIMPLE_IGD_CLASS (gupnp_simple_igd_thread_parent_class);
389   GUPnPSimpleIgdThread *self;
390 
391   g_mutex_lock (&data->mutex);
392   self = data->self;
393   if (self)
394     g_object_ref (self);
395   g_mutex_unlock (&data->mutex);
396   if (!self)
397     return FALSE;
398 
399   if (klass->remove_port)
400     klass->remove_port (GUPNP_SIMPLE_IGD (self), data->protocol,
401         data->external_port);
402 
403   g_object_unref (self);
404 
405   return FALSE;
406 }
407 
408 static gboolean
remove_port_local_idle_func(gpointer user_data)409 remove_port_local_idle_func (gpointer user_data)
410 {
411   struct AddRemovePortData *data = user_data;
412   GUPnPSimpleIgdClass *klass =
413       GUPNP_SIMPLE_IGD_CLASS (gupnp_simple_igd_thread_parent_class);
414   GUPnPSimpleIgdThread *self;
415 
416   g_mutex_lock (&data->mutex);
417   self = data->self;
418   if (self)
419     g_object_ref (self);
420   g_mutex_unlock (&data->mutex);
421   if (!self)
422     return FALSE;
423 
424   if (klass->remove_port_local)
425     klass->remove_port_local (GUPNP_SIMPLE_IGD (self), data->protocol,
426         data->local_ip, data->local_port);
427 
428   g_object_unref (self);
429 
430   return FALSE;
431 }
432 
433 static void
free_add_remove_port_data(gpointer user_data)434 free_add_remove_port_data (gpointer user_data)
435 {
436   struct AddRemovePortData *data = user_data;
437   GUPnPSimpleIgdThread *self;
438 
439   g_mutex_lock (&data->mutex);
440   self = data->self;
441   data->self = NULL;
442   if (self)
443     g_object_ref (self);
444   g_mutex_unlock (&data->mutex);
445   if (self)
446     {
447       GUPNP_SIMPLE_IGD_THREAD_LOCK (self);
448       g_ptr_array_remove_fast (self->priv->add_remove_port_datas, data);
449       GUPNP_SIMPLE_IGD_THREAD_UNLOCK (self);
450       g_object_unref (self);
451     }
452 
453   g_free (data->protocol);
454   g_free (data->local_ip);
455   g_free (data->description);
456 
457   g_mutex_clear (&data->mutex);
458 
459   g_slice_free (struct AddRemovePortData, data);
460 }
461 
462 static void
gupnp_simple_igd_thread_add_port(GUPnPSimpleIgd * self,const gchar * protocol,guint16 external_port,const gchar * local_ip,guint16 local_port,guint32 lease_duration,const gchar * description)463 gupnp_simple_igd_thread_add_port (GUPnPSimpleIgd *self,
464     const gchar *protocol,
465     guint16 external_port,
466     const gchar *local_ip,
467     guint16 local_port,
468     guint32 lease_duration,
469     const gchar *description)
470 {
471   GUPnPSimpleIgdThread *realself = GUPNP_SIMPLE_IGD_THREAD (self);
472   struct AddRemovePortData *data = g_slice_new0 (struct AddRemovePortData);
473   GSource *source;
474 
475   g_mutex_init (&data->mutex);
476   data->self = realself;
477   data->protocol = g_strdup (protocol);
478   data->external_port = external_port;
479   data->local_ip = g_strdup (local_ip);
480   data->local_port = local_port;
481   data->lease_duration = lease_duration;
482   data->description = g_strdup (description);
483   GUPNP_SIMPLE_IGD_THREAD_LOCK (realself);
484   g_ptr_array_add (realself->priv->add_remove_port_datas, data);
485   GUPNP_SIMPLE_IGD_THREAD_UNLOCK (realself);
486 
487   source = g_idle_source_new ();
488   g_source_set_callback (source, add_port_idle_func, data,
489       free_add_remove_port_data);
490   g_source_set_priority (source, G_PRIORITY_DEFAULT);
491   g_source_attach (source, realself->priv->context);
492   g_source_unref (source);
493   g_main_context_wakeup (realself->priv->context);
494 }
495 
496 static void
gupnp_simple_igd_thread_remove_port(GUPnPSimpleIgd * self,const gchar * protocol,guint external_port)497 gupnp_simple_igd_thread_remove_port (GUPnPSimpleIgd *self,
498     const gchar *protocol,
499     guint external_port)
500 {
501   GUPnPSimpleIgdThread *realself = GUPNP_SIMPLE_IGD_THREAD (self);
502   struct AddRemovePortData *data = g_slice_new0 (struct AddRemovePortData);
503   GSource *source;
504 
505   g_mutex_init (&data->mutex);
506   data->self = realself;
507   data->protocol = g_strdup (protocol);
508   data->external_port = external_port;
509   GUPNP_SIMPLE_IGD_THREAD_LOCK (realself);
510   g_ptr_array_add (realself->priv->add_remove_port_datas, data);
511   GUPNP_SIMPLE_IGD_THREAD_UNLOCK (realself);
512 
513   source = g_idle_source_new ();
514   g_source_set_callback (source, remove_port_idle_func, data,
515       free_add_remove_port_data);
516   g_source_set_priority (source, G_PRIORITY_DEFAULT);
517   g_source_attach (source, realself->priv->context);
518   g_source_unref (source);
519   g_main_context_wakeup (realself->priv->context);
520 }
521 
522 static void
gupnp_simple_igd_thread_remove_port_local(GUPnPSimpleIgd * self,const gchar * protocol,const gchar * local_ip,guint16 local_port)523 gupnp_simple_igd_thread_remove_port_local (GUPnPSimpleIgd *self,
524     const gchar *protocol,
525     const gchar *local_ip,
526     guint16 local_port)
527 {
528   GUPnPSimpleIgdThread *realself = GUPNP_SIMPLE_IGD_THREAD (self);
529   struct AddRemovePortData *data = g_slice_new0 (struct AddRemovePortData);
530   GSource *source;
531 
532   g_mutex_init (&data->mutex);
533   data->self = realself;
534   data->protocol = g_strdup (protocol);
535   data->local_ip = g_strdup (local_ip);
536   data->local_port = local_port;
537   GUPNP_SIMPLE_IGD_THREAD_LOCK (realself);
538   g_ptr_array_add (realself->priv->add_remove_port_datas, data);
539   GUPNP_SIMPLE_IGD_THREAD_UNLOCK (realself);
540 
541   source = g_idle_source_new ();
542   g_source_set_callback (source, remove_port_local_idle_func, data,
543       free_add_remove_port_data);
544   g_source_set_priority (source, G_PRIORITY_DEFAULT);
545   g_source_attach (source, realself->priv->context);
546   g_source_unref (source);
547   g_main_context_wakeup (realself->priv->context);
548 }
549 
550 /**
551  * gupnp_simple_igd_thread_new:
552  *
553  * Creates a new #GUPnPSimpleIgdThread
554  *
555  * Returns: the new #GUPnPSimpleIgdThread
556  */
557 
558 GUPnPSimpleIgdThread *
gupnp_simple_igd_thread_new()559 gupnp_simple_igd_thread_new ()
560 {
561   return g_object_new (GUPNP_TYPE_SIMPLE_IGD_THREAD, NULL);
562 }
563