dhcpv6: Initial stateful DHCPv6 support.
authorJukka Rissanen <jukka.rissanen@linux.intel.com>
Thu, 5 Jan 2012 11:38:09 +0000 (13:38 +0200)
committerDaniel Wagner <daniel.wagner@bmw-carit.de>
Thu, 5 Jan 2012 12:17:26 +0000 (13:17 +0100)
This patch contains solicitation message support.

gdhcp/client.c
gdhcp/common.c
gdhcp/common.h
gdhcp/gdhcp.h
src/connman.h
src/dhcpv6.c
src/network.c

index 32ed4aa..425f1cb 100644 (file)
@@ -2,7 +2,7 @@
  *
  *  DHCP client library with GLib integration
  *
- *  Copyright (C) 2009-2010  Intel Corporation. All rights reserved.
+ *  Copyright (C) 2009-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
@@ -70,6 +70,7 @@ typedef enum _dhcp_client_state {
        IPV4LL_MONITOR,
        IPV4LL_DEFEND,
        INFORMATION_REQ,
+       SOLICITATION,
 } ClientState;
 
 struct _GDHCPClient {
@@ -112,10 +113,20 @@ struct _GDHCPClient {
        gpointer debug_data;
        GDHCPClientEventFunc information_req_cb;
        gpointer information_req_data;
+       GDHCPClientEventFunc solicitation_cb;
+       gpointer solicitation_data;
+       GDHCPClientEventFunc advertise_cb;
+       gpointer advertise_data;
        char *last_address;
        unsigned char *duid;
        int duid_len;
+       unsigned char *server_duid;
+       int server_duid_len;
        uint16_t status_code;
+       uint32_t iaid;
+       uint32_t T1, T2;
+       struct in6_addr ia_na;
+       struct in6_addr ia_ta;
 };
 
 static inline void debug(GDHCPClient *client, const char *format, ...)
@@ -254,6 +265,35 @@ static void add_dhcpv6_request_options(GDHCPClient *dhcp_client,
                        (*ptr_buf) += len;
                        break;
 
+               case G_DHCPV6_SERVERID:
+                       if (dhcp_client->server_duid == NULL)
+                               return;
+
+                       len = 2 + 2 + dhcp_client->server_duid_len;
+                       if ((*ptr_buf + len) > (buf + max_buf)) {
+                               debug(dhcp_client, "Too long dhcpv6 message "
+                                       "when writing server id option");
+                               return;
+                       }
+
+                       copy_option(*ptr_buf, G_DHCPV6_SERVERID,
+                               dhcp_client->server_duid_len,
+                               dhcp_client->server_duid);
+                       (*ptr_buf) += len;
+                       break;
+
+               case G_DHCPV6_RAPID_COMMIT:
+                       len = 2 + 2;
+                       if ((*ptr_buf + len) > (buf + max_buf)) {
+                               debug(dhcp_client, "Too long dhcpv6 message "
+                                       "when writing rapid commit option");
+                               return;
+                       }
+
+                       copy_option(*ptr_buf, G_DHCPV6_RAPID_COMMIT, 0, 0);
+                       (*ptr_buf) += len;
+                       break;
+
                case G_DHCPV6_ORO:
                        break;
 
@@ -554,6 +594,132 @@ int g_dhcpv6_client_set_duid(GDHCPClient *dhcp_client, unsigned char *duid,
        return 0;
 }
 
+uint32_t g_dhcpv6_client_get_iaid(GDHCPClient *dhcp_client)
+{
+       if (dhcp_client == NULL || dhcp_client->type == G_DHCP_IPV4)
+               return 0;
+
+       return dhcp_client->iaid;
+}
+
+void g_dhcpv6_client_create_iaid(GDHCPClient *dhcp_client, int index,
+                               unsigned char *iaid)
+{
+       uint8_t buf[6];
+
+       get_interface_mac_address(index, buf);
+
+       memcpy(iaid, &buf[2], 4);
+       dhcp_client->iaid = iaid[0] << 24 |
+                       iaid[1] << 16 | iaid[2] << 8 | iaid[3];
+}
+
+int g_dhcpv6_client_get_timeouts(GDHCPClient *dhcp_client,
+                               uint32_t *T1, uint32_t *T2)
+{
+       if (dhcp_client == NULL || dhcp_client->type == G_DHCP_IPV4)
+               return -EINVAL;
+
+       if (T1 != NULL)
+               *T1 = dhcp_client->T1;
+
+       if (T2 != NULL)
+               *T2 = dhcp_client->T2;
+
+       return 0;
+}
+
+static uint8_t *create_iaaddr(GDHCPClient *dhcp_client, uint8_t *buf,
+                               uint16_t len)
+{
+       buf[0] = 0;
+       buf[1] = G_DHCPV6_IAADDR;
+       buf[2] = 0;
+       buf[3] = len;
+       memcpy(&buf[4], &dhcp_client->ia_na, 16);
+       memset(&buf[20], 0, 4); /* preferred */
+       memset(&buf[24], 0, 4); /* valid */
+       return buf;
+}
+
+static void put_iaid(GDHCPClient *dhcp_client, int index, uint8_t *buf)
+{
+       uint32_t iaid;
+
+       iaid = g_dhcpv6_client_get_iaid(dhcp_client);
+       if (iaid == 0) {
+               g_dhcpv6_client_create_iaid(dhcp_client, index, buf);
+               return;
+       }
+
+       buf[0] = iaid >> 24;
+       buf[1] = iaid >> 16;
+       buf[2] = iaid >> 8;
+       buf[3] = iaid;
+}
+
+int g_dhcpv6_client_set_ia(GDHCPClient *dhcp_client, int index,
+                       int code, uint32_t *T1, uint32_t *T2,
+                       gboolean add_iaaddr)
+{
+       if (code == G_DHCPV6_IA_TA) {
+               uint8_t ia_options[4];
+
+               put_iaid(dhcp_client, index, ia_options);
+
+               g_dhcp_client_set_request(dhcp_client, G_DHCPV6_IA_TA);
+               g_dhcpv6_client_set_send(dhcp_client, G_DHCPV6_IA_TA,
+                                       ia_options, sizeof(ia_options));
+
+       } else if (code == G_DHCPV6_IA_NA) {
+
+               g_dhcp_client_set_request(dhcp_client, G_DHCPV6_IA_NA);
+
+               if (add_iaaddr == TRUE) {
+#define IAADDR_LEN (16+4+4)
+                       uint8_t ia_options[4+4+4+2+2+IAADDR_LEN];
+
+                       put_iaid(dhcp_client, index, ia_options);
+
+                       if (T1 != NULL) {
+                               ia_options[4] = *T1 >> 24;
+                               ia_options[5] = *T1 >> 16;
+                               ia_options[6] = *T1 >> 8;
+                               ia_options[7] = *T1;
+                       } else
+                               memset(&ia_options[4], 0x00, 4);
+
+                       if (T2 != NULL) {
+                               ia_options[8] = *T2 >> 24;
+                               ia_options[9] = *T2 >> 16;
+                               ia_options[10] = *T2 >> 8;
+                               ia_options[11] = *T2;
+                       } else
+                               memset(&ia_options[8], 0x00, 4);
+
+                       create_iaaddr(dhcp_client, &ia_options[12],
+                                       IAADDR_LEN);
+
+                       g_dhcpv6_client_set_send(dhcp_client, G_DHCPV6_IA_NA,
+                                       ia_options, sizeof(ia_options));
+               } else {
+                       uint8_t ia_options[4+4+4];
+
+                       put_iaid(dhcp_client, index, ia_options);
+
+                       memset(&ia_options[4], 0x00, 4); /* T1 (4 bytes) */
+                       memset(&ia_options[8], 0x00, 4); /* T2 (4 bytes) */
+
+                       g_dhcpv6_client_set_send(dhcp_client, G_DHCPV6_IA_NA,
+                                       ia_options, sizeof(ia_options));
+               }
+
+       } else
+               return -EINVAL;
+
+       return 0;
+}
+
 int g_dhcpv6_client_set_oro(GDHCPClient *dhcp_client, int args, ...)
 {
        va_list va;
@@ -608,6 +774,11 @@ static int send_dhcpv6_msg(GDHCPClient *dhcp_client, int type, char *msg)
        return ret;
 }
 
+static int send_solicitation(GDHCPClient *dhcp_client)
+{
+       return send_dhcpv6_msg(dhcp_client, DHCPV6_SOLICIT, "solicit");
+}
+
 static int send_information_req(GDHCPClient *dhcp_client)
 {
        return send_dhcpv6_msg(dhcp_client, DHCPV6_INFORMATION_REQ,
@@ -1339,9 +1510,127 @@ static GList *get_option_value_list(char *value, GDHCPOptionType type)
        return list;
 }
 
+static inline uint32_t get_uint32(unsigned char *value)
+{
+       return value[0] << 24 | value[1] << 16 |
+               value[2] << 8 | value[3];
+}
+
+static inline uint16_t get_uint16(unsigned char *value)
+{
+       return value[0] << 8 | value[1];
+}
+
+static GList *get_addresses(GDHCPClient *dhcp_client,
+                               int code, int len,
+                               unsigned char *value,
+                               uint16_t *status)
+{
+       GList *list = NULL;
+       struct in6_addr addr;
+       uint32_t iaid, T1 = 0, T2 = 0, preferred = 0, valid = 0;
+       uint16_t option_len, option_code, st = 0, max_len;
+       int addr_count = 0, i, pos;
+       uint8_t *option;
+       char *str;
+
+       iaid = get_uint32(&value[0]);
+       if (dhcp_client->iaid != iaid)
+               return NULL;
+
+       if (code == G_DHCPV6_IA_NA) {
+               T1 = get_uint32(&value[4]);
+               T2 = get_uint32(&value[8]);
+
+               if (T1 > T2)
+                       /* RFC 3315, 22.4 */
+                       return NULL;
+
+               pos = 12;
+       } else
+               pos = 4;
+
+       if (len <= pos)
+               return NULL;
+
+       max_len = len - pos;
+
+       /* We have more sub-options in this packet. */
+       do {
+               option = dhcpv6_get_sub_option(&value[pos], max_len,
+                                       &option_code, &option_len);
+
+               debug(dhcp_client, "pos %d option %p code %d len %d",
+                       pos, option, option_code, option_len);
+
+               if (option == NULL)
+                       break;
+
+               if (pos >= max_len)
+                       break;
+
+               switch (option_code) {
+               case G_DHCPV6_IAADDR:
+                       i = 0;
+                       memcpy(&addr, &option[0], sizeof(addr));
+                       i += sizeof(addr);
+                       preferred = get_uint32(&option[i]);
+                       i += 4;
+                       valid = get_uint32(&option[i]);
+
+                       addr_count++;
+                       break;
+
+               case G_DHCPV6_STATUS_CODE:
+                       st = get_uint16(&option[0]);
+                       debug(dhcp_client, "error code %d", st);
+                       if (option_len > 2) {
+                               str = g_strndup((gchar *)&option[2],
+                                               option_len - 2);
+                               debug(dhcp_client, "error text: %s", str);
+                               g_free(str);
+                       }
+
+                       *status = st;
+                       break;
+               }
+
+               pos += 2 + 2 + option_len;
+
+       } while (option != NULL);
+
+       if (addr_count > 0 && st == 0) {
+               /* We only support one address atm */
+               char str[INET6_ADDRSTRLEN + 1];
+
+               if (preferred > valid)
+                       /* RFC 3315, 22.6 */
+                       return NULL;
+
+               dhcp_client->T1 = T1;
+               dhcp_client->T2 = T2;
+
+               inet_ntop(AF_INET6, &addr, str, INET6_ADDRSTRLEN);
+               debug(dhcp_client, "count %d addr %s T1 %u T2 %u",
+                       addr_count, str, T1, T2);
+
+               list = g_list_append(list, g_strdup(str));
+
+               if (code == G_DHCPV6_IA_NA)
+                       memcpy(&dhcp_client->ia_na, &addr,
+                                               sizeof(struct in6_addr));
+               else
+                       memcpy(&dhcp_client->ia_ta, &addr,
+                                               sizeof(struct in6_addr));
+       }
+
+       return list;
+}
+
 static GList *get_dhcpv6_option_value_list(GDHCPClient *dhcp_client,
                                        int code, int len,
-                                       unsigned char *value)
+                                       unsigned char *value,
+                                       uint16_t *status)
 {
        GList *list = NULL;
        char *str;
@@ -1371,6 +1660,11 @@ static GList *get_dhcpv6_option_value_list(GDHCPClient *dhcp_client,
                }
                break;
 
+       case G_DHCPV6_IA_NA:            /* RFC 3315, chapter 22.4 */
+       case G_DHCPV6_IA_TA:            /* RFC 3315, chapter 22.5 */
+               list = get_addresses(dhcp_client, code, len, value, status);
+               break;
+
        default:
                break;
        }
@@ -1380,7 +1674,7 @@ static GList *get_dhcpv6_option_value_list(GDHCPClient *dhcp_client,
 
 static void get_dhcpv6_request(GDHCPClient *dhcp_client,
                                struct dhcpv6_packet *packet,
-                               uint16_t pkt_len)
+                               uint16_t pkt_len, uint16_t *status)
 {
        GList *list, *value_list;
        uint8_t *option;
@@ -1399,7 +1693,7 @@ static void get_dhcpv6_request(GDHCPClient *dhcp_client,
                }
 
                value_list = get_dhcpv6_option_value_list(dhcp_client, code,
-                                               option_len, option);
+                                               option_len, option, status);
 
                debug(dhcp_client, "code %d %p len %d list %p", code, option,
                        option_len, value_list);
@@ -1458,7 +1752,7 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition,
        struct dhcp_packet packet;
        struct dhcpv6_packet *packet6 = NULL;
        uint8_t *message_type = NULL, *client_id = NULL, *option_u8,
-               *server_id;
+               *server_id = NULL;
        uint16_t option_len = 0, status = 0;
        gpointer pkt;
        unsigned char buf[MAX_DHCPV6_PKT_SIZE];
@@ -1520,11 +1814,15 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition,
                if (option_u8 != 0 && option_len > 0) {
                        status = option_u8[0]<<8 | option_u8[1];
                        if (status != 0) {
-                               gchar *txt = g_strndup((gchar *)&option_u8[2],
-                                                       option_len - 2);
-                               debug(dhcp_client, "error code %d: %s",
-                                       status, txt);
-                               g_free(txt);
+                               debug(dhcp_client, "error code %d", status);
+                               if (option_len > 2) {
+                                       gchar *txt = g_strndup(
+                                               (gchar *)&option_u8[2],
+                                               option_len - 2);
+                                       debug(dhcp_client, "error text: %s",
+                                               txt);
+                                       g_free(txt);
+                               }
                        }
                        dhcp_client->status_code = status;
                } else
@@ -1598,6 +1896,66 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition,
                }
 
                break;
+       case SOLICITATION:
+               if (dhcp_client->type != G_DHCP_IPV6)
+                       return TRUE;
+
+               if (packet6->message != DHCPV6_REPLY &&
+                               packet6->message != DHCPV6_ADVERTISE)
+                       return TRUE;
+
+               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,
+                               "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;
+
+               if (packet6->message == DHCPV6_REPLY) {
+                       uint8_t *rapid_commit;
+                       count = 0;
+                       option_len = 0;
+                       rapid_commit = dhcpv6_get_option(packet6, pkt_len,
+                                                       G_DHCPV6_RAPID_COMMIT,
+                                                       &option_len, &count);
+                       if (rapid_commit == NULL || option_len == 0 ||
+                                                               count != 1)
+                               /* RFC 3315, 17.1.4 */
+                               return TRUE;
+               }
+
+               switch_listening_mode(dhcp_client, L_NONE);
+
+               if (dhcp_client->status_code == 0)
+                       get_dhcpv6_request(dhcp_client, packet6, pkt_len,
+                                       &dhcp_client->status_code);
+
+               if (packet6->message == DHCPV6_ADVERTISE) {
+                       if (dhcp_client->advertise_cb != NULL)
+                               dhcp_client->advertise_cb(dhcp_client,
+                                               dhcp_client->advertise_data);
+                       return TRUE;
+               }
+
+               if (dhcp_client->solicitation_cb != NULL) {
+                       /*
+                        * The dhcp_client might not be valid after the
+                        * callback call so just return immediately.
+                        */
+                       dhcp_client->solicitation_cb(dhcp_client,
+                                       dhcp_client->solicitation_data);
+                       return TRUE;
+               }
+               break;
        case INFORMATION_REQ:
                if (dhcp_client->type != G_DHCP_IPV6)
                        return TRUE;
@@ -1618,7 +1976,10 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition,
                }
 
                switch_listening_mode(dhcp_client, L_NONE);
