a19eee06dbcd3860199317be62fe314ce85594bd
[profile/ivi/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 #define SOL_MAX_DELAY   (1 * 1000)
44 #define SOL_TIMEOUT     (1 * 1000)
45 #define SOL_MAX_RT      (120 * 1000)
46
47
48 struct connman_dhcpv6 {
49         struct connman_network *network;
50         dhcp_cb callback;
51
52         char **nameservers;
53         char **timeservers;
54
55         GDHCPClient *dhcp_client;
56
57         guint timeout;          /* operation timeout in msec */
58         guint RT;               /* in msec */
59         gboolean use_ta;        /* set to TRUE if IPv6 privacy is enabled */
60         GSList *prefixes;       /* network prefixes from radvd */
61 };
62
63 static GHashTable *network_table;
64
65 static inline float get_random()
66 {
67         return (rand() % 200 - 100) / 1000.0;
68 }
69
70 /* Calculate a random delay, RFC 3315 chapter 14 */
71 /* RT and MRT are milliseconds */
72 static guint calc_delay(guint RT, guint MRT)
73 {
74         float delay = get_random();
75         float rt = RT * (2 + delay);
76
77         if (rt > MRT)
78                 rt = MRT * (1 + delay);
79
80         if (rt < 0)
81                 rt = MRT;
82
83         return (guint)rt;
84 }
85
86 static void free_prefix(gpointer data, gpointer user_data)
87 {
88         g_free(data);
89 }
90
91 static void dhcpv6_free(struct connman_dhcpv6 *dhcp)
92 {
93         g_strfreev(dhcp->nameservers);
94         g_strfreev(dhcp->timeservers);
95
96         dhcp->nameservers = NULL;
97         dhcp->timeservers = NULL;
98
99         g_slist_foreach(dhcp->prefixes, free_prefix, NULL);
100         g_slist_free(dhcp->prefixes);
101 }
102
103 static gboolean compare_string_arrays(char **array_a, char **array_b)
104 {
105         int i;
106
107         if (array_a == NULL || array_b == NULL)
108                 return FALSE;
109
110         if (g_strv_length(array_a) != g_strv_length(array_b))
111                 return FALSE;
112
113         for (i = 0; array_a[i] != NULL && array_b[i] != NULL; i++)
114                 if (g_strcmp0(array_a[i], array_b[i]) != 0)
115                         return FALSE;
116
117         return TRUE;
118 }
119
120 static void dhcpv6_debug(const char *str, void *data)
121 {
122         connman_info("%s: %s\n", (const char *) data, str);
123 }
124
125 static gchar *convert_to_hex(unsigned char *buf, int len)
126 {
127         gchar *ret = g_try_malloc(len * 2 + 1);
128         int i;
129
130         for (i = 0; ret != NULL && i < len; i++)
131                 g_snprintf(ret + i * 2, 3, "%02x", buf[i]);
132
133         return ret;
134 }
135
136 /*
137  * DUID should not change over time so save it to file.
138  * See RFC 3315 chapter 9 for details.
139  */
140 static int set_duid(struct connman_service *service,
141                         struct connman_network *network,
142                         GDHCPClient *dhcp_client, int index)
143 {
144         GKeyFile *keyfile;
145         const char *ident;
146         char *hex_duid;
147         unsigned char *duid;
148         int duid_len;
149
150         ident = __connman_service_get_ident(service);
151
152         keyfile = connman_storage_load_service(ident);
153         if (keyfile == NULL)
154                 return -EINVAL;
155
156         hex_duid = g_key_file_get_string(keyfile, ident, "IPv6.DHCP.DUID",
157                                         NULL);
158         if (hex_duid != NULL) {
159                 unsigned int i, j = 0, hex;
160                 size_t hex_duid_len = strlen(hex_duid);
161
162                 duid = g_try_malloc0(hex_duid_len / 2);
163                 if (duid == NULL) {
164                         g_key_file_free(keyfile);
165                         g_free(hex_duid);
166                         return -ENOMEM;
167                 }
168
169                 for (i = 0; i < hex_duid_len; i += 2) {
170                         sscanf(hex_duid + i, "%02x", &hex);
171                         duid[j++] = hex;
172                 }
173
174                 duid_len = hex_duid_len / 2;
175         } else {
176                 int ret;
177                 int type = __connman_ipconfig_get_type_from_index(index);
178
179                 ret = g_dhcpv6_create_duid(G_DHCPV6_DUID_LLT, index, type,
180                                         &duid, &duid_len);
181                 if (ret < 0) {
182                         g_key_file_free(keyfile);
183                         return ret;
184                 }
185
186                 hex_duid = convert_to_hex(duid, duid_len);
187                 if (hex_duid == NULL) {
188                         g_key_file_free(keyfile);
189                         return -ENOMEM;
190                 }
191
192                 g_key_file_set_string(keyfile, ident, "IPv6.DHCP.DUID",
193                                 hex_duid);
194
195                 __connman_storage_save_service(keyfile, ident);
196         }
197         g_free(hex_duid);
198
199         g_key_file_free(keyfile);
200
201         g_dhcpv6_client_set_duid(dhcp_client, duid, duid_len);
202
203         return 0;
204 }
205
206 static void clear_callbacks(GDHCPClient *dhcp_client)
207 {
208         g_dhcp_client_register_event(dhcp_client,
209                                 G_DHCP_CLIENT_EVENT_SOLICITATION,
210                                 NULL, NULL);
211
212         g_dhcp_client_register_event(dhcp_client,
213                                 G_DHCP_CLIENT_EVENT_ADVERTISE,
214                                 NULL, NULL);
215
216         g_dhcp_client_register_event(dhcp_client,
217                                 G_DHCP_CLIENT_EVENT_INFORMATION_REQ,
218                                 NULL, NULL);
219 }
220
221 static void info_req_cb(GDHCPClient *dhcp_client, gpointer user_data)
222 {
223         struct connman_dhcpv6 *dhcp = user_data;
224         struct connman_service *service;
225         int entries, i;
226         GList *option, *list;
227         char **nameservers, **timeservers;
228
229         DBG("dhcpv6 information-request %p", dhcp);
230
231         service = __connman_service_lookup_from_network(dhcp->network);
232         if (service == NULL) {
233                 connman_error("Can not lookup service");
234                 return;
235         }
236
237         option = g_dhcp_client_get_option(dhcp_client, G_DHCPV6_DNS_SERVERS);
238         entries = g_list_length(option);
239
240         nameservers = g_try_new0(char *, entries + 1);
241         if (nameservers != NULL) {
242                 for (i = 0, list = option; list; list = list->next, i++)
243                         nameservers[i] = g_strdup(list->data);
244         }
245
246         if (compare_string_arrays(nameservers, dhcp->nameservers) == FALSE) {
247                 if (dhcp->nameservers != NULL) {
248                         for (i = 0; dhcp->nameservers[i] != NULL; i++)
249                                 __connman_service_nameserver_remove(service,
250                                                         dhcp->nameservers[i],
251                                                         FALSE);
252                         g_strfreev(dhcp->nameservers);
253                 }
254
255                 dhcp->nameservers = nameservers;
256
257                 for (i = 0; dhcp->nameservers[i] != NULL; i++)
258                         __connman_service_nameserver_append(service,
259                                                 dhcp->nameservers[i],
260                                                 FALSE);
261         } else
262                 g_strfreev(nameservers);
263
264
265         option = g_dhcp_client_get_option(dhcp_client, G_DHCPV6_SNTP_SERVERS);
266         entries = g_list_length(option);
267
268         timeservers = g_try_new0(char *, entries + 1);
269         if (timeservers != NULL) {
270                 for (i = 0, list = option; list; list = list->next, i++)
271                         timeservers[i] = g_strdup(list->data);
272         }
273
274         if (compare_string_arrays(timeservers, dhcp->timeservers) == FALSE) {
275                 if (dhcp->timeservers != NULL) {
276                         for (i = 0; dhcp->timeservers[i] != NULL; i++)
277                                 __connman_service_timeserver_remove(service,
278                                                         dhcp->timeservers[i]);
279                         g_strfreev(dhcp->timeservers);
280                 }
281
282                 dhcp->timeservers = timeservers;
283
284                 for (i = 0; dhcp->timeservers[i] != NULL; i++)
285                         __connman_service_timeserver_append(service,
286                                                         dhcp->timeservers[i]);
287         } else
288                 g_strfreev(timeservers);
289
290
291         if (dhcp->callback != NULL) {
292                 uint16_t status = g_dhcpv6_client_get_status(dhcp_client);
293                 dhcp->callback(dhcp->network, status == 0 ? TRUE : FALSE);
294         }
295 }
296
297 static int dhcpv6_info_request(struct connman_dhcpv6 *dhcp)
298 {
299         struct connman_service *service;
300         GDHCPClient *dhcp_client;
301         GDHCPClientError error;
302         int index, ret;
303
304         DBG("dhcp %p", dhcp);
305
306         index = connman_network_get_index(dhcp->network);
307
308         dhcp_client = g_dhcp_client_new(G_DHCP_IPV6, index, &error);
309         if (error != G_DHCP_CLIENT_ERROR_NONE)
310                 return -EINVAL;
311
312         if (getenv("CONNMAN_DHCPV6_DEBUG"))
313                 g_dhcp_client_set_debug(dhcp_client, dhcpv6_debug, "DHCPv6");
314
315         service = __connman_service_lookup_from_network(dhcp->network);
316         if (service == NULL)
317                 return -EINVAL;
318
319         ret = set_duid(service, dhcp->network, dhcp_client, index);
320         if (ret < 0)
321                 return ret;
322
323         g_dhcp_client_set_request(dhcp_client, G_DHCPV6_CLIENTID);
324         g_dhcp_client_set_request(dhcp_client, G_DHCPV6_DNS_SERVERS);
325         g_dhcp_client_set_request(dhcp_client, G_DHCPV6_SNTP_SERVERS);
326
327         g_dhcpv6_client_set_oro(dhcp_client, 2, G_DHCPV6_DNS_SERVERS,
328                                 G_DHCPV6_SNTP_SERVERS);
329
330         g_dhcp_client_register_event(dhcp_client,
331                         G_DHCP_CLIENT_EVENT_INFORMATION_REQ, info_req_cb, dhcp);
332
333         dhcp->dhcp_client = dhcp_client;
334
335         return g_dhcp_client_start(dhcp_client, NULL);
336 }
337
338 static int check_ipv6_addr_prefix(GSList *prefixes, char *address)
339 {
340         struct in6_addr addr_prefix, addr;
341         GSList *list;
342         int ret = 128, len;
343
344         for (list = prefixes; list; list = list->next) {
345                 char *prefix = list->data;
346                 const char *slash = g_strrstr(prefix, "/");
347                 const unsigned char bits[] = { 0x00, 0xFE, 0xFC, 0xF8,
348                                                 0xF0, 0xE0, 0xC0, 0x80 };
349                 int left, count, i, plen;
350
351                 if (slash == NULL)
352                         continue;
353
354                 prefix = g_strndup(prefix, slash - prefix);
355                 len = strtol(slash + 1, NULL, 10);
356                 plen = 128 - len;
357
358                 count = plen / 8;
359                 left = plen % 8;
360                 i = 16 - count;
361
362                 inet_pton(AF_INET6, prefix, &addr_prefix);
363                 inet_pton(AF_INET6, address, &addr);
364
365                 memset(&addr_prefix.s6_addr[i], 0, count);
366                 memset(&addr.s6_addr[i], 0, count);
367
368                 if (left) {
369                         addr_prefix.s6_addr[i - 1] &= bits[left];
370                         addr.s6_addr[i - 1] &= bits[left];
371                 }
372
373                 g_free(prefix);
374
375                 if (memcmp(&addr_prefix, &addr, 16) == 0) {
376                         ret = len;
377                         break;
378                 }
379         }
380
381         return ret;
382 }
383
384 static int set_addresses(GDHCPClient *dhcp_client,
385                                                 struct connman_dhcpv6 *dhcp)
386 {
387         struct connman_service *service;
388         struct connman_ipconfig *ipconfig;
389         int entries, i;
390         GList *option, *list;
391         char **nameservers, **timeservers;
392         const char *c_address;
393         char *address = NULL;
394
395         service = __connman_service_lookup_from_network(dhcp->network);
396         if (service == NULL) {
397                 connman_error("Can not lookup service");
398                 return -EINVAL;
399         }
400
401         ipconfig = __connman_service_get_ip6config(service);
402         if (ipconfig == NULL) {
403                 connman_error("Could not lookup ip6config");
404                 return -EINVAL;
405         }
406
407         option = g_dhcp_client_get_option(dhcp_client, G_DHCPV6_DNS_SERVERS);
408         entries = g_list_length(option);
409
410         nameservers = g_try_new0(char *, entries + 1);
411         if (nameservers != NULL) {
412                 for (i = 0, list = option; list; list = list->next, i++)
413                         nameservers[i] = g_strdup(list->data);
414         }
415
416         if (compare_string_arrays(nameservers, dhcp->nameservers) == FALSE) {
417                 if (dhcp->nameservers != NULL) {
418                         for (i = 0; dhcp->nameservers[i] != NULL; i++)
419                                 __connman_service_nameserver_remove(service,
420                                                         dhcp->nameservers[i],
421                                                         FALSE);
422                         g_strfreev(dhcp->nameservers);
423                 }
424
425                 dhcp->nameservers = nameservers;
426
427                 for (i = 0; dhcp->nameservers[i] != NULL; i++)
428                         __connman_service_nameserver_append(service,
429                                                         dhcp->nameservers[i],
430                                                         FALSE);
431         } else
432                 g_strfreev(nameservers);
433
434
435         option = g_dhcp_client_get_option(dhcp_client, G_DHCPV6_SNTP_SERVERS);
436         entries = g_list_length(option);
437
438         timeservers = g_try_new0(char *, entries + 1);
439         if (timeservers != NULL) {
440                 for (i = 0, list = option; list; list = list->next, i++)
441                         timeservers[i] = g_strdup(list->data);
442         }
443
444         if (compare_string_arrays(timeservers, dhcp->timeservers) == FALSE) {
445                 if (dhcp->timeservers != NULL) {
446                         for (i = 0; dhcp->timeservers[i] != NULL; i++)
447                                 __connman_service_timeserver_remove(service,
448                                                         dhcp->timeservers[i]);
449                         g_strfreev(dhcp->timeservers);
450                 }
451
452                 dhcp->timeservers = timeservers;
453
454                 for (i = 0; dhcp->timeservers[i] != NULL; i++)
455                         __connman_service_timeserver_append(service,
456                                                         dhcp->timeservers[i]);
457         } else
458                 g_strfreev(timeservers);
459
460
461         option = g_dhcp_client_get_option(dhcp_client, G_DHCPV6_IA_NA);
462         if (option != NULL)
463                 address = g_strdup(option->data);
464         else {
465                 option = g_dhcp_client_get_option(dhcp_client, G_DHCPV6_IA_TA);
466                 if (option != NULL)
467                         address = g_strdup(option->data);
468         }
469
470         c_address = __connman_ipconfig_get_local(ipconfig);
471
472         if (address != NULL &&
473                         ((c_address != NULL &&
474                                 g_strcmp0(address, c_address) != 0) ||
475                         (c_address == NULL))) {
476                 int prefix_len;
477
478                 /* Is this prefix part of the subnet we are suppose to use? */
479                 prefix_len = check_ipv6_addr_prefix(dhcp->prefixes, address);
480
481                 __connman_ipconfig_set_local(ipconfig, address);
482                 __connman_ipconfig_set_prefixlen(ipconfig, prefix_len);
483
484                 DBG("new address %s/%d", address, prefix_len);
485         }
486
487         return 0;
488 }
489
490 static int dhcpv6_release(struct connman_dhcpv6 *dhcp)
491 {
492         DBG("dhcp %p", dhcp);
493
494         if (dhcp->timeout > 0) {
495                 g_source_remove(dhcp->timeout);
496                 dhcp->timeout = 0;
497         }
498
499         dhcpv6_free(dhcp);
500
501         if (dhcp->dhcp_client == NULL)
502                 return 0;
503
504         g_dhcp_client_stop(dhcp->dhcp_client);
505         g_dhcp_client_unref(dhcp->dhcp_client);
506
507         dhcp->dhcp_client = NULL;
508
509         return 0;
510 }
511
512 static void remove_network(gpointer user_data)
513 {
514         struct connman_dhcpv6 *dhcp = user_data;
515
516         DBG("dhcp %p", dhcp);
517
518         dhcpv6_release(dhcp);
519
520         g_free(dhcp);
521 }
522
523 static gboolean timeout_info_req(gpointer user_data)
524 {
525         struct connman_dhcpv6 *dhcp = user_data;
526
527         dhcp->RT = calc_delay(dhcp->RT, INF_MAX_RT);
528
529         DBG("info RT timeout %d msec", dhcp->RT);
530
531         dhcp->timeout = g_timeout_add(dhcp->RT, timeout_info_req, dhcp);
532
533         g_dhcp_client_start(dhcp->dhcp_client, NULL);
534
535         return FALSE;
536 }
537
538 static gboolean start_info_req(gpointer user_data)
539 {
540         struct connman_dhcpv6 *dhcp = user_data;
541
542         /* Set the retransmission timeout, RFC 3315 chapter 14 */
543         dhcp->RT = INF_TIMEOUT * (1 + get_random());
544
545         DBG("info initial RT timeout %d msec", dhcp->RT);
546
547         dhcp->timeout = g_timeout_add(dhcp->RT, timeout_info_req, dhcp);
548
549         dhcpv6_info_request(dhcp);
550
551         return FALSE;
552 }
553
554 int __connman_dhcpv6_start_info(struct connman_network *network,
555                                 dhcp_cb callback)
556 {
557         struct connman_dhcpv6 *dhcp;
558         int delay;
559
560         DBG("");
561
562         dhcp = g_try_new0(struct connman_dhcpv6, 1);
563         if (dhcp == NULL)
564                 return -ENOMEM;
565
566         dhcp->network = network;
567         dhcp->callback = callback;
568
569         connman_network_ref(network);
570
571         g_hash_table_replace(network_table, network, dhcp);
572
573         /* Initial timeout, RFC 3315, 18.1.5 */
574         delay = rand() % 1000;
575
576         dhcp->timeout = g_timeout_add(delay, start_info_req, dhcp);
577
578         return 0;
579 }
580
581 static void advertise_cb(GDHCPClient *dhcp_client, gpointer user_data)
582 {
583         struct connman_dhcpv6 *dhcp = user_data;
584
585         DBG("dhcpv6 advertise msg %p", dhcp);
586 }
587
588 static void solicitation_cb(GDHCPClient *dhcp_client, gpointer user_data)
589 {
590         /* We get here if server supports rapid commit */
591         struct connman_dhcpv6 *dhcp = user_data;
592
593         DBG("dhcpv6 solicitation msg %p", dhcp);
594
595         if (dhcp->timeout > 0) {
596                 g_source_remove(dhcp->timeout);
597                 dhcp->timeout = 0;
598         }
599
600         set_addresses(dhcp_client, dhcp);
601 }
602
603 static gboolean timeout_solicitation(gpointer user_data)
604 {
605         struct connman_dhcpv6 *dhcp = user_data;
606
607         dhcp->RT = calc_delay(dhcp->RT, SOL_MAX_RT);
608
609         DBG("solicit RT timeout %d msec", dhcp->RT);
610
611         dhcp->timeout = g_timeout_add(dhcp->RT, timeout_solicitation, dhcp);
612
613         g_dhcp_client_start(dhcp->dhcp_client, NULL);
614
615         return FALSE;
616 }
617
618 static int dhcpv6_solicitation(struct connman_dhcpv6 *dhcp)
619 {
620         struct connman_service *service;
621         struct connman_ipconfig *ipconfig_ipv6;
622         GDHCPClient *dhcp_client;
623         GDHCPClientError error;
624         int index, ret;
625
626         DBG("dhcp %p", dhcp);
627
628         index = connman_network_get_index(dhcp->network);
629
630         dhcp_client = g_dhcp_client_new(G_DHCP_IPV6, index, &error);
631         if (error != G_DHCP_CLIENT_ERROR_NONE)
632                 return -EINVAL;
633
634         if (getenv("CONNMAN_DHCPV6_DEBUG"))
635                 g_dhcp_client_set_debug(dhcp_client, dhcpv6_debug, "DHCPv6");
636
637         service = __connman_service_lookup_from_network(dhcp->network);
638         if (service == NULL)
639                 return -EINVAL;
640
641         ret = set_duid(service, dhcp->network, dhcp_client, index);
642         if (ret < 0)
643                 return ret;
644
645         g_dhcp_client_set_request(dhcp_client, G_DHCPV6_CLIENTID);
646         g_dhcp_client_set_request(dhcp_client, G_DHCPV6_RAPID_COMMIT);
647         g_dhcp_client_set_request(dhcp_client, G_DHCPV6_DNS_SERVERS);
648         g_dhcp_client_set_request(dhcp_client, G_DHCPV6_SNTP_SERVERS);
649
650         g_dhcpv6_client_set_oro(dhcp_client, 2, G_DHCPV6_DNS_SERVERS,
651                                 G_DHCPV6_SNTP_SERVERS);
652
653         ipconfig_ipv6 = __connman_service_get_ip6config(service);
654         dhcp->use_ta = __connman_ipconfig_ipv6_privacy_enabled(ipconfig_ipv6);
655
656         g_dhcpv6_client_set_ia(dhcp_client, index,
657                         dhcp->use_ta == TRUE ? G_DHCPV6_IA_TA : G_DHCPV6_IA_NA,
658                         NULL, NULL, FALSE);
659
660         clear_callbacks(dhcp_client);
661
662         g_dhcp_client_register_event(dhcp_client,
663                                 G_DHCP_CLIENT_EVENT_SOLICITATION,
664                                 solicitation_cb, dhcp);
665
666         g_dhcp_client_register_event(dhcp_client,
667                                 G_DHCP_CLIENT_EVENT_ADVERTISE,
668                                 advertise_cb, dhcp);
669
670         dhcp->dhcp_client = dhcp_client;
671
672         return g_dhcp_client_start(dhcp_client, NULL);
673 }
674
675 static gboolean start_solicitation(gpointer user_data)
676 {
677         struct connman_dhcpv6 *dhcp = user_data;
678
679         /* Set the retransmission timeout, RFC 3315 chapter 14 */
680         dhcp->RT = SOL_TIMEOUT * (1 + get_random());
681
682         DBG("solicit initial RT timeout %d msec", dhcp->RT);
683
684         dhcp->timeout = g_timeout_add(dhcp->RT, timeout_solicitation, dhcp);
685
686         dhcpv6_solicitation(dhcp);
687
688         return FALSE;
689 }
690
691 int __connman_dhcpv6_start(struct connman_network *network,
692                                 GSList *prefixes, dhcp_cb callback)
693 {
694         struct connman_dhcpv6 *dhcp;
695         int delay;
696
697         DBG("");
698
699         dhcp = g_try_new0(struct connman_dhcpv6, 1);
700         if (dhcp == NULL)
701                 return -ENOMEM;
702
703         dhcp->network = network;
704         dhcp->callback = callback;
705         dhcp->prefixes = prefixes;
706
707         connman_network_ref(network);
708
709         g_hash_table_replace(network_table, network, dhcp);
710
711         /* Initial timeout, RFC 3315, 17.1.2 */
712         delay = rand() % 1000;
713
714         dhcp->timeout = g_timeout_add(delay, start_solicitation, dhcp);
715
716         return 0;
717 }
718
719 void __connman_dhcpv6_stop(struct connman_network *network)
720 {
721         DBG("");
722
723         if (network_table == NULL)
724                 return;
725
726         if (g_hash_table_remove(network_table, network) == TRUE)
727                 connman_network_unref(network);
728 }
729
730 int __connman_dhcpv6_init(void)
731 {
732         DBG("");
733
734         srand(time(0));
735
736         network_table = g_hash_table_new_full(g_direct_hash, g_direct_equal,
737                                                         NULL, remove_network);
738
739         return 0;
740 }
741
742 void __connman_dhcpv6_cleanup(void)
743 {
744         DBG("");
745
746         g_hash_table_destroy(network_table);
747         network_table = NULL;
748 }