summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJukka Rissanen <jukka.rissanen@linux.intel.com>2012-01-05 11:42:08 +0200
committerDaniel Wagner <daniel.wagner@bmw-carit.de>2012-01-05 11:52:48 +0100
commit19e8effd134678d9467e328090717b10cef0c208 (patch)
tree96a85b0b17beef535d9c0efd8ad7e793e2cf686c
parentf13255cfe3c98ce4e7598f9bda03b4dc0ac837a1 (diff)
downloadconnman-19e8effd134678d9467e328090717b10cef0c208.tar.gz
connman-19e8effd134678d9467e328090717b10cef0c208.tar.bz2
connman-19e8effd134678d9467e328090717b10cef0c208.zip
dhcpv6: Support stateless DHCPv6
See relevant parts from these RFCs: RFC 3315 - DHCP for IPv6 RFC 3646 - DNS configuration options for DHCP for IPv6 RFC 3736 - Stateless DHCP service for IPv6 RFC 4075 - SNTP configuration option for DHCPv6 The patch does not support authenticated information messages.
-rw-r--r--Makefile.am2
-rw-r--r--gdhcp/client.c34
-rw-r--r--gdhcp/gdhcp.h2
-rw-r--r--src/connman.h5
-rw-r--r--src/dhcpv6.c430
-rw-r--r--src/main.c2
-rw-r--r--src/network.c97
7 files changed, 564 insertions, 8 deletions
diff --git a/Makefile.am b/Makefile.am
index ad9f70d9..a046902a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -74,7 +74,7 @@ src_connmand_SOURCES = $(gdbus_sources) $(gdhcp_sources) \
src/clock.c src/timezone.c \
src/agent.c src/notifier.c src/provider.c \
src/resolver.c src/ipconfig.c src/detect.c src/inet.c \
- src/dhcp.c src/rtnl.c src/proxy.c \
+ src/dhcp.c src/dhcpv6.c src/rtnl.c src/proxy.c \
src/utsname.c src/timeserver.c src/rfkill.c \
src/storage.c src/dbus.c src/config.c \
src/technology.c src/counter.c src/ntp.c \
diff --git a/gdhcp/client.c b/gdhcp/client.c
index be8905c8..32ed4aa1 100644
--- a/gdhcp/client.c
+++ b/gdhcp/client.c
@@ -254,6 +254,15 @@ static void add_dhcpv6_request_options(GDHCPClient *dhcp_client,
(*ptr_buf) += len;
break;
+ case G_DHCPV6_ORO:
+ break;
+
+ case G_DHCPV6_DNS_SERVERS:
+ break;
+
+ case G_DHCPV6_SNTP_SERVERS:
+ break;
+
default:
break;
}
@@ -1335,8 +1344,33 @@ static GList *get_dhcpv6_option_value_list(GDHCPClient *dhcp_client,
unsigned char *value)
{
GList *list = NULL;
+ char *str;
+ int i;
switch (code) {
+ case G_DHCPV6_DNS_SERVERS: /* RFC 3646, chapter 3 */
+ case G_DHCPV6_SNTP_SERVERS: /* RFC 4075, chapter 4 */
+ if (len % 16) {
+ debug(dhcp_client,
+ "%s server list length (%d) is invalid",
+ code == G_DHCPV6_DNS_SERVERS ? "DNS" : "SNTP",
+ len);
+ return NULL;
+ }
+ for (i = 0; i < len; i += 16) {
+
+ str = g_try_malloc0(INET6_ADDRSTRLEN+1);
+ if (str == NULL)
+ return list;
+
+ if (inet_ntop(AF_INET6, &value[i], str,
+ INET6_ADDRSTRLEN) == NULL)
+ g_free(str);
+ else
+ list = g_list_append(list, str);
+ }
+ break;
+
default:
break;
}
diff --git a/gdhcp/gdhcp.h b/gdhcp/gdhcp.h
index 5c0e2ee5..c0385d4f 100644
--- a/gdhcp/gdhcp.h
+++ b/gdhcp/gdhcp.h
@@ -73,6 +73,8 @@ typedef enum {
#define G_DHCPV6_SERVERID 2
#define G_DHCPV6_ORO 6
#define G_DHCPV6_STATUS_CODE 13
+#define G_DHCPV6_DNS_SERVERS 23
+#define G_DHCPV6_SNTP_SERVERS 31
typedef enum {
G_DHCPV6_DUID_LLT = 1,
diff --git a/src/connman.h b/src/connman.h
index 6f47be6d..ae242174 100644
--- a/src/connman.h
+++ b/src/connman.h
@@ -274,6 +274,11 @@ int __connman_dhcp_start(struct connman_network *network, dhcp_cb callback);
void __connman_dhcp_stop(struct connman_network *network);
int __connman_dhcp_init(void);
void __connman_dhcp_cleanup(void);
+int __connman_dhcpv6_init(void);
+void __connman_dhcpv6_cleanup(void);
+int __connman_dhcpv6_start_info(struct connman_network *network,
+ dhcp_cb callback);
+void __connman_dhcpv6_stop(struct connman_network *network);
int __connman_ipv4_init(void);
void __connman_ipv4_cleanup(void);
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;
+}
diff --git a/src/main.c b/src/main.c
index d3b213c3..adcef121 100644
--- a/src/main.c
+++ b/src/main.c
@@ -359,6 +359,7 @@ int main(int argc, char *argv[])
__connman_rtnl_start();
__connman_dhcp_init();
+ __connman_dhcpv6_init();
__connman_wpad_init();
__connman_wispr_init();
__connman_rfkill_init();
@@ -375,6 +376,7 @@ int main(int argc, char *argv[])
__connman_rfkill_cleanup();
__connman_wispr_cleanup();
__connman_wpad_cleanup();
+ __connman_dhcpv6_cleanup();
__connman_dhcp_cleanup();
__connman_provider_cleanup();
__connman_plugin_cleanup();
diff --git a/src/network.c b/src/network.c
index bf522c12..6dee555b 100644
--- a/src/network.c
+++ b/src/network.c
@@ -45,6 +45,7 @@ struct connman_network {
char *group;
char *path;
int index;
+ int router_solicit_count;
struct connman_network_driver *driver;
void *driver_data;
@@ -767,7 +768,8 @@ void connman_network_clear_error(struct connman_network *network)
__connman_service_clear_error(service);
}
-static void set_configuration(struct connman_network *network)
+static void set_configuration(struct connman_network *network,
+ enum connman_ipconfig_type type)
{
struct connman_service *service;
@@ -783,7 +785,7 @@ static void set_configuration(struct connman_network *network)
service = __connman_service_lookup_from_network(network);
__connman_service_ipconfig_indicate_state(service,
CONNMAN_SERVICE_STATE_CONFIGURATION,
- CONNMAN_IPCONFIG_TYPE_IPV4);
+ type);
}
static void dhcp_success(struct connman_network *network)
@@ -852,7 +854,7 @@ static int set_connected_fixed(struct connman_network *network)
ipconfig_ipv4 = __connman_service_get_ip4config(service);
- set_configuration(network);
+ set_configuration(network, CONNMAN_IPCONFIG_TYPE_IPV4);
network->connecting = FALSE;
@@ -890,7 +892,7 @@ static void set_connected_manual(struct connman_network *network)
if (__connman_ipconfig_get_local(ipconfig) == NULL)
__connman_service_read_ip4config(service);
- set_configuration(network);
+ set_configuration(network, CONNMAN_IPCONFIG_TYPE_IPV4);
err = __connman_ipconfig_address_add(ipconfig);
if (err < 0)
@@ -918,7 +920,7 @@ static int set_connected_dhcp(struct connman_network *network)
DBG("network %p", network);
- set_configuration(network);
+ set_configuration(network, CONNMAN_IPCONFIG_TYPE_IPV4);
err = __connman_dhcp_start(network, dhcp_callback);
if (err < 0) {
@@ -967,17 +969,80 @@ static int manual_ipv6_set(struct connman_network *network,
return 0;
}
+static void stop_dhcpv6(struct connman_network *network)
+{
+ __connman_dhcpv6_stop(network);
+}
+
+static void dhcpv6_info_callback(struct connman_network *network,
+ connman_bool_t success)
+{
+ DBG("success %d", success);
+
+ stop_dhcpv6(network);
+}
+
+static void check_dhcpv6(struct nd_router_advert *reply, void *user_data)
+{
+ struct connman_network *network = user_data;
+
+ DBG("reply %p", reply);
+
+ if (reply == NULL) {
+ /*
+ * Router solicitation message seem to get lost easily so
+ * try to send it again.
+ */
+ if (network->router_solicit_count > 0) {
+ DBG("re-send router solicitation %d",
+ network->router_solicit_count);
+ network->router_solicit_count--;
+ __connman_inet_ipv6_send_rs(network->index, 1,
+ check_dhcpv6, network);
+ return;
+ }
+ connman_network_unref(network);
+ return;
+ }
+
+ network->router_solicit_count = 0;
+
+ /* We do stateless DHCPv6 only if router advertisement says so */
+ if (reply->nd_ra_flags_reserved & ND_RA_FLAG_OTHER)
+ __connman_dhcpv6_start_info(network, dhcpv6_info_callback);
+
+ connman_network_unref(network);
+}
+
static void autoconf_ipv6_set(struct connman_network *network)
{
+ struct connman_service *service;
+ struct connman_ipconfig *ipconfig;
+ int index;
+
DBG("network %p", network);
__connman_device_set_network(network->device, network);
connman_device_set_disconnected(network->device, FALSE);
- /* XXX: Append IPv6 nameservers here */
-
network->connecting = FALSE;
+
+ service = __connman_service_lookup_from_network(network);
+ if (service == NULL)
+ return;
+
+ ipconfig = __connman_service_get_ip6config(service);
+ if (ipconfig == NULL)
+ return;
+
+ index = connman_ipconfig_get_index(ipconfig);
+
+ connman_network_ref(network);
+
+ /* Try to get stateless DHCPv6 information, RFC 3736 */
+ network->router_solicit_count = 3;
+ __connman_inet_ipv6_send_rs(index, 1, check_dhcpv6, network);
}
static gboolean set_connected(gpointer user_data)
@@ -1050,8 +1115,26 @@ static gboolean set_connected(gpointer user_data)
} else {
enum connman_service_state state;
+ /*
+ * Resetting solicit count here will prevent the RS resend loop
+ * from sending packets in check_dhcpv6()
+ */
+ network->router_solicit_count = 0;
+
__connman_device_set_network(network->device, NULL);
+ switch (ipv6_method) {
+ case CONNMAN_IPCONFIG_METHOD_UNKNOWN:
+ case CONNMAN_IPCONFIG_METHOD_OFF:
+ case CONNMAN_IPCONFIG_METHOD_FIXED:
+ case CONNMAN_IPCONFIG_METHOD_MANUAL:
+ case CONNMAN_IPCONFIG_METHOD_DHCP:
+ break;
+ case CONNMAN_IPCONFIG_METHOD_AUTO:
+ stop_dhcpv6(network);
+ break;
+ }
+
switch (ipv4_method) {
case CONNMAN_IPCONFIG_METHOD_UNKNOWN:
case CONNMAN_IPCONFIG_METHOD_OFF: