more scan retries
[libicd-wpa] / icd.c
1 /**
2   @file icd.c
3
4   Copyright (C) 2009 Javier S. Pedro
5
6   @author Javier S. Pedro <javispedro@javispedro.com>
7
8   This file is part of libicd-network-wpa.
9
10   This program is free software; you can redistribute it and/or modify it
11   under the terms of the GNU General Public License as published by the
12   Free Software Foundation; either version 2 of the License, or (at your
13   option) any later version.
14
15   This program is distributed in the hope that it will be useful, but
16   WITHOUT ANY WARRANTY; without even the implied warranty of
17   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18   General Public License for more details.
19
20   You should have received a copy of the GNU General Public License along
21   with this program; if not, write to the Free Software Foundation, Inc.,
22   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23
24 */
25
26 #include <glib.h>
27
28 #include <network_api.h>
29 #include <osso-ic-dbus.h>
30
31 #include "common.h"
32 #include "dbus.h"
33 #include "dbus-handler.h"
34 #include "log.h"
35 #include "supp.h"
36 #include "networks.h"
37 #include "icd.h"
38
39 /**  time in seconds a found network is kept cached by icd2 */
40 #define ICD_SEARCH_LIFETIME (5 * 60)
41 /** time in seconds after which a new network scan is to be triggered */
42 #define ICD_SEARCH_INTERVAL (2 * 60)
43
44 /** time (in milliseconds) to wait after a wpa_supplicant disconnected event 
45  *   before calling it a day and shutting down the interface */
46 #define ICD_DISCONNECTED_TIMEOUT (60 * 1000)
47
48 /** icd flags to use */
49 #define ICD_WPA_FLAGS (ICD_NW_ATTR_AUTOCONNECT | ICD_NW_ATTR_IAPNAME)
50
51
52 /** equivalent to signal level 0 */
53 #define RSSI_MIN (-85)
54 /** equivalent to signal level max */
55 #define RSSI_MAX (-30)
56 /* guaranteed to be random, chosen by fair dice roll :) */
57
58
59 static icd_nw_watch_pid_fn icd_watch_fn;
60 static gpointer icd_watch_token;
61 static icd_nw_close_fn icd_close_fn;
62 static icd_nw_status_change_fn icd_status_change_fn;
63 static icd_nw_renew_fn icd_renew_fn;
64
65 static gchar * cur_network_id = NULL;
66
67 /** @see ICD_DISCONNECTED_TIMEOUT */
68 static guint connect_timeout = 0;
69
70 static inline gint rssi_to_signal(gint rssi)
71 {
72         gint signal = rssi - RSSI_MIN + ICD_NW_LEVEL_NONE;
73         signal *= ICD_NW_LEVEL_10;
74         signal /= (RSSI_MAX - RSSI_MIN);
75
76         if (signal < ICD_NW_LEVEL_NONE) return ICD_NW_LEVEL_NONE;
77         else if (signal > ICD_NW_LEVEL_10) return ICD_NW_LEVEL_10;
78         else return signal;     
79 }
80
81 static gboolean disconnected_timeout_cb(gpointer data)
82 {
83         // If connect_timeout is 0, we have been disabled.
84         if (!connect_timeout) return FALSE; 
85         
86         // Disconnected for too long
87         DLOG_DEBUG("Disconnected for way too long");
88         icd_close(ICD_NW_ERROR, ICD_DBUS_ERROR_NETWORK_ERROR);
89         connect_timeout = 0;
90         return FALSE;
91 }
92
93 /** Supplicant callback while fully connected */
94 static void icd_supp_cb(enum supp_status status, const char * error_str,
95         gpointer user_data)
96 {
97         DLOG_DEBUG("%s: %d", __func__, status);
98         
99         switch (status) {
100         case SUPP_STATUS_ERROR:
101                 DLOG_WARN_L("Unexpected supplicant error");
102                 
103                 // Fall through
104         case SUPP_STATUS_KILLED:
105                 // Close connection
106                 icd_close(ICD_NW_ERROR, ICD_DBUS_ERROR_SYSTEM_ERROR);
107                 
108                 break;
109         case SUPP_STATUS_DISCONNECTED:
110                 if (!connect_timeout) {
111                         DLOG_DEBUG("Disconnected, starting timeout");
112                         connect_timeout = g_timeout_add(
113                                 ICD_DISCONNECTED_TIMEOUT,
114                                 disconnected_timeout_cb,
115                                 NULL
116                                 );
117                 }
118                 break;
119         case SUPP_STATUS_CONNECTED:
120                 if (connect_timeout) {
121                         DLOG_DEBUG("Connected, removing timeout");
122                         g_source_remove(connect_timeout);
123                         connect_timeout = 0;
124                 }
125                 break;
126         }
127         
128         // TODO Disable PSM while roaming (dis/asociating)?
129 }
130
131 struct pre_down_data {
132         icd_nw_link_pre_down_cb_fn link_pre_down_cb;
133         gpointer link_pre_down_cb_token;
134 };
135
136 /** Supplicant callback in link_pre_down mode */
137 static void icd_down_supp_cb(enum supp_status status, const char * error_str,
138         gpointer user_data)
139 {
140         struct pre_down_data *data = (struct pre_down_data *) user_data;
141         DLOG_DEBUG("%s: %d", __func__, status);
142         
143         if (status == SUPP_STATUS_KILLED) {
144                 // Everything was fine
145                 supp_set_callback(NULL, NULL);
146                 data->link_pre_down_cb(ICD_NW_SUCCESS,
147                         data->link_pre_down_cb_token);
148         }
149 }
150
151 static void
152 icd_pre_down (const gchar *network_type,
153                             const guint network_attrs,
154                             const gchar *network_id,
155                             const gchar *interface_name,
156                             icd_nw_link_pre_down_cb_fn link_pre_down_cb,
157                             const gpointer link_pre_down_cb_token,
158                             gpointer *private)
159 {
160         DLOG_DEBUG(__func__);
161         
162         
163         struct pre_down_data *data = g_new(struct pre_down_data, 1);
164         data->link_pre_down_cb = link_pre_down_cb;
165         data->link_pre_down_cb_token = link_pre_down_cb_token;
166         
167         if (supp_is_active()) {
168                 supp_set_callback(icd_down_supp_cb, data);
169         
170                 // Kill the supplicant, 
171                 supp_disable();
172                 //  but wait for the "child exit" event.
173         } else {
174                 // Supplicant is already dead, no need to wait.
175                 supp_set_callback(NULL, NULL);
176                 supp_disable(); // Clears status info
177         
178                 link_pre_down_cb(ICD_NW_SUCCESS, link_pre_down_cb_token);
179         }
180 }
181
182 struct post_up_data {
183         icd_nw_link_post_up_cb_fn link_post_up_cb;
184         gpointer link_post_up_cb_token;
185 };
186
187 /** Supplicant callback in link_post_up status */
188 static void icd_up_supp_cb(enum supp_status status, const char * error_str,
189         gpointer user_data)
190 {
191         struct post_up_data *data = (struct post_up_data *) user_data;
192         DLOG_DEBUG("%s: %d", __func__, status);
193         
194         switch (status) {
195                 case SUPP_STATUS_CONNECTED:
196                         data->link_post_up_cb(ICD_NW_SUCCESS_NEXT_LAYER,
197                         NULL,
198                         data->link_post_up_cb_token, NULL);
199                         
200                         supp_set_callback(icd_supp_cb, NULL);
201                         g_free(data);
202                         break;
203                 case SUPP_STATUS_DISCONNECTED:
204                         error_str = ICD_DBUS_ERROR_WLAN_AUTH_FAILED;
205                         
206                         // Fall through
207                 case SUPP_STATUS_ERROR:
208                 
209                         // An error happened
210                         //  kill the supplicant before everything crashes.
211                         supp_set_callback(NULL, NULL);
212                         supp_disable();
213                         
214                         // Fall through         
215                 case SUPP_STATUS_KILLED:        
216                         data->link_post_up_cb(ICD_NW_ERROR,
217                         error_str,
218                         data->link_post_up_cb_token, NULL);
219                         
220                         g_free(data);
221                         break;
222                 
223         }
224 }
225
226 static void icd_post_up(const gchar *network_type,
227                            const guint network_attrs,
228                            const gchar *network_id,
229                            const gchar *interface_name,
230                            icd_nw_link_post_up_cb_fn link_post_up_cb,
231                            const gpointer link_post_up_cb_token,
232                            gpointer *private)
233 {
234         DLOG_DEBUG(__func__);
235         
236         struct post_up_data *data = g_new(struct post_up_data, 1);
237         data->link_post_up_cb = link_post_up_cb;
238         data->link_post_up_cb_token = link_post_up_cb_token;
239         
240         supp_set_callback(icd_up_supp_cb, data);
241         
242         if (supp_enable() != 0)
243         {
244                 icd_up_supp_cb(-1, ICD_DBUS_ERROR_SYSTEM_ERROR, data);
245         }
246         
247         supp_set_interface(interface_name);
248         supp_set_network_id(network_id);
249 }
250
251 static void icd_link_down(const gchar *network_type,
252                         const guint network_attrs,
253                         const gchar *network_id,
254                         const gchar *interface_name,
255                         icd_nw_link_down_cb_fn link_down_cb,
256                         const gpointer link_down_cb_token,
257                         gpointer *private)
258 {
259         DLOG_DEBUG(__func__);
260         
261         networks_disconnect(network_id);
262         
263         g_free(cur_network_id);
264         cur_network_id = NULL;
265         
266         // TODO: Maybe wait for wlancond->disconnect callback?
267         
268         link_down_cb(ICD_NW_SUCCESS, link_down_cb_token);
269 }
270
271 struct link_up_data {
272         icd_nw_link_up_cb_fn link_up_cb;
273         gpointer link_up_cb_token;
274 };
275
276 static void icd_link_up_done(int status, const char *error, gpointer user_data)
277 {
278         DLOG_DEBUG("%s: %d", __func__, status);
279         
280         struct link_up_data *data = (struct link_up_data *) user_data;
281         
282         if (status) {
283                 g_free(cur_network_id);
284                 cur_network_id = NULL;
285                 data->link_up_cb(ICD_NW_ERROR,
286                         error,
287                         WPA_IFACE, data->link_up_cb_token,
288                         NULL);
289                         
290         } else {
291                 data->link_up_cb(ICD_NW_SUCCESS_NEXT_LAYER,
292                         NULL,
293                         WPA_IFACE, data->link_up_cb_token,
294                         NULL);
295         }
296         
297         g_free(data);
298 }
299
300 static void icd_link_up(const gchar *network_type,
301                       const guint network_attrs,
302                       const gchar *network_id,
303                       icd_nw_link_up_cb_fn link_up_cb,
304                       const gpointer link_up_cb_token,
305                       gpointer *private)
306 {
307         DLOG_DEBUG("%s: %s", __func__, network_id);
308         
309         struct link_up_data *data = g_new(struct link_up_data, 1);
310         data->link_up_cb = link_up_cb;
311         data->link_up_cb_token = link_up_cb_token;
312         
313         if (cur_network_id) {
314                 DLOG_WARN_L("Double link up");
315                 
316                 data->link_up_cb(ICD_NW_TOO_MANY_CONNECTIONS,
317                         NULL,
318                         WPA_IFACE, data->link_up_cb_token,
319                         NULL);
320                 
321                 return;
322         }
323         cur_network_id = g_strdup(network_id);
324         
325         networks_connect(network_id, icd_link_up_done, data);
326 }
327
328 struct stats_data {
329         icd_nw_link_stats_cb_fn cb;
330         gpointer token;
331 };
332
333 static void icd_link_stats_done
334         (int status, const char * strdata, int rssi, gpointer user_data)
335 {
336         DLOG_DEBUG("%s: %d", __func__, status);
337         
338         struct stats_data * data = (struct stats_data *) user_data;
339         
340         gint signal = rssi_to_signal(rssi);
341         
342         DLOG_DEBUG("status: rssi=%d, signal=%d", rssi, signal);
343         
344         data->cb(data->token,
345                 WPA_NETWORK_TYPE,
346                 ICD_WPA_FLAGS,
347                 cur_network_id,
348                 /*time_active*/ 0,
349                 /*signal*/      signal,
350                 /*station_id*/  NULL,
351                 /*dB*/          rssi,
352                 /*tx*/0,
353                 /*rx*/0);
354                 
355         // IPv[46] module will fill time_active, rx & tx values.
356         // TODO station_id
357         
358         g_free(data);
359 }
360
361 static void icd_link_stats(const gchar *network_type,
362                                       const guint network_attrs,
363                                       const gchar *network_id,
364                                       gpointer *private,
365                                       icd_nw_link_stats_cb_fn cb,
366                                       const gpointer link_stats_cb_token)             
367 {
368         DLOG_DEBUG("%s", __func__);
369         
370         struct stats_data *data = g_new(struct stats_data, 1);
371         data->cb = cb;
372         data->token = link_stats_cb_token;
373         
374         networks_status(icd_link_stats_done, data);
375 }
376
377 struct search_data {
378         icd_nw_search_cb_fn search_cb;
379         gpointer search_cb_token;
380 };
381
382 static void icd_send_search_result(int status, const char * id,
383         const char * ssid, const char * ap, int rssi, gpointer user_data)
384 {
385         DLOG_DEBUG("%s: %d", __func__, status);
386         
387         struct search_data * data = (struct search_data *) user_data;
388         gint signal;
389         
390         switch (status) {
391         case SEARCH_CONTINUE:
392                 signal = rssi_to_signal(rssi);
393                 
394                 data->search_cb(ICD_NW_SEARCH_CONTINUE,
395                                 (gchar *) ssid,
396                                 WPA_NETWORK_TYPE,
397                                 ICD_WPA_FLAGS,
398                                 (gchar *) id,
399                                 signal,
400                                 /*ap*/"ap",
401                                 signal,
402                                 data->search_cb_token);
403                 break;
404         case SEARCH_FINISHED:
405                 data->search_cb(ICD_NW_SEARCH_COMPLETE,
406                                 NULL,
407                                 WPA_NETWORK_TYPE,
408                                 0,
409                                 NULL,
410                                 ICD_NW_LEVEL_NONE,
411                                 NULL,
412                                 0,
413                                 data->search_cb_token);
414                 // Fall through
415         case SEARCH_STOPPED:
416                 g_free(user_data);
417                 break;
418         }
419 }
420
421 static void icd_start_search(const gchar *network_type,
422                            guint search_scope,
423                            icd_nw_search_cb_fn search_cb,
424                            const gpointer search_cb_token,
425                            gpointer *private)
426 {
427         DLOG_DEBUG(__func__);
428         
429         struct search_data *data = g_new(struct search_data, 1);
430         data->search_cb = search_cb;
431         data->search_cb_token = search_cb_token;
432         
433         networks_search_start(icd_send_search_result, data);
434 }
435
436 static void icd_stop_search(gpointer *private)
437 {
438         DLOG_DEBUG(__func__);
439         
440         // Never seen this called.
441         
442         networks_search_stop();
443 }
444
445 static void icd_child_exit(const pid_t pid,
446                          const gint exit_value,
447                          gpointer *private)
448 {
449         DLOG_DEBUG(__func__);
450         
451         /*// Supplicant crashed/exited!
452         if (killed_supp_cb) {
453                 // We have killed it, notify auth layer is now down.
454
455                 g_free(killed_supp_cb);
456                 killed_supp_cb = 0;
457         } else {
458                 DLOG_WARN("Supplicant exited, but we didn't expect it");
459                 icd_close(ICD_NW_ERROR, ICD_DBUS_ERROR_SYSTEM_ERROR);
460         }*/
461         supp_handle_killed();
462 }
463
464 static void icd_network_destruct(gpointer *private)
465 {
466         DLOG_DEBUG(__func__);
467         
468         // Fortunately, icd kills the supplicant for us.
469         
470         close_dbus_connection();
471         networks_free();
472 }
473
474 gboolean icd_nw_init (
475         struct icd_nw_api *network_api,
476         icd_nw_watch_pid_fn watch_fn,
477         gpointer watch_fn_token,
478         icd_nw_close_fn close_fn,
479         icd_nw_status_change_fn status_change_fn,
480         icd_nw_renew_fn renew_fn )
481 {
482         network_api->version = ICD_NW_MODULE_VERSION;
483         network_api->private = NULL;
484         
485         network_api->link_pre_down = icd_pre_down;
486         network_api->link_post_up = icd_post_up;
487         
488         network_api->link_down = icd_link_down;
489         network_api->link_up = icd_link_up;
490         network_api->link_stats = icd_link_stats;
491         
492         network_api->search_lifetime = ICD_SEARCH_LIFETIME;
493         network_api->search_interval = ICD_SEARCH_INTERVAL;
494         
495         network_api->start_search = icd_start_search;
496         network_api->stop_search = icd_stop_search;
497         
498         network_api->child_exit = icd_child_exit;
499         
500         network_api->network_destruct = icd_network_destruct;
501         
502         icd_watch_fn = watch_fn;
503         icd_watch_token = watch_fn_token;
504         icd_close_fn = close_fn;
505         icd_status_change_fn = status_change_fn;
506         icd_renew_fn = renew_fn;
507         
508         if (networks_initialize() != 0) {
509                 DLOG_ERR_L("Network list failed!");
510                 return FALSE;
511         }
512         
513         if (setup_dbus_connection(NULL, init_dbus_handlers) != 0) {
514                 DLOG_ERR_L("D-BUS connection setup failed!");
515                 return FALSE;
516         }
517         
518         return TRUE;
519 }
520
521 void icd_watch_pid(const pid_t pid)
522 {
523         icd_watch_fn(pid, icd_watch_token);
524 }
525
526 void icd_close(enum icd_nw_status status,
527                 const gchar *err_str)
528 {
529         icd_close_fn(status, err_str, WPA_NETWORK_TYPE, ICD_WPA_FLAGS, cur_network_id);
530 }
531