*
* Connection Manager
*
- * Copyright (C) 2011 Intel Corporation. All rights reserved.
+ * Copyright (C) 2012 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
#define REQ_MAX_RC 10
#define REN_TIMEOUT (10 * 1000)
#define REN_MAX_RT (600 * 1000)
+#define REB_TIMEOUT (10 * 1000)
+#define REB_MAX_RT (600 * 1000)
struct connman_dhcpv6 {
gboolean use_ta; /* set to TRUE if IPv6 privacy is enabled */
GSList *prefixes; /* network prefixes from radvd */
int request_count; /* how many times REQUEST have been sent */
+ gboolean stateless; /* TRUE if stateless DHCPv6 is used */
+ gboolean started; /* TRUE if we have DHCPv6 started */
};
static GHashTable *network_table;
static int dhcpv6_request(struct connman_dhcpv6 *dhcp, gboolean add_addresses);
+static void clear_timer(struct connman_dhcpv6 *dhcp)
+{
+ if (dhcp->timeout > 0) {
+ g_source_remove(dhcp->timeout);
+ dhcp->timeout = 0;
+ }
+}
+
static inline float get_random()
{
return (rand() % 200 - 100) / 1000.0;
dhcp->nameservers = NULL;
dhcp->timeservers = NULL;
+ dhcp->started = FALSE;
g_slist_foreach(dhcp->prefixes, free_prefix, NULL);
g_slist_free(dhcp->prefixes);
NULL, NULL);
g_dhcp_client_register_event(dhcp_client,
+ G_DHCP_CLIENT_EVENT_REBIND,
+ NULL, NULL);
+
+ g_dhcp_client_register_event(dhcp_client,
+ G_DHCP_CLIENT_EVENT_RELEASE,
+ NULL, NULL);
+
+ g_dhcp_client_register_event(dhcp_client,
G_DHCP_CLIENT_EVENT_INFORMATION_REQ,
NULL, NULL);
}
dhcp->nameservers = nameservers;
- for (i = 0; dhcp->nameservers[i] != NULL; i++)
+ for (i = 0; dhcp->nameservers != NULL &&
+ dhcp->nameservers[i] != NULL; i++)
__connman_service_nameserver_append(service,
dhcp->nameservers[i],
FALSE);
dhcp->timeservers = timeservers;
- for (i = 0; dhcp->timeservers[i] != NULL; i++)
+ for (i = 0; dhcp->timeservers != NULL &&
+ dhcp->timeservers[i] != NULL; i++)
__connman_service_timeserver_append(service,
dhcp->timeservers[i]);
} else
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)
+ if (error != G_DHCP_CLIENT_ERROR_NONE) {
+ clear_timer(dhcp);
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)
+ if (service == NULL) {
+ clear_timer(dhcp);
+ g_dhcp_client_unref(dhcp_client);
return -EINVAL;
+ }
ret = set_duid(service, dhcp->network, dhcp_client, index);
- if (ret < 0)
+ if (ret < 0) {
+ clear_timer(dhcp);
+ g_dhcp_client_unref(dhcp_client);
return ret;
+ }
g_dhcp_client_set_request(dhcp_client, G_DHCPV6_CLIENTID);
g_dhcp_client_set_request(dhcp_client, G_DHCPV6_DNS_SERVERS);
dhcp->nameservers = nameservers;
- for (i = 0; dhcp->nameservers[i] != NULL; i++)
+ for (i = 0; dhcp->nameservers != NULL &&
+ dhcp->nameservers[i] != NULL; i++)
__connman_service_nameserver_append(service,
dhcp->nameservers[i],
FALSE);
dhcp->timeservers = timeservers;
- for (i = 0; dhcp->timeservers[i] != NULL; i++)
+ for (i = 0; dhcp->timeservers != NULL &&
+ dhcp->timeservers[i] != NULL; i++)
__connman_service_timeserver_append(service,
dhcp->timeservers[i]);
} else
}
}
+static void rebind_cb(GDHCPClient *dhcp_client, gpointer user_data)
+{
+ DBG("");
+
+ g_dhcpv6_client_reset_rebind(dhcp_client);
+ g_dhcpv6_client_reset_renew(dhcp_client);
+
+ re_cb(dhcp_client, user_data);
+}
+
+static int dhcpv6_rebind(struct connman_dhcpv6 *dhcp)
+{
+ GDHCPClient *dhcp_client;
+
+ DBG("dhcp %p", dhcp);
+
+ dhcp_client = dhcp->dhcp_client;
+
+ g_dhcp_client_clear_requests(dhcp_client);
+
+ 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_dhcpv6_client_set_ia(dhcp_client,
+ connman_network_get_index(dhcp->network),
+ dhcp->use_ta == TRUE ? G_DHCPV6_IA_TA : G_DHCPV6_IA_NA,
+ NULL, NULL, FALSE);
+
+ clear_callbacks(dhcp_client);
+
+ g_dhcp_client_register_event(dhcp_client, G_DHCP_CLIENT_EVENT_REBIND,
+ rebind_cb, dhcp);
+
+ dhcp->dhcp_client = dhcp_client;
+
+ return g_dhcp_client_start(dhcp_client, NULL);
+}
+
+static gboolean dhcpv6_restart(gpointer user_data)
+{
+ struct connman_dhcpv6 *dhcp = user_data;
+
+ if (dhcp->callback != NULL)
+ dhcp->callback(dhcp->network, FALSE);
+
+ return FALSE;
+}
+
+/*
+ * Check if we need to restart the solicitation procedure. This
+ * is done if all the addresses have expired. RFC 3315, 18.1.4
+ */
+static int check_restart(struct connman_dhcpv6 *dhcp)
+{
+ time_t current, expired;
+
+ g_dhcpv6_client_get_timeouts(dhcp->dhcp_client, NULL, NULL,
+ NULL, NULL, &expired);
+ current = time(NULL);
+
+ if (current > expired) {
+ DBG("expired by %d secs", (int)(current - expired));
+
+ g_timeout_add(0, dhcpv6_restart, dhcp);
+
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static gboolean timeout_rebind(gpointer user_data)
+{
+ struct connman_dhcpv6 *dhcp = user_data;
+
+ if (check_restart(dhcp) < 0)
+ return FALSE;
+
+ dhcp->RT = calc_delay(dhcp->RT, REB_MAX_RT);
+
+ DBG("rebind RT timeout %d msec", dhcp->RT);
+
+ dhcp->timeout = g_timeout_add(dhcp->RT, timeout_rebind, dhcp);
+
+ g_dhcp_client_start(dhcp->dhcp_client, NULL);
+
+ return FALSE;
+}
+
+static gboolean start_rebind(gpointer user_data)
+{
+ struct connman_dhcpv6 *dhcp = user_data;
+
+ dhcp->RT = REB_TIMEOUT * (1 + get_random());
+
+ DBG("rebind initial RT timeout %d msec", dhcp->RT);
+
+ dhcp->timeout = g_timeout_add(dhcp->RT, timeout_rebind, dhcp);
+
+ dhcpv6_rebind(dhcp);
+
+ return FALSE;
+}
+
static void request_cb(GDHCPClient *dhcp_client, gpointer user_data)
{
DBG("");
g_dhcpv6_client_set_oro(dhcp_client, 2, G_DHCPV6_DNS_SERVERS,
G_DHCPV6_SNTP_SERVERS);
- g_dhcpv6_client_get_timeouts(dhcp_client, &T1, &T2, NULL);
+ g_dhcpv6_client_get_timeouts(dhcp_client, &T1, &T2, NULL, NULL, NULL);
g_dhcpv6_client_set_ia(dhcp_client,
connman_network_get_index(dhcp->network),
dhcp->use_ta == TRUE ? G_DHCPV6_IA_TA : G_DHCPV6_IA_NA,
g_dhcpv6_client_set_oro(dhcp_client, 2, G_DHCPV6_DNS_SERVERS,
G_DHCPV6_SNTP_SERVERS);
- g_dhcpv6_client_get_timeouts(dhcp_client, &T1, &T2, NULL);
+ g_dhcpv6_client_get_timeouts(dhcp_client, &T1, &T2, NULL, NULL, NULL);
g_dhcpv6_client_set_ia(dhcp_client,
connman_network_get_index(dhcp->network),
dhcp->use_ta == TRUE ? G_DHCPV6_IA_TA : G_DHCPV6_IA_NA,
{
struct connman_dhcpv6 *dhcp = user_data;
+ if (check_restart(dhcp) < 0)
+ return FALSE;
+
dhcp->RT = calc_delay(dhcp->RT, REN_MAX_RT);
DBG("renew RT timeout %d msec", dhcp->RT);
{
struct connman_dhcpv6 *dhcp;
uint32_t T1, T2;
- time_t last_renew, current;
-
- DBG("");
+ time_t last_renew, last_rebind, current, expired;
dhcp = g_hash_table_lookup(network_table, network);
if (dhcp == NULL)
return -ENOENT;
- if (dhcp->timeout > 0) {
- g_source_remove(dhcp->timeout);
- dhcp->timeout = 0;
- }
+ DBG("network %p dhcp %p", network, dhcp);
+
+ clear_timer(dhcp);
g_dhcpv6_client_get_timeouts(dhcp->dhcp_client, &T1, &T2,
- &last_renew);
- DBG("T1 %u T2 %u", T1, T2);
+ &last_renew, &last_rebind, &expired);
+
+ current = time(NULL);
+
+ DBG("T1 %u T2 %u expires %lu current %lu", T1, T2,
+ (unsigned long)expired, current);
if (T1 == 0xffffffff)
/* RFC 3315, 22.4 */
*/
T1 = 1800;
- current = time(0);
+ /* RFC 3315, 18.1.4, start solicit if expired */
+ if (current > expired) {
+ DBG("expired by %d secs", (int)(current - expired));
+ return -ETIMEDOUT;
+ }
dhcp->callback = callback;
- DBG("renew after %d secs", T1);
+ if (T2 != 0xffffffff && T2 > 0 &&
+ (unsigned)current > (unsigned)last_rebind + T2) {
+ int timeout;
- dhcp->timeout = g_timeout_add_seconds(T1, start_renew, dhcp);
+ /* RFC 3315, chapter 18.1.3, start rebind */
+ if ((unsigned)current > (unsigned)last_renew + T1)
+ timeout = 0;
+ else
+ timeout = last_renew - current + T1;
+ /*
+ * If we just did a renew, do not restart the rebind
+ * immediately.
+ */
+ dhcp->timeout = g_timeout_add_seconds(timeout, start_rebind,
+ dhcp);
+ } else {
+ DBG("renew after %d secs", T1);
+
+ dhcp->timeout = g_timeout_add_seconds(T1, start_renew, dhcp);
+ }
return 0;
}
+int __connman_dhcpv6_start_release(struct connman_network *network,
+ dhcp_cb callback)
+{
+ struct connman_dhcpv6 *dhcp;
+ GDHCPClient *dhcp_client;
+
+ if (network_table == NULL)
+ return 0; /* we are already released */
+
+ dhcp = g_hash_table_lookup(network_table, network);
+ if (dhcp == NULL)
+ return -ENOENT;
+
+ DBG("network %p dhcp %p client %p stateless %d", network, dhcp,
+ dhcp->dhcp_client, dhcp->stateless);
+
+ if (dhcp->stateless == TRUE)
+ return -EINVAL;
+
+ clear_timer(dhcp);
+
+ dhcp_client = dhcp->dhcp_client;
+ if (dhcp_client == NULL) {
+ /*
+ * We had started the DHCPv6 handshaking i.e., we have called
+ * __connman_dhcpv6_start() but it has not yet sent
+ * a solicitation message to server. This means that we do not
+ * have DHCPv6 configured yet so we can just quit here.
+ */
+ DBG("DHCPv6 was not started");
+ return 0;
+ }
+
+ g_dhcp_client_clear_requests(dhcp_client);
+ g_dhcp_client_clear_values(dhcp_client);
+
+ g_dhcp_client_set_request(dhcp_client, G_DHCPV6_CLIENTID);
+ g_dhcp_client_set_request(dhcp_client, G_DHCPV6_SERVERID);
+
+ g_dhcpv6_client_set_ia(dhcp_client,
+ connman_network_get_index(dhcp->network),
+ dhcp->use_ta == TRUE ? G_DHCPV6_IA_TA : G_DHCPV6_IA_NA,
+ NULL, NULL, TRUE);
+
+ clear_callbacks(dhcp_client);
+
+ /*
+ * We do not register callback here because the answer might take too
+ * long time and network code might be in the middle of the disconnect.
+ * So we just inform the server that we are done with the addresses
+ * but ignore the reply from server. This is allowed by RFC 3315
+ * chapter 18.1.6.
+ */
+
+ 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;
- }
+ clear_timer(dhcp);
dhcpv6_free(dhcp);
DBG("");
+ if (network_table != NULL) {
+ dhcp = g_hash_table_lookup(network_table, network);
+ if (dhcp != NULL && dhcp->started == TRUE)
+ return -EBUSY;
+ }
+
dhcp = g_try_new0(struct connman_dhcpv6, 1);
if (dhcp == NULL)
return -ENOMEM;
dhcp->network = network;
dhcp->callback = callback;
+ dhcp->stateless = TRUE;
+ dhcp->started = TRUE;
connman_network_ref(network);
+ DBG("replace network %p dhcp %p", network, dhcp);
+
g_hash_table_replace(network_table, network, dhcp);
/* Initial timeout, RFC 3315, 18.1.5 */
DBG("dhcpv6 advertise msg %p", dhcp);
- if (dhcp->timeout > 0) {
- g_source_remove(dhcp->timeout);
- dhcp->timeout = 0;
- }
+ clear_timer(dhcp);
if (g_dhcpv6_client_get_status(dhcp_client) != 0) {
if (dhcp->callback != NULL)
DBG("dhcpv6 solicitation msg %p", dhcp);
- if (dhcp->timeout > 0) {
- g_source_remove(dhcp->timeout);
- dhcp->timeout = 0;
- }
+ clear_timer(dhcp);
set_addresses(dhcp_client, 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)
+ if (error != G_DHCP_CLIENT_ERROR_NONE) {
+ clear_timer(dhcp);
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)
+ if (service == NULL) {
+ clear_timer(dhcp);
+ g_dhcp_client_unref(dhcp_client);
return -EINVAL;
+ }
ret = set_duid(service, dhcp->network, dhcp_client, index);
- if (ret < 0)
+ if (ret < 0) {
+ clear_timer(dhcp);
+ g_dhcp_client_unref(dhcp_client);
return ret;
+ }
g_dhcp_client_set_request(dhcp_client, G_DHCPV6_CLIENTID);
g_dhcp_client_set_request(dhcp_client, G_DHCPV6_RAPID_COMMIT);
DBG("");
+ if (network_table != NULL) {
+ dhcp = g_hash_table_lookup(network_table, network);
+ if (dhcp != NULL && dhcp->started == TRUE)
+ return -EBUSY;
+ }
+
dhcp = g_try_new0(struct connman_dhcpv6, 1);
if (dhcp == NULL)
return -ENOMEM;
dhcp->network = network;
dhcp->callback = callback;
dhcp->prefixes = prefixes;
+ dhcp->started = TRUE;
connman_network_ref(network);
+ DBG("replace network %p dhcp %p", network, dhcp);
+
g_hash_table_replace(network_table, network, dhcp);
/* Initial timeout, RFC 3315, 17.1.2 */
{
DBG("");
- srand(time(0));
+ srand(time(NULL));
network_table = g_hash_table_new_full(g_direct_hash, g_direct_equal,
NULL, remove_network);