From 195007a210055b96f42f29348cbce02915dea242 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Thu, 5 Jan 2012 13:38:09 +0200 Subject: [PATCH] dhcpv6: Initial stateful DHCPv6 support. This patch contains solicitation message support. --- gdhcp/client.c | 419 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- gdhcp/common.c | 25 ++++ gdhcp/common.h | 2 + gdhcp/gdhcp.h | 15 +++ src/connman.h | 2 + src/dhcpv6.c | 322 +++++++++++++++++++++++++++++++++++++++++++- src/network.c | 65 ++++++++- 7 files changed, 832 insertions(+), 18 deletions(-) diff --git a/gdhcp/client.c b/gdhcp/client.c index 32ed4aa..425f1cb 100644 --- a/gdhcp/client.c +++ b/gdhcp/client.c @@ -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); diff --git a/gdhcp/common.c b/gdhcp/common.c index 46cf5f6..c511e26 100644 --- a/gdhcp/common.c +++ b/gdhcp/common.c @@ -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] diff --git a/gdhcp/common.h b/gdhcp/common.h index ae4029c..4b7df7e 100644 --- a/gdhcp/common.h +++ b/gdhcp/common.h @@ -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, diff --git a/gdhcp/gdhcp.h b/gdhcp/gdhcp.h index c0385d4..5684854 100644 --- a/gdhcp/gdhcp.h +++ b/gdhcp/gdhcp.h @@ -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 { diff --git a/src/connman.h b/src/connman.h index 49469cc..f48f922 100644 --- a/src/connman.h +++ b/src/connman.h @@ -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); diff --git a/src/dhcpv6.c b/src/dhcpv6.c index bedcc7f..a19eee0 100644 --- a/src/dhcpv6.c +++ b/src/dhcpv6.c @@ -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(""); diff --git a/src/network.c b/src/network.c index e2266d0..f9aed49 100644 --- a/src/network.c +++ b/src/network.c @@ -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; -- 2.7.4