dhcpv6: Implement CONFIRM message support
authorJukka Rissanen <jukka.rissanen@linux.intel.com>
Mon, 6 May 2013 10:06:34 +0000 (13:06 +0300)
committerPatrik Flykt <patrik.flykt@linux.intel.com>
Mon, 6 May 2013 11:34:20 +0000 (14:34 +0300)
See RFC 3315 Chapter 18.1.2. Creation and Transmission of Confirm
Messages for details

gdhcp/client.c
gdhcp/gdhcp.h
src/dhcpv6.c

index 305533f..fca5fff 100644 (file)
@@ -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:
index 0820cdd..ba47eaf 100644 (file)
@@ -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);
index 0ac7f4c..703bedb 100644 (file)
 #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;
 }