dhcpv6: Support stateless DHCPv6
[framework/connectivity/connman.git] / src / dhcpv6.c
1 /*
2  *
3  *  Connection Manager
4  *
5  *  Copyright (C) 2011  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 #include <stdio.h>
26 #include <errno.h>
27 #include <string.h>
28 #include <stdlib.h>
29
30 #include <connman/ipconfig.h>
31 #include <connman/storage.h>
32
33 #include <gdhcp/gdhcp.h>
34
35 #include <glib.h>
36
37 #include "connman.h"
38
39 /* Transmission params in msec, RFC 3315 chapter 5.5 */
40 #define INF_MAX_DELAY   (1 * 1000)
41 #define INF_TIMEOUT     (1 * 1000)
42 #define INF_MAX_RT      (120 * 1000)
43
44
45 struct connman_dhcpv6 {
46         struct connman_network *network;
47         dhcp_cb callback;
48
49         char **nameservers;
50         char **timeservers;
51
52         GDHCPClient *dhcp_client;
53
54         guint timeout;
55         guint RT;       /* in msec */
56 };
57
58 static GHashTable *network_table;
59
60 static inline float get_random()
61 {
62         return (rand() % 200 - 100) / 1000.0;
63 }
64
65 /* Calculate a random delay, RFC 3315 chapter 14 */
66 /* RT and MRT are milliseconds */
67 static guint calc_delay(guint RT, guint MRT)
68 {
69         float delay = get_random();
70         float rt = RT * (2 + delay);
71
72         if (rt > MRT)
73                 rt = MRT * (1 + delay);
74
75         if (rt < 0)
76                 rt = MRT;
77
78         return (guint)rt;
79 }
80
81 static void dhcpv6_free(struct connman_dhcpv6 *dhcp)
82 {
83         g_strfreev(dhcp->nameservers);
84         g_strfreev(dhcp->timeservers);
85
86         dhcp->nameservers = NULL;
87         dhcp->timeservers = NULL;
88 }
89
90 static gboolean compare_string_arrays(char **array_a, char **array_b)
91 {
92         int i;
93
94         if (array_a == NULL || array_b == NULL)
95                 return FALSE;
96
97         if (g_strv_length(array_a) != g_strv_length(array_b))
98                 return FALSE;
99
100         for (i = 0; array_a[i] != NULL && array_b[i] != NULL; i++)
101                 if (g_strcmp0(array_a[i], array_b[i]) != 0)
102                         return FALSE;
103
104         return TRUE;
105 }
106
107 static void dhcpv6_debug(const char *str, void *data)
108 {
109         connman_info("%s: %s\n", (const char *) data, str);
110 }
111
112 static gchar *convert_to_hex(unsigned char *buf, int len)
113 {
114         gchar *ret = g_try_malloc(len * 2 + 1);
115         int i;
116
117         for (i = 0; ret != NULL && i < len; i++)
118                 g_snprintf(ret + i * 2, 3, "%02x", buf[i]);
119
120         return ret;
121 }
122
123 /*
124  * DUID should not change over time so save it to file.
125  * See RFC 3315 chapter 9 for details.
126  */
127 static int set_duid(struct connman_service *service,
128                         struct connman_network *network,
129                         GDHCPClient *dhcp_client, int index)
130 {
131         GKeyFile *keyfile;
132         const char *ident;
133         char *hex_duid;
134         unsigned char *duid;
135         int duid_len;
136
137         ident = __connman_service_get_ident(service);
138
139         keyfile = connman_storage_load_service(ident);
140         if (keyfile == NULL)
141                 return -EINVAL;
142
143         hex_duid = g_key_file_get_string(keyfile, ident, "IPv6.DHCP.DUID",
144                                         NULL);
145         if (hex_duid != NULL) {
146                 unsigned int i, j = 0, hex;
147                 size_t hex_duid_len = strlen(hex_duid);
148
149                 duid = g_try_malloc0(hex_duid_len / 2);
150                 if (duid == NULL) {
151                         g_key_file_free(keyfile);
152                         g_free(hex_duid);
153                         return -ENOMEM;
154                 }
155
156                 for (i = 0; i < hex_duid_len; i += 2) {
157                         sscanf(hex_duid + i, "%02x", &hex);
158                         duid[j++] = hex;
159                 }
160
161                 duid_len = hex_duid_len / 2;
162         } else {
163                 int ret;
164                 int type = __connman_ipconfig_get_type_from_index(index);
165
166                 ret = g_dhcpv6_create_duid(G_DHCPV6_DUID_LLT, index, type,
167                                         &duid, &duid_len);
168                 if (ret < 0) {
169                         g_key_file_free(keyfile);
170                         return ret;
171                 }
172
173                 hex_duid = convert_to_hex(duid, duid_len);
174                 if (hex_duid == NULL) {
175                         g_key_file_free(keyfile);
176                         return -ENOMEM;
177                 }
178
179                 g_key_file_set_string(keyfile, ident, "IPv6.DHCP.DUID",
180                                 hex_duid);
181
182                 __connman_storage_save_service(keyfile, ident);
183         }
184         g_free(hex_duid);
185
186         g_key_file_free(keyfile);
187
188         g_dhcpv6_client_set_duid(dhcp_client, duid, duid_len);
189
190         return 0;
191 }
192
193 static void info_req_cb(GDHCPClient *dhcp_client, gpointer user_data)
194 {
195         struct connman_dhcpv6 *dhcp = user_data;
196         struct connman_service *service;
197         int entries, i;
198         GList *option, *list;
199         char **nameservers, **timeservers;
200
201         DBG("dhcpv6 information-request %p", dhcp);
202
203         service = __connman_service_lookup_from_network(dhcp->network);
204         if (service == NULL) {
205                 connman_error("Can not lookup service");
206                 return;
207         }
208
209         option = g_dhcp_client_get_option(dhcp_client, G_DHCPV6_DNS_SERVERS);
210         entries = g_list_length(option);
211
212         nameservers = g_try_new0(char *, entries + 1);
213         if (nameservers != NULL) {
214                 for (i = 0, list = option; list; list = list->next, i++)
215                         nameservers[i] = g_strdup(list->data);
216         }
217
218         if (compare_string_arrays(nameservers, dhcp->nameservers) == FALSE) {
219                 if (dhcp->nameservers != NULL) {
220                         for (i = 0; dhcp->nameservers[i] != NULL; i++)
221                                 __connman_service_nameserver_remove(service,
222                                                         dhcp->nameservers[i],
223                                                         FALSE);
224                         g_strfreev(dhcp->nameservers);
225                 }
226
227                 dhcp->nameservers = nameservers;
228
229                 for (i = 0; dhcp->nameservers[i] != NULL; i++)
230                         __connman_service_nameserver_append(service,
231                                                 dhcp->nameservers[i],
232                                                 FALSE);
233         } else
234                 g_strfreev(nameservers);
235
236
237         option = g_dhcp_client_get_option(dhcp_client, G_DHCPV6_SNTP_SERVERS);
238         entries = g_list_length(option);
239
240         timeservers = g_try_new0(char *, entries + 1);
241         if (timeservers != NULL) {
242                 for (i = 0, list = option; list; list = list->next, i++)
243                         timeservers[i] = g_strdup(list->data);
244         }
245
246         if (compare_string_arrays(timeservers, dhcp->timeservers) == FALSE) {
247                 if (dhcp->timeservers != NULL) {
248                         for (i = 0; dhcp->timeservers[i] != NULL; i++)
249                                 __connman_service_timeserver_remove(service,
250                                                         dhcp->timeservers[i]);
251                         g_strfreev(dhcp->timeservers);
252                 }
253
254                 dhcp->timeservers = timeservers;
255
256                 for (i = 0; dhcp->timeservers[i] != NULL; i++)
257                         __connman_service_timeserver_append(service,
258                                                         dhcp->timeservers[i]);
259         } else
260                 g_strfreev(timeservers);
261
262
263         if (dhcp->callback != NULL) {
264                 uint16_t status = g_dhcpv6_client_get_status(dhcp_client);
265                 dhcp->callback(dhcp->network, status == 0 ? TRUE : FALSE);
266         }
267 }
268
269 static int dhcpv6_info_request(struct connman_dhcpv6 *dhcp)
270 {
271         struct connman_service *service;
272         GDHCPClient *dhcp_client;
273         GDHCPClientError error;
274         int index, ret;
275
276         DBG("dhcp %p", dhcp);
277
278         index = connman_network_get_index(dhcp->network);
279
280         dhcp_client = g_dhcp_client_new(G_DHCP_IPV6, index, &error);
281         if (error != G_DHCP_CLIENT_ERROR_NONE)
282                 return -EINVAL;
283
284         if (getenv("CONNMAN_DHCPV6_DEBUG"))
285                 g_dhcp_client_set_debug(dhcp_client, dhcpv6_debug, "DHCPv6");
286
287         service = __connman_service_lookup_from_network(dhcp->network);
288         if (service == NULL)
289                 return -EINVAL;
290
291         ret = set_duid(service, dhcp->network, dhcp_client, index);
292         if (ret < 0)
293                 return ret;
294
295         g_dhcp_client_set_request(dhcp_client, G_DHCPV6_CLIENTID);
296         g_dhcp_client_set_request(dhcp_client, G_DHCPV6_DNS_SERVERS);
297         g_dhcp_client_set_request(dhcp_client, G_DHCPV6_SNTP_SERVERS);
298
299         g_dhcpv6_client_set_oro(dhcp_client, 2, G_DHCPV6_DNS_SERVERS,
300                                 G_DHCPV6_SNTP_SERVERS);
301
302         g_dhcp_client_register_event(dhcp_client,
303                         G_DHCP_CLIENT_EVENT_INFORMATION_REQ, info_req_cb, dhcp);
304
305         dhcp->dhcp_client = dhcp_client;
306
307         return g_dhcp_client_start(dhcp_client, NULL);
308 }
309
310 static int dhcpv6_release(struct connman_dhcpv6 *dhcp)
311 {
312         DBG("dhcp %p", dhcp);
313
314         if (dhcp->timeout > 0) {
315                 g_source_remove(dhcp->timeout);
316                 dhcp->timeout = 0;
317         }
318
319         dhcpv6_free(dhcp);
320
321         if (dhcp->dhcp_client == NULL)
322                 return 0;
323
324         g_dhcp_client_stop(dhcp->dhcp_client);
325         g_dhcp_client_unref(dhcp->dhcp_client);
326
327         dhcp->dhcp_client = NULL;
328
329         return 0;
330 }
331
332 static void remove_network(gpointer user_data)
333 {
334         struct connman_dhcpv6 *dhcp = user_data;
335
336         DBG("dhcp %p", dhcp);
337
338         dhcpv6_release(dhcp);
339
340         g_free(dhcp);
341 }
342
343 static gboolean timeout_info_req(gpointer user_data)
344 {
345         struct connman_dhcpv6 *dhcp = user_data;
346
347         dhcp->RT = calc_delay(dhcp->RT, INF_MAX_RT);
348
349         DBG("info RT timeout %d msec", dhcp->RT);
350
351         dhcp->timeout = g_timeout_add(dhcp->RT, timeout_info_req, dhcp);
352
353         g_dhcp_client_start(dhcp->dhcp_client, NULL);
354
355         return FALSE;
356 }
357
358 static gboolean start_info_req(gpointer user_data)
359 {
360         struct connman_dhcpv6 *dhcp = user_data;
361
362         /* Set the retransmission timeout, RFC 3315 chapter 14 */
363         dhcp->RT = INF_TIMEOUT * (1 + get_random());
364
365         DBG("info initial RT timeout %d msec", dhcp->RT);
366
367         dhcp->timeout = g_timeout_add(dhcp->RT, timeout_info_req, dhcp);
368
369         dhcpv6_info_request(dhcp);
370
371         return FALSE;
372 }
373
374 int __connman_dhcpv6_start_info(struct connman_network *network,
375                                 dhcp_cb callback)
376 {
377         struct connman_dhcpv6 *dhcp;
378         int delay;
379
380         DBG("");
381
382         dhcp = g_try_new0(struct connman_dhcpv6, 1);
383         if (dhcp == NULL)
384                 return -ENOMEM;
385
386         dhcp->network = network;
387         dhcp->callback = callback;
388
389         connman_network_ref(network);
390
391         g_hash_table_replace(network_table, network, dhcp);
392
393         /* Initial timeout, RFC 3315, 18.1.5 */
394         delay = rand() % 1000;
395
396         dhcp->timeout = g_timeout_add(delay, start_info_req, dhcp);
397
398         return 0;
399 }
400
401 void __connman_dhcpv6_stop(struct connman_network *network)
402 {
403         DBG("");
404
405         if (network_table == NULL)
406                 return;
407
408         if (g_hash_table_remove(network_table, network) == TRUE)
409                 connman_network_unref(network);
410 }
411
412 int __connman_dhcpv6_init(void)
413 {
414         DBG("");
415
416         srand(time(0));
417
418         network_table = g_hash_table_new_full(g_direct_hash, g_direct_equal,
419                                                         NULL, remove_network);
420
421         return 0;
422 }
423
424 void __connman_dhcpv6_cleanup(void)
425 {
426         DBG("");
427
428         g_hash_table_destroy(network_table);
429         network_table = NULL;
430 }