diff options
Diffstat (limited to 'src/dhcpv6.c')
-rw-r--r-- | src/dhcpv6.c | 430 |
1 files changed, 430 insertions, 0 deletions
diff --git a/src/dhcpv6.c b/src/dhcpv6.c new file mode 100644 index 00000000..bedcc7fd --- /dev/null +++ b/src/dhcpv6.c @@ -0,0 +1,430 @@ +/* + * + * Connection Manager + * + * Copyright (C) 2011 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> + +#include <connman/ipconfig.h> +#include <connman/storage.h> + +#include <gdhcp/gdhcp.h> + +#include <glib.h> + +#include "connman.h" + +/* Transmission params in msec, RFC 3315 chapter 5.5 */ +#define INF_MAX_DELAY (1 * 1000) +#define INF_TIMEOUT (1 * 1000) +#define INF_MAX_RT (120 * 1000) + + +struct connman_dhcpv6 { + struct connman_network *network; + dhcp_cb callback; + + char **nameservers; + char **timeservers; + + GDHCPClient *dhcp_client; + + guint timeout; + guint RT; /* in msec */ +}; + +static GHashTable *network_table; + +static inline float get_random() +{ + return (rand() % 200 - 100) / 1000.0; +} + +/* Calculate a random delay, RFC 3315 chapter 14 */ +/* RT and MRT are milliseconds */ +static guint calc_delay(guint RT, guint MRT) +{ + float delay = get_random(); + float rt = RT * (2 + delay); + + if (rt > MRT) + rt = MRT * (1 + delay); + + if (rt < 0) + rt = MRT; + + return (guint)rt; +} + +static void dhcpv6_free(struct connman_dhcpv6 *dhcp) +{ + g_strfreev(dhcp->nameservers); + g_strfreev(dhcp->timeservers); + + dhcp->nameservers = NULL; + dhcp->timeservers = NULL; +} + +static gboolean compare_string_arrays(char **array_a, char **array_b) +{ + int i; + + if (array_a == NULL || array_b == NULL) + return FALSE; + + if (g_strv_length(array_a) != g_strv_length(array_b)) + return FALSE; + + for (i = 0; array_a[i] != NULL && array_b[i] != NULL; i++) + if (g_strcmp0(array_a[i], array_b[i]) != 0) + return FALSE; + + return TRUE; +} + +static void dhcpv6_debug(const char *str, void *data) +{ + connman_info("%s: %s\n", (const char *) data, str); +} + +static gchar *convert_to_hex(unsigned char *buf, int len) +{ + gchar *ret = g_try_malloc(len * 2 + 1); + int i; + + for (i = 0; ret != NULL && i < len; i++) + g_snprintf(ret + i * 2, 3, "%02x", buf[i]); + + return ret; +} + +/* + * DUID should not change over time so save it to file. + * See RFC 3315 chapter 9 for details. + */ +static int set_duid(struct connman_service *service, + struct connman_network *network, + GDHCPClient *dhcp_client, int index) +{ + GKeyFile *keyfile; + const char *ident; + char *hex_duid; + unsigned char *duid; + int duid_len; + + ident = __connman_service_get_ident(service); + + keyfile = connman_storage_load_service(ident); + if (keyfile == NULL) + return -EINVAL; + + hex_duid = g_key_file_get_string(keyfile, ident, "IPv6.DHCP.DUID", + NULL); + if (hex_duid != NULL) { + unsigned int i, j = 0, hex; + size_t hex_duid_len = strlen(hex_duid); + + duid = g_try_malloc0(hex_duid_len / 2); + if (duid == NULL) { + g_key_file_free(keyfile); + g_free(hex_duid); + return -ENOMEM; + } + + for (i = 0; i < hex_duid_len; i += 2) { + sscanf(hex_duid + i, "%02x", &hex); + duid[j++] = hex; + } + + duid_len = hex_duid_len / 2; + } else { + int ret; + int type = __connman_ipconfig_get_type_from_index(index); + + ret = g_dhcpv6_create_duid(G_DHCPV6_DUID_LLT, index, type, + &duid, &duid_len); + if (ret < 0) { + g_key_file_free(keyfile); + return ret; + } + + hex_duid = convert_to_hex(duid, duid_len); + if (hex_duid == NULL) { + g_key_file_free(keyfile); + return -ENOMEM; + } + + g_key_file_set_string(keyfile, ident, "IPv6.DHCP.DUID", + hex_duid); + + __connman_storage_save_service(keyfile, ident); + } + g_free(hex_duid); + + g_key_file_free(keyfile); + + g_dhcpv6_client_set_duid(dhcp_client, duid, duid_len); + + return 0; +} + +static void info_req_cb(GDHCPClient *dhcp_client, gpointer user_data) +{ + struct connman_dhcpv6 *dhcp = user_data; + struct connman_service *service; + int entries, i; + GList *option, *list; + char **nameservers, **timeservers; + + DBG("dhcpv6 information-request %p", dhcp); + + service = __connman_service_lookup_from_network(dhcp->network); + if (service == NULL) { + connman_error("Can not lookup service"); + return; + } + + option = g_dhcp_client_get_option(dhcp_client, G_DHCPV6_DNS_SERVERS); + entries = g_list_length(option); + + nameservers = g_try_new0(char *, entries + 1); + if (nameservers != NULL) { + for (i = 0, list = option; list; list = list->next, i++) + nameservers[i] = g_strdup(list->data); + } + + if (compare_string_arrays(nameservers, dhcp->nameservers) == FALSE) { + if (dhcp->nameservers != NULL) { + for (i = 0; dhcp->nameservers[i] != NULL; i++) + __connman_service_nameserver_remove(service, + dhcp->nameservers[i], + FALSE); + g_strfreev(dhcp->nameservers); + } + + dhcp->nameservers = nameservers; + + for (i = 0; dhcp->nameservers[i] != NULL; i++) + __connman_service_nameserver_append(service, + dhcp->nameservers[i], + FALSE); + } else + g_strfreev(nameservers); + + + option = g_dhcp_client_get_option(dhcp_client, G_DHCPV6_SNTP_SERVERS); + entries = g_list_length(option); + + timeservers = g_try_new0(char *, entries + 1); + if (timeservers != NULL) { + for (i = 0, list = option; list; list = list->next, i++) + timeservers[i] = g_strdup(list->data); + } + + if (compare_string_arrays(timeservers, dhcp->timeservers) == FALSE) { + if (dhcp->timeservers != NULL) { + for (i = 0; dhcp->timeservers[i] != NULL; i++) + __connman_service_timeserver_remove(service, + dhcp->timeservers[i]); + g_strfreev(dhcp->timeservers); + } + + dhcp->timeservers = timeservers; + + for (i = 0; dhcp->timeservers[i] != NULL; i++) + __connman_service_timeserver_append(service, + dhcp->timeservers[i]); + } else + g_strfreev(timeservers); + + + if (dhcp->callback != NULL) { + uint16_t status = g_dhcpv6_client_get_status(dhcp_client); + dhcp->callback(dhcp->network, status == 0 ? TRUE : FALSE); + } +} + +static int dhcpv6_info_request(struct connman_dhcpv6 *dhcp) +{ + struct connman_service *service; + GDHCPClient *dhcp_client; + GDHCPClientError error; + int index, ret; + + DBG("dhcp %p", dhcp); + + index = connman_network_get_index(dhcp->network); + + dhcp_client = g_dhcp_client_new(G_DHCP_IPV6, index, &error); + if (error != G_DHCP_CLIENT_ERROR_NONE) + return -EINVAL; + + if (getenv("CONNMAN_DHCPV6_DEBUG")) + g_dhcp_client_set_debug(dhcp_client, dhcpv6_debug, "DHCPv6"); + + service = __connman_service_lookup_from_network(dhcp->network); + if (service == NULL) + return -EINVAL; + + ret = set_duid(service, dhcp->network, dhcp_client, index); + if (ret < 0) + return ret; + + g_dhcp_client_set_request(dhcp_client, G_DHCPV6_CLIENTID); + g_dhcp_client_set_request(dhcp_client, G_DHCPV6_DNS_SERVERS); + g_dhcp_client_set_request(dhcp_client, G_DHCPV6_SNTP_SERVERS); + + g_dhcpv6_client_set_oro(dhcp_client, 2, G_DHCPV6_DNS_SERVERS, + G_DHCPV6_SNTP_SERVERS); + + g_dhcp_client_register_event(dhcp_client, + G_DHCP_CLIENT_EVENT_INFORMATION_REQ, info_req_cb, dhcp); + + dhcp->dhcp_client = dhcp_client; + + return g_dhcp_client_start(dhcp_client, NULL); +} + +static int dhcpv6_release(struct connman_dhcpv6 *dhcp) +{ + DBG("dhcp %p", dhcp); + + if (dhcp->timeout > 0) { + g_source_remove(dhcp->timeout); + dhcp->timeout = 0; + } + + dhcpv6_free(dhcp); + + if (dhcp->dhcp_client == NULL) + return 0; + + g_dhcp_client_stop(dhcp->dhcp_client); + g_dhcp_client_unref(dhcp->dhcp_client); + + dhcp->dhcp_client = NULL; + + return 0; +} + +static void remove_network(gpointer user_data) +{ + struct connman_dhcpv6 *dhcp = user_data; + + DBG("dhcp %p", dhcp); + + dhcpv6_release(dhcp); + + g_free(dhcp); +} + +static gboolean timeout_info_req(gpointer user_data) +{ + struct connman_dhcpv6 *dhcp = user_data; + + dhcp->RT = calc_delay(dhcp->RT, INF_MAX_RT); + + DBG("info RT timeout %d msec", dhcp->RT); + + dhcp->timeout = g_timeout_add(dhcp->RT, timeout_info_req, dhcp); + + g_dhcp_client_start(dhcp->dhcp_client, NULL); + + return FALSE; +} + +static gboolean start_info_req(gpointer user_data) +{ + struct connman_dhcpv6 *dhcp = user_data; + + /* Set the retransmission timeout, RFC 3315 chapter 14 */ + dhcp->RT = INF_TIMEOUT * (1 + get_random()); + + DBG("info initial RT timeout %d msec", dhcp->RT); + + dhcp->timeout = g_timeout_add(dhcp->RT, timeout_info_req, dhcp); + + dhcpv6_info_request(dhcp); + + return FALSE; +} + +int __connman_dhcpv6_start_info(struct connman_network *network, + dhcp_cb callback) +{ + struct connman_dhcpv6 *dhcp; + int delay; + + DBG(""); + + dhcp = g_try_new0(struct connman_dhcpv6, 1); + if (dhcp == NULL) + return -ENOMEM; + + dhcp->network = network; + dhcp->callback = callback; + + connman_network_ref(network); + + g_hash_table_replace(network_table, network, dhcp); + + /* Initial timeout, RFC 3315, 18.1.5 */ + delay = rand() % 1000; + + dhcp->timeout = g_timeout_add(delay, start_info_req, dhcp); + + return 0; +} + +void __connman_dhcpv6_stop(struct connman_network *network) +{ + DBG(""); + + if (network_table == NULL) + return; + + if (g_hash_table_remove(network_table, network) == TRUE) + connman_network_unref(network); +} + +int __connman_dhcpv6_init(void) +{ + DBG(""); + + srand(time(0)); + + network_table = g_hash_table_new_full(g_direct_hash, g_direct_equal, + NULL, remove_network); + + return 0; +} + +void __connman_dhcpv6_cleanup(void) +{ + DBG(""); + + g_hash_table_destroy(network_table); + network_table = NULL; +} |