bgscan learn: Add data file for storing learned network information
[mech_eap.git] / wpa_supplicant / bgscan_learn.c
1 /*
2  * WPA Supplicant - background scan and roaming module: learn
3  * Copyright (c) 2009-2010, Jouni Malinen <j@w1.fi>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License version 2 as
7  * published by the Free Software Foundation.
8  *
9  * Alternatively, this software may be distributed under the terms of BSD
10  * license.
11  *
12  * See README and COPYING for more details.
13  */
14
15 #include "includes.h"
16
17 #include "common.h"
18 #include "eloop.h"
19 #include "drivers/driver.h"
20 #include "config_ssid.h"
21 #include "wpa_supplicant_i.h"
22 #include "driver_i.h"
23 #include "scan.h"
24 #include "bgscan.h"
25
26 struct bgscan_learn_data {
27         struct wpa_supplicant *wpa_s;
28         const struct wpa_ssid *ssid;
29         int scan_interval;
30         int signal_threshold;
31         int short_interval; /* use if signal < threshold */
32         int long_interval; /* use if signal > threshold */
33         struct os_time last_bgscan;
34         char *fname;
35 };
36
37
38 static int bgscan_learn_load(struct bgscan_learn_data *data)
39 {
40         FILE *f;
41         char buf[128];
42
43         if (data->fname == NULL)
44                 return 0;
45
46         f = fopen(data->fname, "r");
47         if (f == NULL)
48                 return 0;
49
50         wpa_printf(MSG_DEBUG, "bgscan learn: Loading data from %s",
51                    data->fname);
52
53         if (fgets(buf, sizeof(buf), f) == NULL ||
54             os_strncmp(buf, "wpa_supplicant-bgscan-learn\n", 28) != 0) {
55                 wpa_printf(MSG_INFO, "bgscan learn: Invalid data file %s",
56                            data->fname);
57                 fclose(f);
58                 return -1;
59         }
60
61         fclose(f);
62         return 0;
63 }
64
65
66 static void bgscan_learn_save(struct bgscan_learn_data *data)
67 {
68         FILE *f;
69
70         if (data->fname == NULL)
71                 return;
72
73         wpa_printf(MSG_DEBUG, "bgscan learn: Saving data to %s",
74                    data->fname);
75
76         f = fopen(data->fname, "w");
77         if (f == NULL)
78                 return;
79         fprintf(f, "wpa_supplicant-bgscan-learn\n");
80
81         fclose(f);
82 }
83
84
85 static void bgscan_learn_timeout(void *eloop_ctx, void *timeout_ctx)
86 {
87         struct bgscan_learn_data *data = eloop_ctx;
88         struct wpa_supplicant *wpa_s = data->wpa_s;
89         struct wpa_driver_scan_params params;
90
91         os_memset(&params, 0, sizeof(params));
92         params.num_ssids = 1;
93         params.ssids[0].ssid = data->ssid->ssid;
94         params.ssids[0].ssid_len = data->ssid->ssid_len;
95         params.freqs = data->ssid->scan_freq;
96
97         /*
98          * A more advanced bgscan module would learn about most like channels
99          * over time and request scans only for some channels (probing others
100          * every now and then) to reduce effect on the data connection.
101          */
102
103         wpa_printf(MSG_DEBUG, "bgscan learn: Request a background scan");
104         if (wpa_supplicant_trigger_scan(wpa_s, &params)) {
105                 wpa_printf(MSG_DEBUG, "bgscan learn: Failed to trigger scan");
106                 eloop_register_timeout(data->scan_interval, 0,
107                                        bgscan_learn_timeout, data, NULL);
108         } else
109                 os_get_time(&data->last_bgscan);
110 }
111
112
113 static int bgscan_learn_get_params(struct bgscan_learn_data *data,
114                                    const char *params)
115 {
116         const char *pos;
117
118         if (params == NULL)
119                 return 0;
120
121         data->short_interval = atoi(params);
122
123         pos = os_strchr(params, ':');
124         if (pos == NULL)
125                 return 0;
126         pos++;
127         data->signal_threshold = atoi(pos);
128         pos = os_strchr(pos, ':');
129         if (pos == NULL) {
130                 wpa_printf(MSG_ERROR, "bgscan learn: Missing scan interval "
131                            "for high signal");
132                 return -1;
133         }
134         pos++;
135         data->long_interval = atoi(pos);
136         pos = os_strchr(pos, ':');
137         if (pos) {
138                 pos++;
139                 data->fname = os_strdup(pos);
140         }
141
142         return 0;
143 }
144
145
146 static void * bgscan_learn_init(struct wpa_supplicant *wpa_s,
147                                 const char *params,
148                                 const struct wpa_ssid *ssid)
149 {
150         struct bgscan_learn_data *data;
151
152         data = os_zalloc(sizeof(*data));
153         if (data == NULL)
154                 return NULL;
155         data->wpa_s = wpa_s;
156         data->ssid = ssid;
157         if (bgscan_learn_get_params(data, params) < 0) {
158                 os_free(data->fname);
159                 os_free(data);
160                 return NULL;
161         }
162         if (data->short_interval <= 0)
163                 data->short_interval = 30;
164         if (data->long_interval <= 0)
165                 data->long_interval = 30;
166
167         if (bgscan_learn_load(data) < 0) {
168                 os_free(data->fname);
169                 os_free(data);
170                 return NULL;
171         }
172
173         wpa_printf(MSG_DEBUG, "bgscan learn: Signal strength threshold %d  "
174                    "Short bgscan interval %d  Long bgscan interval %d",
175                    data->signal_threshold, data->short_interval,
176                    data->long_interval);
177
178         if (data->signal_threshold &&
179             wpa_drv_signal_monitor(wpa_s, data->signal_threshold, 4) < 0) {
180                 wpa_printf(MSG_ERROR, "bgscan learn: Failed to enable "
181                            "signal strength monitoring");
182         }
183
184         data->scan_interval = data->short_interval;
185         eloop_register_timeout(data->scan_interval, 0, bgscan_learn_timeout,
186                                data, NULL);
187         return data;
188 }
189
190
191 static void bgscan_learn_deinit(void *priv)
192 {
193         struct bgscan_learn_data *data = priv;
194         bgscan_learn_save(data);
195         eloop_cancel_timeout(bgscan_learn_timeout, data, NULL);
196         if (data->signal_threshold)
197                 wpa_drv_signal_monitor(data->wpa_s, 0, 0);
198         os_free(data->fname);
199         os_free(data);
200 }
201
202
203 static int bgscan_learn_notify_scan(void *priv,
204                                     struct wpa_scan_results *scan_res)
205 {
206         struct bgscan_learn_data *data = priv;
207
208         wpa_printf(MSG_DEBUG, "bgscan learn: scan result notification");
209
210         eloop_cancel_timeout(bgscan_learn_timeout, data, NULL);
211         eloop_register_timeout(data->scan_interval, 0, bgscan_learn_timeout,
212                                data, NULL);
213
214         /*
215          * A more advanced bgscan could process scan results internally, select
216          * the BSS and request roam if needed. This sample uses the existing
217          * BSS/ESS selection routine. Change this to return 1 if selection is
218          * done inside the bgscan module.
219          */
220
221         return 0;
222 }
223
224
225 static void bgscan_learn_notify_beacon_loss(void *priv)
226 {
227         wpa_printf(MSG_DEBUG, "bgscan learn: beacon loss");
228         /* TODO: speed up background scanning */
229 }
230
231
232 static void bgscan_learn_notify_signal_change(void *priv, int above)
233 {
234         struct bgscan_learn_data *data = priv;
235
236         if (data->short_interval == data->long_interval ||
237             data->signal_threshold == 0)
238                 return;
239
240         wpa_printf(MSG_DEBUG, "bgscan learn: signal level changed "
241                    "(above=%d)", above);
242         if (data->scan_interval == data->long_interval && !above) {
243                 wpa_printf(MSG_DEBUG, "bgscan learn: Trigger immediate scan "
244                            "and start using short bgscan interval");
245                 data->scan_interval = data->short_interval;
246                 eloop_cancel_timeout(bgscan_learn_timeout, data, NULL);
247                 eloop_register_timeout(0, 0, bgscan_learn_timeout, data,
248                                        NULL);
249         } else if (data->scan_interval == data->short_interval && above) {
250                 wpa_printf(MSG_DEBUG, "bgscan learn: Start using long bgscan "
251                            "interval");
252                 data->scan_interval = data->long_interval;
253                 eloop_cancel_timeout(bgscan_learn_timeout, data, NULL);
254                 eloop_register_timeout(data->scan_interval, 0,
255                                        bgscan_learn_timeout, data, NULL);
256         } else if (!above) {
257                 struct os_time now;
258                 /*
259                  * Signal dropped further 4 dB. Request a new scan if we have
260                  * not yet scanned in a while.
261                  */
262                 os_get_time(&now);
263                 if (now.sec > data->last_bgscan.sec + 10) {
264                         wpa_printf(MSG_DEBUG, "bgscan learn: Trigger "
265                                    "immediate scan");
266                         eloop_cancel_timeout(bgscan_learn_timeout, data,
267                                              NULL);
268                         eloop_register_timeout(0, 0, bgscan_learn_timeout,
269                                                data, NULL);
270                 }
271         }
272 }
273
274
275 const struct bgscan_ops bgscan_learn_ops = {
276         .name = "learn",
277         .init = bgscan_learn_init,
278         .deinit = bgscan_learn_deinit,
279         .notify_scan = bgscan_learn_notify_scan,
280         .notify_beacon_loss = bgscan_learn_notify_beacon_loss,
281         .notify_signal_change = bgscan_learn_notify_signal_change,
282 };