Add support for exporting the WiFi network mode
[connman] / plugins / wifi.c
1 /*
2  *
3  *  Connection Manager
4  *
5  *  Copyright (C) 2007-2008  Intel Corporation. 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 <stdio.h>
27 #include <unistd.h>
28 #include <string.h>
29 #include <sys/ioctl.h>
30 #include <sys/socket.h>
31 #include <linux/if_arp.h>
32 #include <linux/wireless.h>
33
34 #include <gdbus.h>
35
36 #include <connman/plugin.h>
37 #include <connman/driver.h>
38 #include <connman/rtnl.h>
39 #include <connman/dbus.h>
40 #include <connman/log.h>
41
42 #include "inet.h"
43 #include "supplicant.h"
44
45 #define CLEANUP_TIMEOUT   8     /* in seconds */
46 #define INACTIVE_TIMEOUT  12    /* in seconds */
47
48 struct wifi_data {
49         GSList *current;
50         GSList *pending;
51         guint cleanup_timer;
52         guint inactive_timer;
53         gchar *identifier;
54         gboolean connected;
55 };
56
57 static int network_probe(struct connman_element *element)
58 {
59         DBG("element %p name %s", element, element->name);
60
61         return 0;
62 }
63
64 static void network_remove(struct connman_element *element)
65 {
66         DBG("element %p name %s", element, element->name);
67 }
68
69 static int network_enable(struct connman_element *element)
70 {
71         struct connman_element *device = element->parent;
72         char *name, *security = NULL, *passphrase = NULL;
73         unsigned char *ssid;
74         int ssid_len;
75
76         DBG("element %p name %s", element, element->name);
77
78         if (connman_element_get_static_property(element,
79                                                 "Name", &name) == FALSE)
80                 return -EIO;
81
82         if (connman_element_get_static_array_property(element,
83                                 "WiFi.SSID", &ssid, &ssid_len) == FALSE)
84                 return -EIO;
85
86         if (device != NULL) {
87                 struct wifi_data *data = connman_element_get_data(device);
88
89                 if (data != NULL) {
90                         if (data->connected == TRUE)
91                                 return -EBUSY;
92
93                         g_free(data->identifier);
94                         data->identifier = g_strdup(name);
95                 }
96         }
97
98         connman_element_get_value(element,
99                         CONNMAN_PROPERTY_ID_WIFI_SECURITY, &security);
100
101         connman_element_get_value(element,
102                         CONNMAN_PROPERTY_ID_WIFI_PASSPHRASE, &passphrase);
103
104         DBG("name %s security %s passhprase %s",
105                                         name, security, passphrase);
106
107         if (__supplicant_connect(element, ssid, ssid_len,
108                                                 security, passphrase) < 0)
109                 connman_error("Failed to initiate connect");
110
111         return 0;
112 }
113
114 static int network_disable(struct connman_element *element)
115 {
116         DBG("element %p name %s", element, element->name);
117
118         connman_element_unregister_children(element);
119
120         __supplicant_disconnect(element);
121
122         return 0;
123 }
124
125 static struct connman_driver network_driver = {
126         .name           = "wifi-network",
127         .type           = CONNMAN_ELEMENT_TYPE_NETWORK,
128         .subtype        = CONNMAN_ELEMENT_SUBTYPE_WIFI,
129         .probe          = network_probe,
130         .remove         = network_remove,
131         .enable         = network_enable,
132         .disable        = network_disable,
133 };
134
135 static struct connman_element *find_current_element(struct wifi_data *data,
136                                                         const char *identifier)
137 {
138         GSList *list;
139
140         for (list = data->current; list; list = list->next) {
141                 struct connman_element *element = list->data;
142
143                 if (connman_element_match_static_property(element,
144                                                 "Name", &identifier) == TRUE)
145                         return element;
146         }
147
148         return NULL;
149 }
150
151 static struct connman_element *find_pending_element(struct wifi_data *data,
152                                                         const char *identifier)
153 {
154         GSList *list;
155
156         for (list = data->pending; list; list = list->next) {
157                 struct connman_element *element = list->data;
158
159                 if (connman_element_match_static_property(element,
160                                                 "Name", &identifier) == TRUE)
161                         return element;
162         }
163
164         return NULL;
165 }
166
167 static gboolean inactive_scan(gpointer user_data)
168 {
169         struct connman_element *device = user_data;
170         struct wifi_data *data = connman_element_get_data(device);
171
172         DBG("");
173
174         __supplicant_scan(device);
175
176         data->inactive_timer = 0;
177
178         return FALSE;
179 }
180
181 static void connect_known_networks(struct connman_element *device)
182 {
183         struct wifi_data *data = connman_element_get_data(device);
184         GSList *list;
185
186         DBG("");
187
188         if (data->inactive_timer > 0) {
189                 g_source_remove(data->inactive_timer);
190                 data->inactive_timer = 0;
191         }
192
193         for (list = data->current; list; list = list->next) {
194                 struct connman_element *element = list->data;
195
196                 if (element->policy == CONNMAN_ELEMENT_POLICY_AUTO &&
197                                                 element->remember == TRUE &&
198                                                 element->available == TRUE) {
199                         if (network_enable(element) == 0)
200                                 return;
201                 }
202         }
203
204         data->inactive_timer = g_timeout_add_seconds(INACTIVE_TIMEOUT,
205                                                         inactive_scan, device);
206 }
207
208 static void state_change(struct connman_element *device,
209                                                 enum supplicant_state state)
210 {
211         struct wifi_data *data = connman_element_get_data(device);
212         struct connman_element *element;
213
214         DBG("state %d", state);
215
216         if (state == STATE_SCANNING)
217                 connman_element_set_scanning(device, TRUE);
218         else
219                 connman_element_set_scanning(device, FALSE);
220
221         if (data == NULL)
222                 return;
223
224         if (data->identifier == NULL)
225                 goto reconnect;
226
227         element = find_current_element(data, data->identifier);
228         if (element == NULL)
229                 goto reconnect;
230
231         if (state == STATE_COMPLETED) {
232                 struct connman_element *dhcp;
233
234                 data->connected = TRUE;
235                 connman_element_set_enabled(element, TRUE);
236
237                 dhcp = connman_element_create(NULL);
238
239                 dhcp->type = CONNMAN_ELEMENT_TYPE_DHCP;
240                 dhcp->index = element->index;
241
242                 if (connman_element_register(dhcp, element) < 0)
243                         connman_element_unref(dhcp);
244         } else if (state == STATE_INACTIVE || state == STATE_DISCONNECTED) {
245                 data->connected = FALSE;
246                 connman_element_set_enabled(element, FALSE);
247
248                 connman_element_unregister_children(element);
249         }
250
251 reconnect:
252         if (state == STATE_INACTIVE) {
253                 data->connected = FALSE;
254                 connect_known_networks(device);
255         }
256 }
257
258 static gboolean cleanup_pending(gpointer user_data)
259 {
260         struct wifi_data *data = user_data;
261         GSList *list;
262
263         DBG("");
264
265         for (list = data->pending; list; list = list->next) {
266                 struct connman_element *element = list->data;
267
268                 DBG("element %p name %s", element, element->name);
269
270                 connman_element_unregister(element);
271                 connman_element_unref(element);
272         }
273
274         g_slist_free(data->pending);
275         data->pending = NULL;
276
277         data->cleanup_timer = 0;
278
279         return FALSE;
280 }
281
282 static void clear_results(struct connman_element *device)
283 {
284         struct wifi_data *data = connman_element_get_data(device);
285
286         DBG("pending %d", g_slist_length(data->pending));
287         DBG("current %d", g_slist_length(data->current));
288
289         if (data->cleanup_timer > 0) {
290                 g_source_remove(data->cleanup_timer);
291                 cleanup_pending(data);
292         }
293
294         data->pending = data->current;
295         data->current = NULL;
296
297         data->cleanup_timer = g_timeout_add_seconds(CLEANUP_TIMEOUT,
298                                                         cleanup_pending, data);
299 }
300
301 static void scan_result(struct connman_element *device,
302                                         struct supplicant_network *network)
303 {
304         struct wifi_data *data = connman_element_get_data(device);
305         struct connman_element *element;
306         gchar *temp;
307         int i;
308
309         DBG("network %p identifier %s", network, network->identifier);
310
311         if (data == NULL)
312                 return;
313
314         if (network->identifier == NULL)
315                 return;
316
317         if (network->identifier[0] == '\0')
318                 return;
319
320         temp = g_strdup(network->identifier);
321
322         for (i = 0; i < strlen(temp); i++) {
323                 char tmp = temp[i];
324                 if ((tmp < '0' || tmp > '9') && (tmp < 'A' || tmp > 'Z') &&
325                                                 (tmp < 'a' || tmp > 'z'))
326                         temp[i] = '_';
327         }
328
329         element = find_pending_element(data, network->identifier);
330         if (element == NULL) {
331                 const char *mode;
332
333                 element = connman_element_create(temp);
334
335                 element->type = CONNMAN_ELEMENT_TYPE_NETWORK;
336                 element->index = device->index;
337
338                 connman_element_add_static_property(element, "Name",
339                                 DBUS_TYPE_STRING, &network->identifier);
340
341                 connman_element_add_static_array_property(element, "WiFi.SSID",
342                         DBUS_TYPE_BYTE, &network->ssid, network->ssid_len);
343
344                 mode = (network->adhoc == TRUE) ? "adhoc" : "managed";
345                 connman_element_add_static_property(element, "WiFi.Mode",
346                                                 DBUS_TYPE_STRING, &mode);
347
348                 if (element->wifi.security == NULL) {
349                         const char *security;
350
351                         if (network->has_rsn == TRUE)
352                                 security = "wpa2";
353                         else if (network->has_wpa == TRUE)
354                                 security = "wpa";
355                         else if (network->has_wep == TRUE)
356                                 security = "wep";
357                         else
358                                 security = "none";
359
360                         element->wifi.security = g_strdup(security);
361                 }
362
363                 element->strength = network->quality;
364
365                 connman_element_add_static_property(element, "Strength",
366                                         DBUS_TYPE_BYTE, &element->strength);
367
368                 DBG("%s (%s %s) strength %d", network->identifier, mode,
369                                 element->wifi.security, element->strength);
370
371                 if (connman_element_register(element, device) < 0) {
372                         connman_element_unref(element);
373                         goto done;
374                 }
375         } else {
376                 data->pending = g_slist_remove(data->pending, element);
377
378                 if (element->strength != network->quality) {
379                         element->strength = network->quality;
380
381                         connman_element_set_static_property(element, "Strength",
382                                         DBUS_TYPE_BYTE, &element->strength);
383
384                         connman_element_update(element);
385                 }
386         }
387
388         data->current = g_slist_append(data->current, element);
389
390         element->available = TRUE;
391
392 done:
393         g_free(temp);
394 }
395
396 static struct supplicant_callback wifi_callback = {
397         .state_change   = state_change,
398         .clear_results  = clear_results,
399         .scan_result    = scan_result,
400 };
401
402 static int wifi_probe(struct connman_element *element)
403 {
404         struct wifi_data *data;
405
406         DBG("element %p name %s", element, element->name);
407
408         data = g_try_new0(struct wifi_data, 1);
409         if (data == NULL)
410                 return -ENOMEM;
411
412         data->connected = FALSE;
413
414         connman_element_set_data(element, data);
415
416         return 0;
417 }
418
419 static void wifi_remove(struct connman_element *element)
420 {
421         struct wifi_data *data = connman_element_get_data(element);
422
423         DBG("element %p name %s", element, element->name);
424
425         connman_element_set_data(element, NULL);
426
427         g_free(data->identifier);
428         g_free(data);
429 }
430
431 static int wifi_update(struct connman_element *element)
432 {
433         DBG("element %p name %s", element, element->name);
434
435         __supplicant_scan(element);
436
437         return 0;
438 }
439
440 static int wifi_enable(struct connman_element *element)
441 {
442         int err;
443
444         DBG("element %p name %s", element, element->name);
445
446         err = __supplicant_start(element, &wifi_callback);
447         if (err < 0)
448                 return err;
449
450         __supplicant_scan(element);
451
452         return 0;
453 }
454
455 static int wifi_disable(struct connman_element *element)
456 {
457         struct wifi_data *data = connman_element_get_data(element);
458         GSList *list;
459
460         DBG("element %p name %s", element, element->name);
461
462         if (data->cleanup_timer > 0) {
463                 g_source_remove(data->cleanup_timer);
464                 cleanup_pending(data);
465         }
466
467         if (data->inactive_timer > 0) {
468                 g_source_remove(data->inactive_timer);
469                 data->inactive_timer = 0;
470         }
471
472         __supplicant_disconnect(element);
473
474         for (list = data->current; list; list = list->next) {
475                 struct connman_element *network = list->data;
476
477                 connman_element_unref(network);
478         }
479
480         g_slist_free(data->current);
481         data->current = NULL;
482
483         connman_element_unregister_children(element);
484
485         __supplicant_stop(element);
486
487         return 0;
488 }
489
490 static struct connman_driver wifi_driver = {
491         .name           = "wifi-device",
492         .type           = CONNMAN_ELEMENT_TYPE_DEVICE,
493         .subtype        = CONNMAN_ELEMENT_SUBTYPE_WIFI,
494         .probe          = wifi_probe,
495         .remove         = wifi_remove,
496         .update         = wifi_update,
497         .enable         = wifi_enable,
498         .disable        = wifi_disable,
499 };
500
501 static GSList *device_list = NULL;
502
503 static void wifi_newlink(unsigned short type, int index,
504                                         unsigned flags, unsigned change)
505 {
506         struct connman_element *device;
507         GSList *list;
508         gboolean exists = FALSE;
509         gchar *name, *devname;
510         struct iwreq iwr;
511         int sk;
512
513         DBG("index %d", index);
514
515         if (type != ARPHRD_ETHER)
516                 return;
517
518         name = inet_index2ident(index, "dev_");
519         devname = inet_index2name(index);
520
521         memset(&iwr, 0, sizeof(iwr));
522         strncpy(iwr.ifr_ifrn.ifrn_name, devname, IFNAMSIZ);
523
524         sk = socket(PF_INET, SOCK_DGRAM, 0);
525
526         if (ioctl(sk, SIOCGIWNAME, &iwr) < 0) {
527                 g_free(name);
528                 close(sk);
529                 return;
530         }
531
532         close(sk);
533
534         for (list = device_list; list; list = list->next) {
535                 struct connman_element *device = list->data;
536
537                 if (device->index == index) {
538                         exists = TRUE;
539                         break;
540                 }
541         }
542
543         if (exists == TRUE) {
544                 g_free(name);
545                 return;
546         }
547
548         device = connman_element_create(NULL);
549         device->type = CONNMAN_ELEMENT_TYPE_DEVICE;
550         device->subtype = CONNMAN_ELEMENT_SUBTYPE_WIFI;
551
552         device->index = index;
553         device->name = name;
554         device->devname = devname;
555
556         if (connman_element_register(device, NULL) < 0) {
557                 connman_element_unregister(device);
558                 return;
559         }
560
561         device_list = g_slist_append(device_list, device);
562 }
563
564 static void wifi_dellink(unsigned short type, int index,
565                                         unsigned flags, unsigned change)
566 {
567         GSList *list;
568
569         DBG("index %d", index);
570
571         for (list = device_list; list; list = list->next) {
572                 struct connman_element *device = list->data;
573
574                 if (device->index == index) {
575                         device_list = g_slist_remove(device_list, device);
576                         connman_element_unregister(device);
577                         connman_element_unref(device);
578                         break;
579                 }
580         }
581 }
582
583 static struct connman_rtnl wifi_rtnl = {
584         .name           = "wifi",
585         .newlink        = wifi_newlink,
586         .dellink        = wifi_dellink,
587 };
588
589 static void supplicant_connect(DBusConnection *connection, void *user_data)
590 {
591         DBG("connection %p", connection);
592
593         __supplicant_init(connection);
594
595         if (connman_rtnl_register(&wifi_rtnl) < 0)
596                 return;
597
598         connman_rtnl_send_getlink();
599 }
600
601 static void supplicant_disconnect(DBusConnection *connection, void *user_data)
602 {
603         GSList *list;
604
605         DBG("connection %p", connection);
606
607         connman_rtnl_unregister(&wifi_rtnl);
608
609         for (list = device_list; list; list = list->next) {
610                 struct connman_element *device = list->data;
611
612                 connman_element_unregister(device);
613                 connman_element_unref(device);
614         }
615
616         g_slist_free(device_list);
617         device_list = NULL;
618
619         __supplicant_exit();
620 }
621
622 static DBusConnection *connection;
623 static guint watch;
624
625 static int wifi_init(void)
626 {
627         int err;
628
629         connection = connman_dbus_get_connection();
630         if (connection == NULL)
631                 return -EIO;
632
633         err = connman_driver_register(&network_driver);
634         if (err < 0) {
635                 dbus_connection_unref(connection);
636                 return err;
637         }
638
639         err = connman_driver_register(&wifi_driver);
640         if (err < 0) {
641                 connman_driver_unregister(&network_driver);
642                 dbus_connection_unref(connection);
643                 return err;
644         }
645
646         watch = g_dbus_add_service_watch(connection, SUPPLICANT_NAME,
647                         supplicant_connect, supplicant_disconnect, NULL, NULL);
648
649         if (g_dbus_check_service(connection, SUPPLICANT_NAME) == TRUE)
650                 supplicant_connect(connection, NULL);
651         else
652                 __supplicant_activate(connection);
653
654         return 0;
655 }
656
657 static void wifi_exit(void)
658 {
659         connman_driver_unregister(&network_driver);
660         connman_driver_unregister(&wifi_driver);
661
662         if (watch > 0)
663                 g_dbus_remove_watch(connection, watch);
664
665         supplicant_disconnect(connection, NULL);
666
667         dbus_connection_unref(connection);
668 }
669
670 CONNMAN_PLUGIN_DEFINE(wifi, "WiFi interface plugin", VERSION,
671                                                         wifi_init, wifi_exit)