Imported Upstream version 1.24
[platform/upstream/connman.git] / src / ipv6pd.c
1 /*
2  *
3  *  Connection Manager
4  *
5  *  Copyright (C) 2013  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 <errno.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <unistd.h>
30 #include <stdio.h>
31
32 #include "connman.h"
33
34 #include <gdhcp/gdhcp.h>
35
36 #define DEFAULT_ROUTER_LIFETIME 180  /* secs */
37 #define DEFAULT_RA_INTERVAL 120  /* secs */
38
39 static int bridge_index = -1;
40 static guint timer_uplink;
41 static guint timer_ra;
42 static char *default_interface;
43 static GSList *prefixes;
44 static GHashTable *timer_hash;
45 static void *rs_context;
46
47 static int setup_prefix_delegation(struct connman_service *service);
48 static void dhcpv6_callback(struct connman_network *network,
49                         enum __connman_dhcpv6_status status, gpointer data);
50
51 static int enable_ipv6_forward(bool enable)
52 {
53         FILE *f;
54
55         f = fopen("/proc/sys/net/ipv6/ip_forward", "r+");
56         if (!f)
57                 return -errno;
58
59         if (enable)
60                 fprintf(f, "1");
61         else
62                 fprintf(f, "0");
63
64         fclose(f);
65
66         return 0;
67 }
68
69 static gboolean send_ra(gpointer data)
70 {
71         __connman_inet_ipv6_send_ra(bridge_index, NULL, prefixes,
72                                         DEFAULT_ROUTER_LIFETIME);
73
74         return TRUE;
75 }
76
77 static void start_ra(int ifindex, GSList *prefix)
78 {
79         if (prefixes)
80                 g_slist_free_full(prefixes, g_free);
81
82         prefixes = g_dhcpv6_copy_prefixes(prefix);
83
84         enable_ipv6_forward(true);
85
86         if (timer_ra > 0)
87                 g_source_remove(timer_ra);
88
89         send_ra(NULL);
90
91         timer_ra = g_timeout_add_seconds(DEFAULT_RA_INTERVAL, send_ra, NULL);
92 }
93
94 static void stop_ra(int ifindex)
95 {
96         __connman_inet_ipv6_send_ra(ifindex, NULL, prefixes, 0);
97
98         if (timer_ra > 0) {
99                 g_source_remove(timer_ra);
100                 timer_ra = 0;
101         }
102
103         enable_ipv6_forward(false);
104
105         if (prefixes) {
106                 g_slist_free_full(prefixes, g_free);
107                 prefixes = NULL;
108         }
109 }
110
111 static void rs_received(struct nd_router_solicit *reply,
112                         unsigned int length, void *user_data)
113 {
114         GDHCPIAPrefix *prefix;
115         GSList *list;
116
117         if (!prefixes)
118                 return;
119
120         DBG("");
121
122         for (list = prefixes; list; list = list->next) {
123                 prefix = list->data;
124
125                 prefix->valid -= time(NULL) - prefix->expire;
126                 prefix->preferred -= time(NULL) - prefix->expire;
127         }
128
129         __connman_inet_ipv6_send_ra(bridge_index, NULL, prefixes,
130                                         DEFAULT_ROUTER_LIFETIME);
131 }
132
133 static gboolean do_setup(gpointer data)
134 {
135         int ret;
136
137         timer_uplink = 0;
138
139         if (!default_interface)
140                 DBG("No uplink connection, retrying prefix delegation");
141
142         ret = setup_prefix_delegation(__connman_service_get_default());
143         if (ret < 0 && ret != -EINPROGRESS)
144                 return TRUE; /* delegation error, try again */
145
146         return FALSE;
147 }
148
149 static void dhcpv6_renew_callback(struct connman_network *network,
150                                 enum __connman_dhcpv6_status status,
151                                 gpointer data)
152 {
153         DBG("network %p status %d data %p", network, status, data);
154
155         if (status == CONNMAN_DHCPV6_STATUS_SUCCEED)
156                 dhcpv6_callback(network, status, data);
157         else
158                 setup_prefix_delegation(__connman_service_get_default());
159 }
160
161 static void cleanup(void)
162 {
163         if (timer_uplink != 0) {
164                 g_source_remove(timer_uplink);
165                 timer_uplink = 0;
166         }
167
168         g_hash_table_destroy(timer_hash);
169         timer_hash = NULL;
170
171         if (prefixes) {
172                 g_slist_free_full(prefixes, g_free);
173                 prefixes = NULL;
174         }
175 }
176
177 static void dhcpv6_callback(struct connman_network *network,
178                         enum __connman_dhcpv6_status status, gpointer data)
179 {
180         GSList *prefix_list = data;
181
182         DBG("network %p status %d data %p", network, status, data);
183
184         if (status == CONNMAN_DHCPV6_STATUS_FAIL) {
185                 DBG("Prefix delegation request failed");
186                 cleanup();
187                 return;
188         }
189
190         if (!prefix_list) {
191                 DBG("No prefixes, retrying");
192                 if (timer_uplink == 0)
193                         timer_uplink = g_timeout_add_seconds(10, do_setup,
194                                                                         NULL);
195                 return;
196         }
197
198         /*
199          * After we have got a list of prefixes, we can start to send router
200          * advertisements (RA) to tethering interface.
201          */
202         start_ra(bridge_index, prefix_list);
203
204         if (__connman_dhcpv6_start_pd_renew(network,
205                                         dhcpv6_renew_callback) == -ETIMEDOUT)
206                 dhcpv6_renew_callback(network, CONNMAN_DHCPV6_STATUS_FAIL,
207                                                                         NULL);
208 }
209
210 static int setup_prefix_delegation(struct connman_service *service)
211 {
212         struct connman_ipconfig *ipconfig;
213         char *interface;
214         int err = 0, ifindex;
215
216         if (!service) {
217                 /*
218                  * We do not have uplink connection. We just wait until
219                  * default interface is updated.
220                  */
221                 return -EINPROGRESS;
222         }
223
224         interface = connman_service_get_interface(service);
225
226         DBG("interface %s bridge_index %d", interface, bridge_index);
227
228         if (default_interface) {
229                 stop_ra(bridge_index);
230
231                 ifindex = connman_inet_ifindex(default_interface);
232                 __connman_dhcpv6_stop_pd(ifindex);
233         }
234
235         g_free(default_interface);
236
237         ipconfig = __connman_service_get_ip6config(service);
238         if (!__connman_ipconfig_ipv6_is_enabled(ipconfig)) {
239                 g_free(interface);
240                 default_interface = NULL;
241                 return -EPFNOSUPPORT;
242         }
243
244         default_interface = interface;
245
246         if (default_interface) {
247                 ifindex = connman_inet_ifindex(default_interface);
248
249                 /*
250                  * Try to get a IPv6 prefix so we can start to advertise it.
251                  */
252                 err = __connman_dhcpv6_start_pd(ifindex, prefixes,
253                                                 dhcpv6_callback);
254                 if (err < 0)
255                         DBG("prefix delegation %d/%s", err, strerror(-err));
256         }
257
258         return err;
259 }
260
261 static void cleanup_timer(gpointer user_data)
262 {
263         guint timer = GPOINTER_TO_UINT(user_data);
264
265         g_source_remove(timer);
266 }
267
268 static void update_default_interface(struct connman_service *service)
269 {
270         setup_prefix_delegation(service);
271 }
272
273 static void update_ipconfig(struct connman_service *service,
274                                 struct connman_ipconfig *ipconfig)
275 {
276         if (!service || service != __connman_service_get_default())
277                 return;
278
279         if (ipconfig != __connman_service_get_ip6config(service))
280                 return;
281
282         if (!__connman_ipconfig_ipv6_is_enabled(ipconfig)) {
283                 if (default_interface) {
284                         int ifindex;
285
286                         ifindex = connman_inet_ifindex(default_interface);
287                         __connman_dhcpv6_stop_pd(ifindex);
288
289                         g_free(default_interface);
290                         default_interface = NULL;
291                 }
292
293                 DBG("No IPv6 support for interface index %d",
294                         __connman_ipconfig_get_index(ipconfig));
295                 return;
296         }
297
298         /*
299          * Did we had PD activated already? If not, then start it.
300          */
301         if (!default_interface) {
302                 DBG("IPv6 ipconfig %p changed for interface index %d", ipconfig,
303                         __connman_ipconfig_get_index(ipconfig));
304
305                 setup_prefix_delegation(service);
306         }
307 }
308
309 static struct connman_notifier pd_notifier = {
310         .name                   = "IPv6 prefix delegation",
311         .default_changed        = update_default_interface,
312         .ipconfig_changed       = update_ipconfig,
313 };
314
315 int __connman_ipv6pd_setup(const char *bridge)
316 {
317         struct connman_service *service;
318         int err;
319
320         if (!connman_inet_is_ipv6_supported())
321                 return -EPFNOSUPPORT;
322
323         if (bridge_index >= 0) {
324                 DBG("Prefix delegation already running");
325                 return -EALREADY;
326         }
327
328         err = connman_notifier_register(&pd_notifier);
329         if (err < 0)
330                 return err;
331
332         bridge_index = connman_inet_ifindex(bridge);
333
334         timer_hash = g_hash_table_new_full(g_direct_hash, g_direct_equal,
335                                                 NULL, cleanup_timer);
336
337         err = __connman_inet_ipv6_start_recv_rs(bridge_index, rs_received,
338                                                 NULL, &rs_context);
339         if (err < 0)
340                 DBG("Cannot receive router solicitation %d/%s",
341                         err, strerror(-err));
342
343         service = __connman_service_get_default();
344         if (service) {
345                 /*
346                  * We have an uplink connection already, try to use it.
347                  */
348                 return setup_prefix_delegation(service);
349         }
350
351         /*
352          * The prefix delegation is started after have got the uplink
353          * connection up i.e., when the default service is setup in which
354          * case the update_default_interface() will be called.
355          */
356         return -EINPROGRESS;
357 }
358
359 void __connman_ipv6pd_cleanup(void)
360 {
361         int ifindex;
362
363         if (!connman_inet_is_ipv6_supported())
364                 return;
365
366         connman_notifier_unregister(&pd_notifier);
367
368         __connman_inet_ipv6_stop_recv_rs(rs_context);
369         rs_context = NULL;
370
371         cleanup();
372
373         stop_ra(bridge_index);
374
375         if (default_interface) {
376                 ifindex = connman_inet_ifindex(default_interface);
377                 __connman_dhcpv6_stop_pd(ifindex);
378                 g_free(default_interface);
379                 default_interface = NULL;
380         }
381
382         bridge_index = -1;
383 }