summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJukka Rissanen <jukka.rissanen@linux.intel.com>2013-05-06 13:06:34 +0300
committerPatrik Flykt <patrik.flykt@linux.intel.com>2013-05-06 14:34:20 +0300
commitfd415b40a2219cfc99dca8789496ec9df8e116e6 (patch)
tree3be2c38177c6de3a2f3b896fb9050fc913319c56
parent68ce7d3019b847c655bac4764b2b0a7d4e32ce69 (diff)
downloadconnman-fd415b40a2219cfc99dca8789496ec9df8e116e6.tar.gz
connman-fd415b40a2219cfc99dca8789496ec9df8e116e6.tar.bz2
connman-fd415b40a2219cfc99dca8789496ec9df8e116e6.zip
dhcpv6: Implement CONFIRM message support
See RFC 3315 Chapter 18.1.2. Creation and Transmission of Confirm Messages for details
-rw-r--r--gdhcp/client.c70
-rw-r--r--gdhcp/gdhcp.h4
-rw-r--r--src/dhcpv6.c181
3 files changed, 246 insertions, 9 deletions
diff --git a/gdhcp/client.c b/gdhcp/client.c
index 305533f0..fca5fff7 100644
--- a/gdhcp/client.c
+++ b/gdhcp/client.c
@@ -73,6 +73,7 @@ typedef enum _dhcp_client_state {
INFORMATION_REQ,
SOLICITATION,
REQUEST,
+ CONFIRM,
RENEW,
REBIND,
RELEASE,
@@ -131,6 +132,8 @@ struct _GDHCPClient {
gpointer rebind_data;
GDHCPClientEventFunc release_cb;
gpointer release_data;
+ GDHCPClientEventFunc confirm_cb;
+ gpointer confirm_data;
char *last_address;
unsigned char *duid;
int duid_len;
@@ -759,7 +762,7 @@ static void put_iaid(GDHCPClient *dhcp_client, int index, uint8_t *buf)
int g_dhcpv6_client_set_ia(GDHCPClient *dhcp_client, int index,
int code, uint32_t *T1, uint32_t *T2,
- gboolean add_iaaddr)
+ gboolean add_iaaddr, const char *ia_na)
{
if (code == G_DHCPV6_IA_TA) {
uint8_t ia_options[4];
@@ -771,13 +774,29 @@ int g_dhcpv6_client_set_ia(GDHCPClient *dhcp_client, int index,
ia_options, sizeof(ia_options));
} else if (code == G_DHCPV6_IA_NA) {
+ struct in6_addr addr;
g_dhcp_client_set_request(dhcp_client, G_DHCPV6_IA_NA);
- if (add_iaaddr == TRUE) {
+ /*
+ * If caller has specified the IPv6 address it wishes to
+ * to use (ia_na != NULL and address is valid), then send
+ * the address to server.
+ * If caller did not specify the address (ia_na == NULL) and
+ * if the current address is not set, then we should not send
+ * the address sub-option.
+ */
+ if (add_iaaddr == TRUE && ((ia_na == NULL &&
+ IN6_IS_ADDR_UNSPECIFIED(&dhcp_client->ia_na) == FALSE)
+ || (ia_na != NULL &&
+ inet_pton(AF_INET6, ia_na, &addr) == 1))) {
#define IAADDR_LEN (16+4+4)
uint8_t ia_options[4+4+4+2+2+IAADDR_LEN];
+ if (ia_na != NULL)
+ memcpy(&dhcp_client->ia_na, &addr,
+ sizeof(struct in6_addr));
+
put_iaid(dhcp_client, index, ia_options);
if (T1 != NULL) {
@@ -895,6 +914,11 @@ static int send_dhcpv6_request(GDHCPClient *dhcp_client)
return send_dhcpv6_msg(dhcp_client, DHCPV6_REQUEST, "request");
}
+static int send_dhcpv6_confirm(GDHCPClient *dhcp_client)
+{
+ return send_dhcpv6_msg(dhcp_client, DHCPV6_CONFIRM, "confirm");
+}
+
static int send_dhcpv6_renew(GDHCPClient *dhcp_client)
{
return send_dhcpv6_msg(dhcp_client, DHCPV6_RENEW, "renew");
@@ -2109,6 +2133,7 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition,
case RENEW:
case REBIND:
case RELEASE:
+ case CONFIRM:
if (dhcp_client->type != G_DHCP_IPV6)
return TRUE;
@@ -2164,6 +2189,30 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition,
dhcp_client->release_data);
return TRUE;
}
+ if (dhcp_client->confirm_cb != NULL) {
+ count = 0;
+ server_id = dhcpv6_get_option(packet6, pkt_len,
+ G_DHCPV6_SERVERID, &option_len,
+ &count);
+ if (server_id == NULL || count != 1 ||
+ option_len == 0) {
+ /* RFC 3315, 15.10 */
+ debug(dhcp_client,
+ "confirm server duid error, "
+ "discarding msg %p/%d/%d",
+ server_id, option_len, count);
+ return TRUE;
+ }
+ dhcp_client->server_duid = g_try_malloc(option_len);
+ if (dhcp_client->server_duid == NULL)
+ return TRUE;
+ memcpy(dhcp_client->server_duid, server_id, option_len);
+ dhcp_client->server_duid_len = option_len;
+
+ dhcp_client->confirm_cb(dhcp_client,
+ dhcp_client->confirm_data);
+ return TRUE;
+ }
break;
default:
break;
@@ -2288,6 +2337,16 @@ int g_dhcp_client_start(GDHCPClient *dhcp_client, const char *last_address)
}
send_dhcpv6_request(dhcp_client);
+ } else if (dhcp_client->confirm_cb) {
+ dhcp_client->state = CONFIRM;
+ re = switch_listening_mode(dhcp_client, L3);
+ if (re != 0) {
+ switch_listening_mode(dhcp_client, L_NONE);
+ dhcp_client->state = 0;
+ return re;
+ }
+ send_dhcpv6_confirm(dhcp_client);
+
} else if (dhcp_client->renew_cb) {
dhcp_client->state = RENEW;
re = switch_listening_mode(dhcp_client, L3);
@@ -2474,6 +2533,12 @@ void g_dhcp_client_register_event(GDHCPClient *dhcp_client,
dhcp_client->release_cb = func;
dhcp_client->release_data = data;
return;
+ case G_DHCP_CLIENT_EVENT_CONFIRM:
+ if (dhcp_client->type == G_DHCP_IPV4)
+ return;
+ dhcp_client->confirm_cb = func;
+ dhcp_client->confirm_data = data;
+ return;
}
}
@@ -2512,6 +2577,7 @@ char *g_dhcp_client_get_netmask(GDHCPClient *dhcp_client)
case INFORMATION_REQ:
case SOLICITATION:
case REQUEST:
+ case CONFIRM:
case RENEW:
case REBIND:
case RELEASE:
diff --git a/gdhcp/gdhcp.h b/gdhcp/gdhcp.h
index 0820cdd5..ba47eaff 100644
--- a/gdhcp/gdhcp.h
+++ b/gdhcp/gdhcp.h
@@ -59,6 +59,7 @@ typedef enum {
G_DHCP_CLIENT_EVENT_RENEW,
G_DHCP_CLIENT_EVENT_REBIND,
G_DHCP_CLIENT_EVENT_RELEASE,
+ G_DHCP_CLIENT_EVENT_CONFIRM,
} GDHCPClientEvent;
typedef enum {
@@ -152,7 +153,8 @@ int g_dhcpv6_client_get_timeouts(GDHCPClient *dhcp_client,
time_t *expire);
uint32_t g_dhcpv6_client_get_iaid(GDHCPClient *dhcp_client);
int g_dhcpv6_client_set_ia(GDHCPClient *dhcp_client, int index,
- int code, uint32_t *T1, uint32_t *T2, gboolean add_iaaddr);
+ int code, uint32_t *T1, uint32_t *T2,
+ gboolean add_addresses, const char *address);
void g_dhcpv6_client_reset_renew(GDHCPClient *dhcp_client);
void g_dhcpv6_client_reset_rebind(GDHCPClient *dhcp_client);
void g_dhcpv6_client_set_expire(GDHCPClient *dhcp_client, uint32_t timeout);
diff --git a/src/dhcpv6.c b/src/dhcpv6.c
index 0ac7f4ce..703bedbf 100644
--- a/src/dhcpv6.c
+++ b/src/dhcpv6.c
@@ -50,6 +50,10 @@
#define REN_MAX_RT (600 * 1000)
#define REB_TIMEOUT (10 * 1000)
#define REB_MAX_RT (600 * 1000)
+#define CNF_MAX_DELAY (1 * 1000)
+#define CNF_TIMEOUT (1 * 1000)
+#define CNF_MAX_RT (4 * 1000)
+#define CNF_MAX_RD (10 * 1000)
struct connman_dhcpv6 {
@@ -245,6 +249,10 @@ static void clear_callbacks(GDHCPClient *dhcp_client)
NULL, NULL);
g_dhcp_client_register_event(dhcp_client,
+ G_DHCP_CLIENT_EVENT_CONFIRM,
+ NULL, NULL);
+
+ g_dhcp_client_register_event(dhcp_client,
G_DHCP_CLIENT_EVENT_RENEW,
NULL, NULL);
@@ -608,7 +616,7 @@ static int dhcpv6_rebind(struct connman_dhcpv6 *dhcp)
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);
+ NULL, NULL, FALSE, NULL);
clear_callbacks(dhcp_client);
@@ -721,7 +729,7 @@ static int dhcpv6_request(struct connman_dhcpv6 *dhcp,
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,
- &T1, &T2, add_addresses);
+ &T1, &T2, add_addresses, NULL);
clear_callbacks(dhcp_client);
@@ -791,7 +799,7 @@ static int dhcpv6_renew(struct connman_dhcpv6 *dhcp)
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,
- &T1, &T2, TRUE);
+ &T1, &T2, TRUE, NULL);
clear_callbacks(dhcp_client);
@@ -945,7 +953,7 @@ int __connman_dhcpv6_start_release(struct connman_network *network,
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);
+ NULL, NULL, TRUE, NULL);
clear_callbacks(dhcp_client);
@@ -1166,7 +1174,7 @@ static int dhcpv6_solicitation(struct connman_dhcpv6 *dhcp)
g_dhcpv6_client_set_ia(dhcp_client, index,
dhcp->use_ta == TRUE ? G_DHCPV6_IA_TA : G_DHCPV6_IA_NA,
- NULL, NULL, FALSE);
+ NULL, NULL, FALSE, NULL);
clear_callbacks(dhcp_client);
@@ -1199,10 +1207,150 @@ static gboolean start_solicitation(gpointer user_data)
return FALSE;
}
+static void confirm_cb(GDHCPClient *dhcp_client, gpointer user_data)
+{
+ struct connman_dhcpv6 *dhcp = user_data;
+ int status = g_dhcpv6_client_get_status(dhcp_client);
+
+ DBG("dhcpv6 confirm msg %p status %d", dhcp, status);
+
+ clear_timer(dhcp);
+
+ set_addresses(dhcp_client, dhcp);
+
+ g_dhcpv6_client_clear_retransmit(dhcp_client);
+
+ /*
+ * If confirm fails, start from scratch.
+ */
+ if (status != 0) {
+ g_dhcp_client_unref(dhcp->dhcp_client);
+ start_solicitation(dhcp);
+ } else if (dhcp->callback != NULL)
+ dhcp->callback(dhcp->network, TRUE);
+}
+
+static int dhcpv6_confirm(struct connman_dhcpv6 *dhcp)
+{
+ GDHCPClient *dhcp_client;
+ GDHCPClientError error;
+ struct connman_service *service;
+ struct connman_ipconfig *ipconfig_ipv6;
+ 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) {
+ 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) {
+ clear_timer(dhcp);
+ g_dhcp_client_unref(dhcp_client);
+ return -EINVAL;
+ }
+
+ ret = set_duid(service, dhcp->network, dhcp_client, index);
+ 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);
+ 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);
+
+ ipconfig_ipv6 = __connman_service_get_ip6config(service);
+ dhcp->use_ta = __connman_ipconfig_ipv6_privacy_enabled(ipconfig_ipv6);
+
+ g_dhcpv6_client_set_ia(dhcp_client, index,
+ dhcp->use_ta == TRUE ? G_DHCPV6_IA_TA : G_DHCPV6_IA_NA,
+ NULL, NULL, TRUE,
+ __connman_ipconfig_get_dhcp_address(ipconfig_ipv6));
+
+ clear_callbacks(dhcp_client);
+
+ g_dhcp_client_register_event(dhcp_client,
+ G_DHCP_CLIENT_EVENT_CONFIRM,
+ confirm_cb, dhcp);
+
+ dhcp->dhcp_client = dhcp_client;
+
+ return g_dhcp_client_start(dhcp_client, NULL);
+}
+
+static gboolean timeout_confirm(gpointer user_data)
+{
+ struct connman_dhcpv6 *dhcp = user_data;
+
+ dhcp->RT = calc_delay(dhcp->RT, CNF_MAX_RT);
+
+ DBG("confirm RT timeout %d msec", dhcp->RT);
+
+ dhcp->timeout = g_timeout_add(dhcp->RT, timeout_confirm, dhcp);
+
+ g_dhcpv6_client_set_retransmit(dhcp->dhcp_client);
+
+ g_dhcp_client_start(dhcp->dhcp_client, NULL);
+
+ return FALSE;
+}
+
+static gboolean timeout_max_confirm(gpointer user_data)
+{
+ struct connman_dhcpv6 *dhcp = user_data;
+
+ dhcp->MRD = 0;
+
+ clear_timer(dhcp);
+
+ DBG("confirm max retransmit duration timeout");
+
+ g_dhcpv6_client_clear_retransmit(dhcp->dhcp_client);
+
+ if (dhcp->callback != NULL)
+ dhcp->callback(dhcp->network, FALSE);
+
+ return FALSE;
+}
+
+static gboolean start_confirm(gpointer user_data)
+{
+ struct connman_dhcpv6 *dhcp = user_data;
+
+ /* Set the confirm timeout, RFC 3315 chapter 14 */
+ dhcp->RT = CNF_TIMEOUT * (1 + get_random());
+
+ DBG("confirm initial RT timeout %d msec", dhcp->RT);
+
+ dhcp->timeout = g_timeout_add(dhcp->RT, timeout_confirm, dhcp);
+ dhcp->MRD = g_timeout_add(CNF_MAX_RD, timeout_max_confirm, dhcp);
+
+ dhcpv6_confirm(dhcp);
+
+ return FALSE;
+}
+
int __connman_dhcpv6_start(struct connman_network *network,
GSList *prefixes, dhcp_cb callback)
{
+ struct connman_service *service;
+ struct connman_ipconfig *ipconfig_ipv6;
struct connman_dhcpv6 *dhcp;
+ char *last_address;
int delay;
DBG("");
@@ -1213,6 +1361,10 @@ int __connman_dhcpv6_start(struct connman_network *network,
return -EBUSY;
}
+ service = connman_service_lookup_from_network(network);
+ if (service == NULL)
+ return -EINVAL;
+
dhcp = g_try_new0(struct connman_dhcpv6, 1);
if (dhcp == NULL)
return -ENOMEM;
@@ -1231,7 +1383,24 @@ int __connman_dhcpv6_start(struct connman_network *network,
/* Initial timeout, RFC 3315, 17.1.2 */
delay = rand() % 1000;
- dhcp->timeout = g_timeout_add(delay, start_solicitation, dhcp);
+ ipconfig_ipv6 = __connman_service_get_ip6config(service);
+ last_address = __connman_ipconfig_get_dhcp_address(ipconfig_ipv6);
+
+ if (prefixes != NULL && last_address != NULL &&
+ check_ipv6_addr_prefix(prefixes,
+ last_address) != 128) {
+ /*
+ * So we are in the same subnet
+ * RFC 3315, chapter 18.1.2 Confirm message
+ */
+ dhcp->timeout = g_timeout_add(delay, start_confirm, dhcp);
+ } else {
+ /*
+ * Start from scratch.
+ * RFC 3315, chapter 17.1.2 Solicitation message
+ */
+ dhcp->timeout = g_timeout_add(delay, start_solicitation, dhcp);
+ }
return 0;
}