dundee: add timeout to close stalled ppp handshake
[platform/upstream/ofono.git] / dundee / device.c
1 /*
2  *  oFono - Open Source Telephony
3  *
4  *  Copyright (C) 2008-2012  Intel Corporation. All rights reserved.
5  *  Copyright (C) 2012  BMW Car IT GmbH. All rights reserved.
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License version 2 as
9  *  published by the Free Software Foundation.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <errno.h>
27 #include <string.h>
28 #include <stdio.h>
29 #include <netinet/ether.h>
30
31 #include <glib.h>
32 #include <gdbus.h>
33 #include <gatchat.h>
34 #include <gatppp.h>
35
36 #include "dundee.h"
37
38 #define PPP_TIMEOUT 15
39
40 static int next_device_id = 0;
41 static GHashTable *device_hash;
42
43 static const char *none_prefix[] = { NULL };
44
45 struct ipv4_settings {
46         char *interface;
47         char *ip;
48         char **nameservers;
49 };
50
51 struct dundee_device {
52         char *path;
53         struct dundee_device_driver *driver;
54         gboolean registered;
55
56         GAtPPP *ppp;
57         GAtChat *chat;
58
59         char *name;
60         gboolean active;
61         struct ipv4_settings settings;
62
63         DBusMessage *pending;
64         guint connect_timeout;
65         void *data;
66 };
67
68 const char *__dundee_device_get_path(struct dundee_device *device)
69 {
70         return device->path;
71 }
72
73 static void settings_append(struct dundee_device *device,
74                                         DBusMessageIter *iter)
75 {
76         DBusMessageIter variant;
77         DBusMessageIter array;
78         char typesig[5];
79         char arraysig[6];
80
81         arraysig[0] = DBUS_TYPE_ARRAY;
82         arraysig[1] = typesig[0] = DBUS_DICT_ENTRY_BEGIN_CHAR;
83         arraysig[2] = typesig[1] = DBUS_TYPE_STRING;
84         arraysig[3] = typesig[2] = DBUS_TYPE_VARIANT;
85         arraysig[4] = typesig[3] = DBUS_DICT_ENTRY_END_CHAR;
86         arraysig[5] = typesig[4] = '\0';
87
88         dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT,
89                                                 arraysig, &variant);
90
91         dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY,
92                                                 typesig, &array);
93
94         if (device->active == FALSE)
95                 goto out;
96
97         if (device->settings.interface)
98                 ofono_dbus_dict_append(&array, "Interface",
99                                 DBUS_TYPE_STRING, &device->settings.interface);
100
101         if (device->settings.ip)
102                 ofono_dbus_dict_append(&array, "Address", DBUS_TYPE_STRING,
103                                         &device->settings.ip);
104
105         if (device->settings.nameservers)
106                 ofono_dbus_dict_append_array(&array, "DomainNameServers",
107                                                 DBUS_TYPE_STRING,
108                                                 &device->settings.nameservers);
109
110 out:
111         dbus_message_iter_close_container(&variant, &array);
112
113         dbus_message_iter_close_container(iter, &variant);
114 }
115
116 static void settings_append_dict(struct dundee_device *device,
117                                 DBusMessageIter *dict)
118 {
119         DBusMessageIter entry;
120         const char *key = "Settings";
121
122         dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY,
123                                                 NULL, &entry);
124
125         dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key);
126
127         settings_append(device, &entry);
128
129         dbus_message_iter_close_container(dict, &entry);
130 }
131
132 void __dundee_device_append_properties(struct dundee_device *device,
133                                         DBusMessageIter *dict)
134 {
135         settings_append_dict(device, dict);
136
137         ofono_dbus_dict_append(dict, "Name", DBUS_TYPE_STRING,
138                                 &device->name);
139
140         ofono_dbus_dict_append(dict, "Active", DBUS_TYPE_BOOLEAN,
141                                 &device->active);
142 }
143
144 void __dundee_device_foreach(dundee_device_foreach_func func, void *userdata)
145 {
146         GHashTableIter iter;
147         gpointer key, value;
148
149         DBG("");
150
151         g_hash_table_iter_init(&iter, device_hash);
152
153         while (g_hash_table_iter_next(&iter, &key, &value) == TRUE) {
154                 struct dundee_device *device = value;
155
156                 func(device, userdata);
157         }
158 }
159
160 static void settings_changed(struct dundee_device *device)
161 {
162         DBusConnection *conn = ofono_dbus_get_connection();
163         DBusMessage *signal;
164         DBusMessageIter iter;
165         const char *key = "Settings";
166
167         signal = dbus_message_new_signal(device->path,
168                                         DUNDEE_DEVICE_INTERFACE,
169                                         "PropertyChanged");
170
171         if (signal == NULL)
172                 return;
173         dbus_message_iter_init_append(signal, &iter);
174         dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &key);
175
176         settings_append(device, &iter);
177
178         g_dbus_send_message(conn, signal);
179 }
180
181 static DBusMessage *device_get_properties(DBusConnection *conn,
182                                         DBusMessage *msg, void *data)
183 {
184         struct dundee_device *device = data;
185         DBusMessage *reply;
186         DBusMessageIter iter;
187         DBusMessageIter dict;
188
189         reply = dbus_message_new_method_return(msg);
190         if (reply == NULL)
191                 return NULL;
192
193         dbus_message_iter_init_append(reply, &iter);
194
195         dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
196                                         OFONO_PROPERTIES_ARRAY_SIGNATURE,
197                                         &dict);
198
199         __dundee_device_append_properties(device, &dict);
200
201         dbus_message_iter_close_container(&iter, &dict);
202
203         return reply;
204 }
205
206
207 static void debug(const char *str, void *data)
208 {
209         DBG("%s: %s\n", (const char *) data, str);
210 }
211
212 static void ppp_connect(const char *iface, const char *local, const char *peer,
213                         const char *dns1, const char *dns2,
214                         gpointer user_data)
215 {
216         DBusConnection *conn = ofono_dbus_get_connection();
217         struct dundee_device *device = user_data;
218         const char *dns[3] = { dns1, dns2, 0 };
219
220         DBG("%p", device);
221         DBG("Network Device: %s\n", iface);
222         DBG("IP Address: %s\n", local);
223         DBG("Peer IP Address: %s\n", peer);
224         DBG("Primary DNS Server: %s\n", dns1);
225         DBG("Secondary DNS Server: %s\n", dns2);
226
227         if (device->connect_timeout > 0) {
228                 g_source_remove(device->connect_timeout);
229                 device->connect_timeout = 0;
230         }
231
232         g_free(device->settings.interface);
233         device->settings.interface = g_strdup(iface);
234         if (device->settings.interface == NULL)
235                 goto err;
236
237         g_free(device->settings.ip);
238         device->settings.ip = g_strdup(local);
239         if (device->settings.ip == NULL)
240                 goto err;
241
242         g_strfreev(device->settings.nameservers);
243         device->settings.nameservers = g_strdupv((gchar **)dns);
244         if (device->settings.nameservers == NULL)
245                 goto err;
246
247         __ofono_dbus_pending_reply(&device->pending,
248                         dbus_message_new_method_return(device->pending));
249         device->pending = NULL;
250
251         device->active = TRUE;
252
253         settings_changed(device);
254         ofono_dbus_signal_property_changed(conn, device->path,
255                                         DUNDEE_DEVICE_INTERFACE, "Active",
256                                         DBUS_TYPE_BOOLEAN, &device->active);
257
258         return;
259
260 err:
261         g_free(device->settings.interface);
262         g_free(device->settings.ip);
263         g_strfreev(device->settings.nameservers);
264         device->settings.interface = NULL;
265         device->settings.ip = NULL;
266         device->settings.nameservers = NULL;
267
268         __ofono_dbus_pending_reply(&device->pending,
269                                 __dundee_error_failed(device->pending));
270         device->pending = NULL;
271 }
272
273 static void disconnect_callback(const struct dundee_error *error, void *data)
274 {
275         struct dundee_device *device = data;
276
277         DBG("%p", device);
278
279         g_at_chat_unref(device->chat);
280         device->chat = NULL;
281
282         if (device->pending == NULL)
283                 return;
284
285         if (error->type != DUNDEE_ERROR_TYPE_NO_ERROR) {
286                 __ofono_dbus_pending_reply(&device->pending,
287                                         __dundee_error_failed(device->pending));
288                 goto out;
289         }
290
291         __ofono_dbus_pending_reply(&device->pending,
292                 dbus_message_new_method_return(device->pending));
293
294 out:
295         device->pending = NULL;
296 }
297
298 static gboolean ppp_connect_timeout(gpointer user_data)
299 {
300         struct dundee_device *device = user_data;
301
302         if (device->pending != NULL) {
303                 __ofono_dbus_pending_reply(&device->pending,
304                                 __dundee_error_timed_out(device->pending));
305                 device->pending = NULL;
306         }
307
308         device->driver->disconnect(device, disconnect_callback, device);
309
310         device->connect_timeout = 0;
311
312         return FALSE;
313 }
314
315 static void ppp_disconnect(GAtPPPDisconnectReason reason, gpointer user_data)
316 {
317         DBusConnection *conn = ofono_dbus_get_connection();
318         struct dundee_device *device = user_data;
319
320         DBG("%p", device);
321         DBG("PPP Link down: %d\n", reason);
322
323         g_at_ppp_unref(device->ppp);
324         device->ppp = NULL;
325
326         g_at_chat_resume(device->chat);
327
328         g_free(device->settings.interface);
329         g_free(device->settings.ip);
330         g_strfreev(device->settings.nameservers);
331         device->settings.interface = NULL;
332         device->settings.ip = NULL;
333         device->settings.nameservers = NULL;
334
335         device->active = FALSE;
336
337         settings_changed(device);
338         ofono_dbus_signal_property_changed(conn, device->path,
339                                         DUNDEE_DEVICE_INTERFACE, "Active",
340                                         DBUS_TYPE_BOOLEAN, &device->active);
341
342         device->driver->disconnect(device, disconnect_callback, device);
343 }
344
345 static void dial_cb(gboolean ok, GAtResult *result, gpointer user_data)
346 {
347         struct dundee_device *device = user_data;
348         GAtIO *io;
349
350         if (!ok) {
351                 DBG("Unable to define context\n");
352                 goto err;
353         }
354
355         /* get the data IO channel */
356         io = g_at_chat_get_io(device->chat);
357
358         /*
359          * shutdown gatchat or else it tries to take all the input
360          * from the modem and does not let PPP get it.
361          */
362         g_at_chat_suspend(device->chat);
363
364         /* open ppp */
365         device->ppp = g_at_ppp_new();
366         if (device->ppp == NULL) {
367                 DBG("Unable to create PPP object\n");
368                 goto err;
369         }
370         g_at_ppp_set_debug(device->ppp, debug, "PPP");
371
372         device->connect_timeout = g_timeout_add_seconds(PPP_TIMEOUT,
373                                                 ppp_connect_timeout, device);
374
375         /* set connect and disconnect callbacks */
376         g_at_ppp_set_connect_function(device->ppp, ppp_connect, device);
377         g_at_ppp_set_disconnect_function(device->ppp, ppp_disconnect, device);
378
379         /* open the ppp connection */
380         g_at_ppp_open(device->ppp, io);
381
382         return;
383
384 err:
385         __ofono_dbus_pending_reply(&device->pending,
386                                 __dundee_error_failed(device->pending));
387         device->pending = NULL;
388 }
389
390 static int device_dial_setup(struct dundee_device *device, int fd)
391 {
392         GAtSyntax *syntax;
393         GIOChannel *io;
394
395         io = g_io_channel_unix_new(fd);
396         if (io == NULL)
397                 return -EIO;
398
399         syntax = g_at_syntax_new_gsm_permissive();
400         device->chat = g_at_chat_new(io, syntax);
401         g_io_channel_unref(io);
402         g_at_syntax_unref(syntax);
403
404         if (device->chat == NULL)
405                 return -EIO;
406
407         g_at_chat_set_debug(device->chat, debug, "Control");
408
409         g_at_chat_send(device->chat, "ATD*99#", none_prefix, dial_cb,
410                         device, NULL);
411
412         return 0;
413 }
414
415 static void connect_callback(const struct dundee_error *error,
416                                 int fd, void *data)
417 {
418         struct dundee_device *device = data;
419         int err;
420
421         DBG("%p", device);
422
423         if (error->type != DUNDEE_ERROR_TYPE_NO_ERROR)
424                 goto err;
425
426         err = device_dial_setup(device, fd);
427         if (err < 0)
428                 goto err;
429
430         return;
431
432 err:
433         __ofono_dbus_pending_reply(&device->pending,
434                                 __dundee_error_failed(device->pending));
435         device->pending = NULL;
436 }
437
438 static DBusMessage *set_property_active(struct dundee_device *device,
439                                         DBusMessage *msg,
440                                         DBusMessageIter *var)
441 {
442         ofono_bool_t active;
443
444         DBG("%p path %s", device, device->path);
445
446         if (dbus_message_iter_get_arg_type(var) != DBUS_TYPE_BOOLEAN)
447                 return __dundee_error_invalid_args(msg);
448
449         dbus_message_iter_get_basic(var, &active);
450
451         device->pending = dbus_message_ref(msg);
452
453         if (active)
454                 device->driver->connect(device, connect_callback, device);
455         else if (device->ppp)
456                 g_at_ppp_shutdown(device->ppp);
457
458         return NULL;
459 }
460
461 static DBusMessage *device_set_property(DBusConnection *conn,
462                                         DBusMessage *msg, void *data)
463 {
464         struct dundee_device *device = data;
465         DBusMessageIter iter, var;
466         const char *name;
467
468         if (dbus_message_iter_init(msg, &iter) == FALSE)
469                 return __dundee_error_invalid_args(msg);
470
471         if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
472                 return __dundee_error_invalid_args(msg);
473
474         dbus_message_iter_get_basic(&iter, &name);
475         dbus_message_iter_next(&iter);
476
477         if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)
478                 return __dundee_error_invalid_args(msg);
479
480         dbus_message_iter_recurse(&iter, &var);
481
482         if (g_str_equal(name, "Active"))
483                 return set_property_active(device, msg, &var);
484
485         return __dundee_error_invalid_args(msg);
486 }
487
488 static const GDBusMethodTable device_methods[] = {
489         { GDBUS_METHOD("GetProperties",
490                         NULL, GDBUS_ARGS({ "properties", "a{sv}" }),
491                         device_get_properties) },
492         { GDBUS_ASYNC_METHOD("SetProperty",
493                         GDBUS_ARGS({ "property", "s" }, { "value", "v" }),
494                         NULL, device_set_property) },
495         { }
496 };
497
498 static const GDBusSignalTable device_signals[] = {
499         { GDBUS_SIGNAL("PropertyChanged",
500                         GDBUS_ARGS({ "name", "s" }, { "value", "v" })) },
501         { }
502 };
503
504 static int register_device(struct dundee_device *device)
505 {
506         DBusConnection *conn = ofono_dbus_get_connection();
507         DBusMessage *signal;
508         DBusMessageIter iter;
509         DBusMessageIter dict;
510
511         DBG("%p path %s", device, device->path);
512
513         if (!g_dbus_register_interface(conn, device->path,
514                                         DUNDEE_DEVICE_INTERFACE,
515                                         device_methods, device_signals,
516                                         NULL, device, NULL)) {
517                 ofono_error("Could not register Device %s", device->path);
518                 return -EIO;
519         }
520
521         signal = dbus_message_new_signal(DUNDEE_MANAGER_PATH,
522                                                 DUNDEE_MANAGER_INTERFACE,
523                                                 "DeviceAdded");
524
525         if (signal == NULL)
526                 return -ENOMEM;
527
528         dbus_message_iter_init_append(signal, &iter);
529
530         dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH,
531                                         &device->path);
532         dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
533                                         OFONO_PROPERTIES_ARRAY_SIGNATURE,
534                                         &dict);
535         __dundee_device_append_properties(device, &dict);
536         dbus_message_iter_close_container(&iter, &dict);
537
538         g_dbus_send_message(conn, signal);
539
540         return 0;
541 }
542
543 static int unregister_device(struct dundee_device *device)
544 {
545         DBusConnection *conn = ofono_dbus_get_connection();
546
547         DBG("%p path %s", device, device->path);
548
549         g_dbus_unregister_interface(conn, device->path,
550                                         DUNDEE_DEVICE_INTERFACE);
551
552         g_dbus_emit_signal(conn, DUNDEE_MANAGER_PATH,
553                                 DUNDEE_MANAGER_INTERFACE, "DeviceRemoved",
554                                 DBUS_TYPE_OBJECT_PATH, &device->path,
555                                 DBUS_TYPE_INVALID);
556
557         return 0;
558 }
559
560 static void destroy_device(gpointer user)
561 {
562         struct dundee_device *device = user;
563
564         if (device->chat != NULL)
565                 g_at_chat_unref(device->chat);
566
567         if (device->ppp != NULL)
568                 g_at_ppp_unref(device->ppp);
569
570         if (device->pending)
571                 dbus_message_unref(device->pending);
572
573         g_free(device->settings.interface);
574         g_free(device->settings.ip);
575         g_strfreev(device->settings.nameservers);
576
577         g_free(device->path);
578         g_free(device->name);
579
580         g_free(device);
581 }
582
583 struct dundee_device *dundee_device_create(struct dundee_device_driver *d)
584 {
585         struct dundee_device *device;
586
587         device = g_try_new0(struct dundee_device, 1);
588         if (device == NULL)
589                 return NULL;
590
591         device->driver = d;
592
593         device->path = g_strdup_printf("/device%d", next_device_id);
594         if (device->path == NULL) {
595                 g_free(device);
596                 return NULL;
597         }
598
599         next_device_id += 1;
600
601         return device;
602 }
603
604 int dundee_device_register(struct dundee_device *device)
605 {
606         int err;
607
608         err = register_device(device);
609         if (err < 0)
610                 return err;
611
612         device->registered = TRUE;
613
614         g_hash_table_insert(device_hash, g_strdup(device->path), device);
615
616         return 0;
617 }
618
619 void dundee_device_unregister(struct dundee_device *device)
620 {
621         DBG("%p", device);
622
623         unregister_device(device);
624
625         device->registered = FALSE;
626
627         g_hash_table_remove(device_hash, device->path);
628 }
629
630 void dundee_device_set_data(struct dundee_device *device, void *data)
631 {
632         device->data = data;
633 }
634
635 void *dundee_device_get_data(struct dundee_device *device)
636 {
637         return device->data;
638 }
639
640 int dundee_device_set_name(struct dundee_device *device, const char *name)
641 {
642         DBusConnection *conn = ofono_dbus_get_connection();
643
644         DBG("%p name %s", device, name);
645
646         g_free(device->name);
647         device->name = g_strdup(name);
648
649         if (device->registered == FALSE)
650                 return 0;
651
652         ofono_dbus_signal_property_changed(conn, device->path,
653                                         DUNDEE_DEVICE_INTERFACE, "Name",
654                                         DBUS_TYPE_STRING, &device->name);
655
656         return 0;
657 }
658
659 static void device_shutdown(gpointer key, gpointer value, gpointer user_data)
660 {
661         struct dundee_device *device = value;
662
663         unregister_device(device);
664 }
665
666 void __dundee_device_shutdown(void)
667 {
668         g_hash_table_foreach(device_hash, device_shutdown, NULL);
669
670         __dundee_exit();
671 }
672
673 int __dundee_device_init(void)
674 {
675         DBG("");
676
677         device_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
678                                                 g_free, destroy_device);
679
680         return 0;
681 }
682
683 void __dundee_device_cleanup(void)
684 {
685         DBG("");
686
687         g_hash_table_destroy(device_hash);
688 }