diff options
-rw-r--r-- | gdhcp/client.c | 70 | ||||
-rw-r--r-- | gdhcp/gdhcp.h | 4 | ||||
-rw-r--r-- | src/dhcpv6.c | 181 |
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; } |