plugins: Fix compilation in MeeGo
[platform/upstream/connman.git] / plugins / vpn.c
1 /*
2  *
3  *  Connection Manager
4  *
5  *  Copyright (C) 2007-2010  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 #define _GNU_SOURCE
27 #include <string.h>
28 #include <fcntl.h>
29 #include <unistd.h>
30 #include <sys/stat.h>
31 #include <stdio.h>
32 #include <errno.h>
33 #include <sys/ioctl.h>
34 #include <sys/types.h>
35 #include <linux/if_tun.h>
36 #include <net/if.h>
37
38 #include <dbus/dbus.h>
39
40 #include <glib/ghash.h>
41 #include <glib/gprintf.h>
42
43 #include <connman/provider.h>
44 #include <connman/log.h>
45 #include <connman/rtnl.h>
46 #include <connman/task.h>
47 #include <connman/inet.h>
48
49 #include "vpn.h"
50
51 struct vpn_data {
52         struct connman_provider *provider;
53         char *if_name;
54         unsigned flags;
55         unsigned int watch;
56         unsigned int state;
57         struct connman_task *task;
58 };
59
60 struct vpn_driver_data {
61         const char *name;
62         const char *program;
63         struct vpn_driver *vpn_driver;
64         struct connman_provider_driver provider_driver;
65 };
66
67 GHashTable *driver_hash = NULL;
68
69 static int kill_tun(char *tun_name)
70 {
71         struct ifreq ifr;
72         int fd, err;
73
74         memset(&ifr, 0, sizeof(ifr));
75         ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
76         sprintf(ifr.ifr_name, "%s", tun_name);
77
78         fd = open("/dev/net/tun", O_RDWR | O_CLOEXEC);
79         if (fd < 0) {
80                 err = -errno;
81                 connman_error("Failed to open /dev/net/tun to device %s: %s",
82                               tun_name, strerror(errno));
83                 return err;
84         }
85
86         if (ioctl(fd, TUNSETIFF, (void *)&ifr)) {
87                 err = -errno;
88                 connman_error("Failed to TUNSETIFF for device %s to it: %s",
89                               tun_name, strerror(errno));
90                 close(fd);
91                 return err;
92         }
93
94         if (ioctl(fd, TUNSETPERSIST, 0)) {
95                 err = -errno;
96                 connman_error("Failed to set tun device %s nonpersistent: %s",
97                               tun_name, strerror(errno));
98                 close(fd);
99                 return err;
100         }
101         close(fd);
102         DBG("Killed tun device %s", tun_name);
103         return 0;
104 }
105
106 void vpn_died(struct connman_task *task, int exit_code, void *user_data)
107 {
108         struct connman_provider *provider = user_data;
109         struct vpn_data *data = connman_provider_get_data(provider);
110         int state = VPN_STATE_FAILURE;
111         enum connman_provider_error ret;
112
113         DBG("provider %p data %p", provider, data);
114
115         if (data == NULL)
116                 goto vpn_exit;
117
118         state = data->state;
119
120         kill_tun(data->if_name);
121         connman_provider_set_data(provider, NULL);
122         connman_rtnl_remove_watch(data->watch);
123
124 vpn_exit:
125         if (state != VPN_STATE_READY && state != VPN_STATE_DISCONNECT) {
126                 const char *name;
127                 struct vpn_driver_data *vpn_data;
128
129                 name = connman_provider_get_driver_name(provider);
130                 vpn_data = g_hash_table_lookup(driver_hash, name);
131                 if (vpn_data != NULL &&
132                                 vpn_data->vpn_driver->error_code != NULL)
133                         ret = vpn_data->vpn_driver->error_code(exit_code);
134                 else
135                         ret = CONNMAN_PROVIDER_ERROR_UNKNOWN;
136
137                 connman_provider_indicate_error(provider, ret);
138         } else
139                 connman_provider_set_state(provider,
140                                                 CONNMAN_PROVIDER_STATE_IDLE);
141
142         connman_provider_set_index(provider, -1);
143         connman_provider_unref(data->provider);
144         g_free(data);
145
146         connman_task_destroy(task);
147 }
148
149 static void vpn_newlink(unsigned flags, unsigned change, void *user_data)
150 {
151         struct connman_provider *provider = user_data;
152         struct vpn_data *data = connman_provider_get_data(provider);
153
154         if ((data->flags & IFF_UP) != (flags & IFF_UP)) {
155                 if (flags & IFF_UP) {
156                         data->state = VPN_STATE_READY;
157                         connman_provider_set_state(provider,
158                                         CONNMAN_PROVIDER_STATE_READY);
159                 }
160         }
161         data->flags = flags;
162 }
163
164 static void vpn_notify(struct connman_task *task,
165                         DBusMessage *msg, void *user_data)
166 {
167         struct connman_provider *provider = user_data;
168         struct vpn_data *data;
169         struct vpn_driver_data *vpn_driver_data;
170         const char *name;
171         int state, index;
172
173         data = connman_provider_get_data(provider);
174
175         name = connman_provider_get_driver_name(provider);
176         vpn_driver_data = g_hash_table_lookup(driver_hash, name);
177         if (vpn_driver_data == NULL)
178                 return;
179
180         state = vpn_driver_data->vpn_driver->notify(msg, provider);
181         switch (state) {
182         case VPN_STATE_CONNECT:
183         case VPN_STATE_READY:
184                 index = connman_provider_get_index(provider);
185                 data->watch = connman_rtnl_add_newlink_watch(index,
186                                                      vpn_newlink, provider);
187                 connman_inet_ifup(index);
188                 break;
189
190         case VPN_STATE_UNKNOWN:
191         case VPN_STATE_IDLE:
192         case VPN_STATE_DISCONNECT:
193         case VPN_STATE_FAILURE:
194                 connman_provider_set_state(provider,
195                                         CONNMAN_PROVIDER_STATE_DISCONNECT);
196                 break;
197
198         case VPN_STATE_AUTH_FAILURE:
199                 connman_provider_indicate_error(provider,
200                                         CONNMAN_PROVIDER_ERROR_AUTH_FAILED);
201                 break;
202         }
203 }
204
205 static int vpn_connect(struct connman_provider *provider)
206 {
207         struct vpn_data *data = connman_provider_get_data(provider);
208         struct vpn_driver_data *vpn_driver_data;
209         struct ifreq ifr;
210         const char *name;
211         int i, fd, index;
212         int ret = 0;
213
214         if (data != NULL)
215                 return -EISCONN;
216
217         data = g_try_new0(struct vpn_data, 1);
218         if (data == NULL)
219                 return -ENOMEM;
220
221         data->provider = connman_provider_ref(provider);
222         data->watch = 0;
223         data->flags = 0;
224         data->task = NULL;
225         data->state = VPN_STATE_IDLE;
226
227         connman_provider_set_data(provider, data);
228
229         name = connman_provider_get_driver_name(provider);
230         vpn_driver_data = g_hash_table_lookup(driver_hash, name);
231
232         fd = open("/dev/net/tun", O_RDWR | O_CLOEXEC);
233         if (fd < 0) {
234                 i = -errno;
235                 connman_error("Failed to open /dev/net/tun: %s",
236                               strerror(errno));
237                 ret = i;
238                 goto exist_err;
239         }
240
241         memset(&ifr, 0, sizeof(ifr));
242         ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
243
244         for (i = 0; i < 256; i++) {
245                 sprintf(ifr.ifr_name, "vpn%d", i);
246
247                 if (!ioctl(fd, TUNSETIFF, (void *)&ifr))
248                         break;
249         }
250
251         if (i == 256) {
252                 connman_error("Failed to find available tun device");
253                 close(fd);
254                 ret = -ENODEV;
255                 goto exist_err;
256         }
257
258         data->if_name = (char *)g_strdup(ifr.ifr_name);
259         if (data->if_name == NULL) {
260                 connman_error("Failed to allocate memory");
261                 close(fd);
262                 ret = -ENOMEM;
263                 goto exist_err;
264         }
265
266         if (ioctl(fd, TUNSETPERSIST, 1)) {
267                 i = -errno;
268                 connman_error("Failed to set tun persistent: %s",
269                               strerror(errno));
270                 close(fd);
271                 ret = i;
272                 goto exist_err;
273         }
274
275         close(fd);
276
277         index = connman_inet_ifindex(data->if_name);
278         if (index < 0) {
279                 connman_error("Failed to get tun ifindex");
280                 kill_tun(data->if_name);
281                 ret = -EIO;
282                 goto exist_err;
283         }
284         connman_provider_set_index(provider, index);
285
286         data->task = connman_task_create(vpn_driver_data->program);
287
288         if (data->task == NULL) {
289                 ret = -ENOMEM;
290                 kill_tun(data->if_name);
291                 goto exist_err;
292         }
293
294         if (connman_task_set_notify(data->task, "notify",
295                                         vpn_notify, provider)) {
296                 ret = -ENOMEM;
297                 kill_tun(data->if_name);
298                 connman_task_destroy(data->task);
299                 data->task = NULL;
300                 goto exist_err;
301         }
302
303         ret = vpn_driver_data->vpn_driver->connect(provider, data->task,
304                                                         data->if_name);
305         if (ret < 0) {
306                 kill_tun(data->if_name);
307                 connman_task_destroy(data->task);
308                 data->task = NULL;
309                 goto exist_err;
310         }
311
312         DBG("%s started with dev %s",
313                 vpn_driver_data->provider_driver.name, data->if_name);
314
315         data->state = VPN_STATE_CONNECT;
316
317         return -EINPROGRESS;
318
319 exist_err:
320         connman_provider_set_index(provider, -1);
321         connman_provider_set_data(provider, NULL);
322         connman_provider_unref(data->provider);
323         g_free(data);
324
325         return ret;
326 }
327
328 static int vpn_probe(struct connman_provider *provider)
329 {
330         return 0;
331 }
332
333 static int vpn_disconnect(struct connman_provider *provider)
334 {
335         struct vpn_data *data = connman_provider_get_data(provider);
336         struct vpn_driver_data *vpn_driver_data;
337         const char *name;
338
339         DBG("disconnect provider %p:", provider);
340
341         if (data == NULL)
342                 return 0;
343
344         name = connman_provider_get_driver_name(provider);
345         vpn_driver_data = g_hash_table_lookup(driver_hash, name);
346         if (vpn_driver_data->vpn_driver->disconnect)
347                 vpn_driver_data->vpn_driver->disconnect();
348
349         if (data->watch != 0)
350                 connman_rtnl_remove_watch(data->watch);
351
352         data->watch = 0;
353         data->state = VPN_STATE_DISCONNECT;
354         connman_task_stop(data->task);
355
356         return 0;
357 }
358
359 static int vpn_remove(struct connman_provider *provider)
360 {
361         struct vpn_data *data;
362
363         data = connman_provider_get_data(provider);
364         connman_provider_set_data(provider, NULL);
365         if (data == NULL)
366                 return 0;
367
368         if (data->watch != 0)
369                 connman_rtnl_remove_watch(data->watch);
370         data->watch = 0;
371         connman_task_stop(data->task);
372
373         g_usleep(G_USEC_PER_SEC);
374         kill_tun(data->if_name);
375         return 0;
376 }
377
378 int vpn_register(const char *name, struct vpn_driver *vpn_driver,
379                         const char *program)
380 {
381         struct vpn_driver_data *data;
382
383         data = g_try_new0(struct vpn_driver_data, 1);
384         if (data == NULL)
385                 return -ENOMEM;
386
387         data->name = name;
388         data->program = program;
389
390         data->vpn_driver = vpn_driver;
391
392         data->provider_driver.name = name;
393         data->provider_driver.disconnect = vpn_disconnect;
394         data->provider_driver.connect = vpn_connect;
395         data->provider_driver.probe = vpn_probe;
396         data->provider_driver.remove = vpn_remove;
397
398         if (driver_hash == NULL) {
399                 driver_hash = g_hash_table_new_full(g_str_hash,
400                                                         g_str_equal,
401                                                         NULL, g_free);
402         }
403
404         g_hash_table_insert(driver_hash, (char *)name, data);
405
406         connman_provider_driver_register(&data->provider_driver);
407
408         return 0;
409 }
410
411 void vpn_unregister(const char *name)
412 {
413         struct vpn_driver_data *data;
414
415         data = g_hash_table_lookup(driver_hash, name);
416         if (data == NULL)
417                 return;
418
419         connman_provider_driver_unregister(&data->provider_driver);
420
421         g_hash_table_remove(driver_hash, name);
422
423         if (g_hash_table_size(driver_hash) == 0)
424                 g_hash_table_destroy(driver_hash);
425 }