-               get_dhcpv6_request(dhcp_client, packet6, pkt_len);
+
+               dhcp_client->status_code = 0;
+               get_dhcpv6_request(dhcp_client, packet6, pkt_len,
+                                               &dhcp_client->status_code);
 
                if (dhcp_client->information_req_cb != NULL) {
                        /*
@@ -1732,7 +2093,18 @@ int g_dhcp_client_start(GDHCPClient *dhcp_client, const char *last_address)
                                return re;
                        }
                        send_information_req(dhcp_client);
+
+               } else if (dhcp_client->solicitation_cb) {
+                       dhcp_client->state = SOLICITATION;
+                       re = switch_listening_mode(dhcp_client, L3);
+                       if (re != 0) {
+                               switch_listening_mode(dhcp_client, L_NONE);
+                               dhcp_client->state = 0;
+                               return re;
+                       }
+                       send_solicitation(dhcp_client);
                }
+
                return 0;
        }
 
@@ -1851,6 +2223,18 @@ void g_dhcp_client_register_event(GDHCPClient *dhcp_client,
                dhcp_client->information_req_cb = func;
                dhcp_client->information_req_data = data;
                return;
+       case G_DHCP_CLIENT_EVENT_SOLICITATION:
+               if (dhcp_client->type == G_DHCP_IPV4)
+                       return;
+               dhcp_client->solicitation_cb = func;
+               dhcp_client->solicitation_data = data;
+               return;
+       case G_DHCP_CLIENT_EVENT_ADVERTISE:
+               if (dhcp_client->type == G_DHCP_IPV4)
+                       return;
+               dhcp_client->advertise_cb = func;
+               dhcp_client->advertise_data = data;
+               return;
        }
 }
 
@@ -1887,6 +2271,7 @@ char *g_dhcp_client_get_netmask(GDHCPClient *dhcp_client)
        case IPV4LL_PROBE:
        case IPV4LL_ANNOUNCE:
        case INFORMATION_REQ:
+       case SOLICITATION:
                break;
        }
        return NULL;
@@ -1904,6 +2289,17 @@ GDHCPClientError g_dhcp_client_set_request(GDHCPClient *dhcp_client,
        return G_DHCP_CLIENT_ERROR_NONE;
 }
 
+void g_dhcp_client_clear_requests(GDHCPClient *dhcp_client)
+{
+       g_list_free(dhcp_client->request_list);
+       dhcp_client->request_list = NULL;
+}
+
+void g_dhcp_client_clear_values(GDHCPClient *dhcp_client)
+{
+       g_hash_table_remove_all(dhcp_client->send_value_hash);
+}
+
 static uint8_t *alloc_dhcp_option(int code, const char *str, int extra)
 {
        uint8_t *storage;
@@ -2004,6 +2400,7 @@ void g_dhcp_client_unref(GDHCPClient *dhcp_client)
        g_free(dhcp_client->assigned_ip);
        g_free(dhcp_client->last_address);
        g_free(dhcp_client->duid);
+       g_free(dhcp_client->server_duid);
 
        g_list_free(dhcp_client->request_list);
        g_list_free(dhcp_client->require_list);
index 46cf5f6..c511e26 100644 (file)
@@ -188,6 +188,31 @@ uint8_t *dhcpv6_get_option(struct dhcpv6_packet *packet, uint16_t pkt_len,
        return found;
 }
 
+uint8_t *dhcpv6_get_sub_option(unsigned char *option, uint16_t max_len,
+                       uint16_t *option_code, uint16_t *option_len)
+{
+       int rem;
+       uint16_t code, len;
+
+       rem = max_len - 2 - 2;
+
+       if (rem <= 0)
+               /* Bad option */
+               return NULL;
+
+       code = option[0] << 8 | option[1];
+       len = option[2] << 8 | option[3];
+
+       rem -= len;
+       if (rem < 0)
+               return NULL;
+
+       *option_code = code;
+       *option_len = len;
+
+       return &option[4];
+}
+
 /*
  * Add an option (supplied in binary form) to the options.
  * Option format: [code][len][data1][data2]..[dataLEN]
index ae4029c..4b7df7e 100644 (file)
@@ -188,6 +188,8 @@ static const uint8_t dhcp_option_lengths[] = {
 uint8_t *dhcp_get_option(struct dhcp_packet *packet, int code);
 uint8_t *dhcpv6_get_option(struct dhcpv6_packet *packet, uint16_t pkt_len,
                        int code, uint16_t *option_len, int *option_count);
+uint8_t *dhcpv6_get_sub_option(unsigned char *option, uint16_t max_len,
+                       uint16_t *code, uint16_t *option_len);
 int dhcp_end_option(uint8_t *optionptr);
 void dhcp_add_binary_option(struct dhcp_packet *packet, uint8_t *addopt);
 void dhcpv6_add_binary_option(struct dhcpv6_packet *packet, uint16_t max_len,
index c0385d4..5684854 100644 (file)
@@ -53,6 +53,8 @@ typedef enum {
        G_DHCP_CLIENT_EVENT_IPV4LL_LOST,
        G_DHCP_CLIENT_EVENT_ADDRESS_CONFLICT,
        G_DHCP_CLIENT_EVENT_INFORMATION_REQ,
+       G_DHCP_CLIENT_EVENT_SOLICITATION,
+       G_DHCP_CLIENT_EVENT_ADVERTISE,
 } GDHCPClientEvent;
 
 typedef enum {
@@ -71,8 +73,12 @@ typedef enum {
 
 #define G_DHCPV6_CLIENTID      1
 #define G_DHCPV6_SERVERID      2
+#define G_DHCPV6_IA_NA         3
+#define G_DHCPV6_IA_TA         4
+#define G_DHCPV6_IAADDR                5
 #define G_DHCPV6_ORO           6
 #define G_DHCPV6_STATUS_CODE   13
+#define G_DHCPV6_RAPID_COMMIT  14
 #define G_DHCPV6_DNS_SERVERS   23
 #define G_DHCPV6_SNTP_SERVERS  31
 
@@ -102,6 +108,8 @@ void g_dhcp_client_register_event(GDHCPClient *client,
 
 GDHCPClientError g_dhcp_client_set_request(GDHCPClient *client,
                                                unsigned int option_code);
+void g_dhcp_client_clear_requests(GDHCPClient *dhcp_client);
+void g_dhcp_client_clear_values(GDHCPClient *dhcp_client);
 GDHCPClientError g_dhcp_client_set_send(GDHCPClient *client,
                                                unsigned char option_code,
                                                const char *option_value);
@@ -122,6 +130,13 @@ void g_dhcpv6_client_set_send(GDHCPClient *dhcp_client, uint16_t option_code,
                        uint8_t *option_value, uint16_t option_len);
 uint16_t g_dhcpv6_client_get_status(GDHCPClient *dhcp_client);
 int g_dhcpv6_client_set_oro(GDHCPClient *dhcp_client, int args, ...);
+void g_dhcpv6_client_create_iaid(GDHCPClient *dhcp_client, int index,
+                               unsigned char *iaid);
+int g_dhcpv6_client_get_timeouts(GDHCPClient *dhcp_client,
+                               uint32_t *T1, uint32_t *T2);
+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);
 
 /* DHCP Server */
 typedef enum {
index 49469cc..f48f922 100644 (file)
@@ -282,6 +282,8 @@ 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_dhcpv6_start(struct connman_network *network,
+                               GSList *prefixes, dhcp_cb callback);
 
 int __connman_ipv4_init(void);
 void __connman_ipv4_cleanup(void);
index bedcc7f..a19eee0 100644 (file)
@@ -40,6 +40,9 @@
 #define INF_MAX_DELAY  (1 * 1000)
 #define INF_TIMEOUT    (1 * 1000)
 #define INF_MAX_RT     (120 * 1000)
+#define SOL_MAX_DELAY   (1 * 1000)
+#define SOL_TIMEOUT     (1 * 1000)
+#define SOL_MAX_RT      (120 * 1000)
 
 
 struct connman_dhcpv6 {
@@ -51,8 +54,10 @@ struct connman_dhcpv6 {
 
        GDHCPClient *dhcp_client;
 
-       guint timeout;
-       guint RT;       /* in msec */
+       guint timeout;          /* operation timeout in msec */
+       guint RT;               /* in msec */
+       gboolean use_ta;        /* set to TRUE if IPv6 privacy is enabled */
+       GSList *prefixes;       /* network prefixes from radvd */
 };
 
 static GHashTable *network_table;
@@ -78,6 +83,11 @@ static guint calc_delay(guint RT, guint MRT)
        return (guint)rt;
 }
 
+static void free_prefix(gpointer data, gpointer user_data)
+{
+       g_free(data);
+}
+
 static void dhcpv6_free(struct connman_dhcpv6 *dhcp)
 {
        g_strfreev(dhcp->nameservers);
@@ -85,6 +95,9 @@ static void dhcpv6_free(struct connman_dhcpv6 *dhcp)
 
        dhcp->nameservers = NULL;
        dhcp->timeservers = NULL;
+
+       g_slist_foreach(dhcp->prefixes, free_prefix, NULL);
+       g_slist_free(dhcp->prefixes);
 }
 
 static gboolean compare_string_arrays(char **array_a, char **array_b)
@@ -190,6 +203,21 @@ static int set_duid(struct connman_service *service,
        return 0;
 }
 
+static void clear_callbacks(GDHCPClient *dhcp_client)
+{
+       g_dhcp_client_register_event(dhcp_client,
+                               G_DHCP_CLIENT_EVENT_SOLICITATION,
+                               NULL, NULL);
+
+       g_dhcp_client_register_event(dhcp_client,
+                               G_DHCP_CLIENT_EVENT_ADVERTISE,
+                               NULL, NULL);
+
+       g_dhcp_client_register_event(dhcp_client,
+                               G_DHCP_CLIENT_EVENT_INFORMATION_REQ,
+                               NULL, NULL);
+}
+
 static void info_req_cb(GDHCPClient *dhcp_client, gpointer user_data)
 {
        struct connman_dhcpv6 *dhcp = user_data;
@@ -307,6 +335,158 @@ static int dhcpv6_info_request(struct connman_dhcpv6 *dhcp)
        return g_dhcp_client_start(dhcp_client, NULL);
 }
 
+static int check_ipv6_addr_prefix(GSList *prefixes, char *address)
+{
+       struct in6_addr addr_prefix, addr;
+       GSList *list;
+       int ret = 128, len;
+
+       for (list = prefixes; list; list = list->next) {
+               char *prefix = list->data;
+               const char *slash = g_strrstr(prefix, "/");
+               const unsigned char bits[] = { 0x00, 0xFE, 0xFC, 0xF8,
+                                               0xF0, 0xE0, 0xC0, 0x80 };
+               int left, count, i, plen;
+
+               if (slash == NULL)
+                       continue;
+
+               prefix = g_strndup(prefix, slash - prefix);
+               len = strtol(slash + 1, NULL, 10);
+               plen = 128 - len;
+
+               count = plen / 8;
+               left = plen % 8;
+               i = 16 - count;
+
+               inet_pton(AF_INET6, prefix, &addr_prefix);
+               inet_pton(AF_INET6, address, &addr);
+
+               memset(&addr_prefix.s6_addr[i], 0, count);
+               memset(&addr.s6_addr[i], 0, count);
+
+               if (left) {
+                       addr_prefix.s6_addr[i - 1] &= bits[left];
+                       addr.s6_addr[i - 1] &= bits[left];
+               }
+
+               g_free(prefix);
+
+               if (memcmp(&addr_prefix, &addr, 16) == 0) {
+                       ret = len;
+                       break;
+               }
+       }
+
+       return ret;
+}
+
+static int set_addresses(GDHCPClient *dhcp_client,
+                                               struct connman_dhcpv6 *dhcp)
+{
+       struct connman_service *service;
+       struct connman_ipconfig *ipconfig;
+       int entries, i;
+       GList *option, *list;
+       char **nameservers, **timeservers;
+       const char *c_address;
+       char *address = NULL;
+
+       service = __connman_service_lookup_from_network(dhcp->network);
+       if (service == NULL) {
+               connman_error("Can not lookup service");
+               return -EINVAL;
+       }
+
+       ipconfig = __connman_service_get_ip6config(service);
+       if (ipconfig == NULL) {
+               connman_error("Could not lookup ip6config");
+               return -EINVAL;
+       }
+
+       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);
+
+
+       option = g_dhcp_client_get_option(dhcp_client, G_DHCPV6_IA_NA);
+       if (option != NULL)
+               address = g_strdup(option->data);
+       else {
+               option = g_dhcp_client_get_option(dhcp_client, G_DHCPV6_IA_TA);
+               if (option != NULL)
+                       address = g_strdup(option->data);
+       }
+
+       c_address = __connman_ipconfig_get_local(ipconfig);
+
+       if (address != NULL &&
+                       ((c_address != NULL &&
+                               g_strcmp0(address, c_address) != 0) ||
+                       (c_address == NULL))) {
+               int prefix_len;
+
+               /* Is this prefix part of the subnet we are suppose to use? */
+               prefix_len = check_ipv6_addr_prefix(dhcp->prefixes, address);
+
+               __connman_ipconfig_set_local(ipconfig, address);
+               __connman_ipconfig_set_prefixlen(ipconfig, prefix_len);
+
+               DBG("new address %s/%d", address, prefix_len);
+       }
+
+       return 0;
+}
+
 static int dhcpv6_release(struct connman_dhcpv6 *dhcp)
 {
        DBG("dhcp %p", dhcp);
@@ -398,6 +578,144 @@ int __connman_dhcpv6_start_info(struct connman_network *network,
        return 0;
 }
 
+static void advertise_cb(GDHCPClient *dhcp_client, gpointer user_data)
+{
+       struct connman_dhcpv6 *dhcp = user_data;
+
+       DBG("dhcpv6 advertise msg %p", dhcp);
+}
+
+static void solicitation_cb(GDHCPClient *dhcp_client, gpointer user_data)
+{
+       /* We get here if server supports rapid commit */
+       struct connman_dhcpv6 *dhcp = user_data;
+
+       DBG("dhcpv6 solicitation msg %p", dhcp);
+
+       if (dhcp->timeout > 0) {
+               g_source_remove(dhcp->timeout);
+               dhcp->timeout = 0;
+       }
+
+       set_addresses(dhcp_client, dhcp);
+}
+
+static gboolean timeout_solicitation(gpointer user_data)
+{
+       struct connman_dhcpv6 *dhcp = user_data;
+
+       dhcp->RT = calc_delay(dhcp->RT, SOL_MAX_RT);
+
+       DBG("solicit RT timeout %d msec", dhcp->RT);
+
+       dhcp->timeout = g_timeout_add(dhcp->RT, timeout_solicitation, dhcp);
+
+       g_dhcp_client_start(dhcp->dhcp_client, NULL);
+
+       return FALSE;
+}
+
+static int dhcpv6_solicitation(struct connman_dhcpv6 *dhcp)
+{
+       struct connman_service *service;
+       struct connman_ipconfig *ipconfig_ipv6;
+       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_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, FALSE);
+
+       clear_callbacks(dhcp_client);
+
+       g_dhcp_client_register_event(dhcp_client,
+                               G_DHCP_CLIENT_EVENT_SOLICITATION,
+                               solicitation_cb, dhcp);
+
+       g_dhcp_client_register_event(dhcp_client,
+                               G_DHCP_CLIENT_EVENT_ADVERTISE,
+                               advertise_cb, dhcp);
+
+       dhcp->dhcp_client = dhcp_client;
+
+       return g_dhcp_client_start(dhcp_client, NULL);
+}
+
+static gboolean start_solicitation(gpointer user_data)
+{
+       struct connman_dhcpv6 *dhcp = user_data;
+
+       /* Set the retransmission timeout, RFC 3315 chapter 14 */
+       dhcp->RT = SOL_TIMEOUT * (1 + get_random());
+
+       DBG("solicit initial RT timeout %d msec", dhcp->RT);
+
+       dhcp->timeout = g_timeout_add(dhcp->RT, timeout_solicitation, dhcp);
+
+       dhcpv6_solicitation(dhcp);
+
+       return FALSE;
+}
+
+int __connman_dhcpv6_start(struct connman_network *network,
+                               GSList *prefixes, 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;
+       dhcp->prefixes = prefixes;
+
+       connman_network_ref(network);
+
+       g_hash_table_replace(network_table, network, dhcp);
+
+       /* Initial timeout, RFC 3315, 17.1.2 */
+       delay = rand() % 1000;
+
+       dhcp->timeout = g_timeout_add(delay, start_solicitation, dhcp);
+
+       return 0;
+}
+
 void __connman_dhcpv6_stop(struct connman_network *network)
 {
        DBG("");
index e2266d0..f9aed49 100644 (file)
@@ -982,10 +982,60 @@ static void dhcpv6_info_callback(struct connman_network *network,
        stop_dhcpv6(network);
 }
 
+static gboolean dhcpv6_set_addresses(struct connman_network *network)
+{
+       struct connman_service *service;
+       struct connman_ipconfig *ipconfig_ipv6;
+       int err = -EINVAL;
+
+       service = __connman_service_lookup_from_network(network);
+       if (service == NULL)
+               goto err;
+
+       connman_network_set_associating(network, FALSE);
+
+       network->connecting = FALSE;
+
+       ipconfig_ipv6 = __connman_service_get_ip6config(service);
+       err = __connman_ipconfig_address_add(ipconfig_ipv6);
+       if (err < 0)
+               goto err;
+
+       err = __connman_ipconfig_gateway_add(ipconfig_ipv6);
+       if (err < 0)
+               goto err;
+
+       return 0;
+
+err:
+       connman_network_set_error(network,
+                               CONNMAN_NETWORK_ERROR_CONFIGURE_FAIL);
+       return err;
+}
+
+static void dhcpv6_callback(struct connman_network *network,
+                                       connman_bool_t success)
+{
+       DBG("success %d", success);
+
+       /* Start the renew process if necessary */
+       if (success == TRUE) {
+
+               if (dhcpv6_set_addresses(network) < 0) {
+                       stop_dhcpv6(network);
+                       return;
+               }
+
+               return;
+       } else
+               stop_dhcpv6(network);
+}
+
 static void check_dhcpv6(struct nd_router_advert *reply,
                        unsigned int length, void *user_data)
 {
        struct connman_network *network = user_data;
+       GSList *prefixes;
 
        DBG("reply %p", reply);
 
@@ -1008,8 +1058,14 @@ static void check_dhcpv6(struct nd_router_advert *reply,
 
        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)
+       prefixes = __connman_inet_ipv6_get_prefixes(reply, length);
+
+       /*
+        * We do stateful/stateless DHCPv6 if router advertisement says so.
+        */
+       if (reply->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED)
+               __connman_dhcpv6_start(network, prefixes, dhcpv6_callback);
+       else if (reply->nd_ra_flags_reserved & ND_RA_FLAG_OTHER)
                __connman_dhcpv6_start_info(network, dhcpv6_info_callback);
 
        connman_network_unref(network);
@@ -1074,6 +1130,7 @@ static gboolean set_connected(gpointer user_data)
                case CONNMAN_IPCONFIG_METHOD_UNKNOWN:
                case CONNMAN_IPCONFIG_METHOD_OFF:
                        break;
+               case CONNMAN_IPCONFIG_METHOD_DHCP:
                case CONNMAN_IPCONFIG_METHOD_AUTO:
                        autoconf_ipv6_set(network);
                        break;
@@ -1086,8 +1143,6 @@ static gboolean set_connected(gpointer user_data)
                                return FALSE;
                        }
                        break;
-               case CONNMAN_IPCONFIG_METHOD_DHCP:
-                       break;
                }
 
                switch (ipv4_method) {
@@ -1129,8 +1184,8 @@ static gboolean set_connected(gpointer user_data)
                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_DHCP:
                case CONNMAN_IPCONFIG_METHOD_AUTO:
                        stop_dhcpv6(network);
                        break;