X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=gdhcp%2Fclient.c;h=09dfe5ec2991ca36acf8747014c41508066b057c;hb=dd3cccc5e67548dcc2dd6c6254ed6c97859085d5;hp=7743aa508e40bdfbeb4d9d9b81ad0572b738de87;hpb=5f5b3609c4f9884954623d7e6bf2ae1ae7096637;p=platform%2Fupstream%2Fconnman.git diff --git a/gdhcp/client.c b/gdhcp/client.c index 7743aa5..09dfe5e 100644 --- a/gdhcp/client.c +++ b/gdhcp/client.c @@ -2,7 +2,7 @@ * * DHCP client library with GLib integration * - * Copyright (C) 2009-2011 Intel Corporation. All rights reserved. + * Copyright (C) 2009-2014 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 @@ -23,7 +23,6 @@ #include #endif -#define _GNU_SOURCE #include #include #include @@ -31,6 +30,8 @@ #include #include #include +#include +#include #include #include @@ -41,15 +42,16 @@ #include +#include "../src/connman.h" +#include "../src/shared/arp.h" #include "gdhcp.h" #include "common.h" -#include "ipv4ll.h" -#define DISCOVER_TIMEOUT 3 -#define DISCOVER_RETRIES 10 +#define DISCOVER_TIMEOUT 5 +#define DISCOVER_RETRIES 6 -#define REQUEST_TIMEOUT 3 -#define REQUEST_RETRIES 5 +#define REQUEST_TIMEOUT 5 +#define REQUEST_RETRIES 3 typedef enum _listen_mode { L_NONE, @@ -60,8 +62,10 @@ typedef enum _listen_mode { typedef enum _dhcp_client_state { INIT_SELECTING, + REBOOTING, REQUESTING, BOUND, + DECLINED, RENEWING, REBINDING, RELEASED, @@ -72,8 +76,11 @@ typedef enum _dhcp_client_state { INFORMATION_REQ, SOLICITATION, REQUEST, + CONFIRM, RENEW, REBIND, + RELEASE, + DECLINE, } ClientState; struct _GDHCPClient { @@ -87,6 +94,7 @@ struct _GDHCPClient { uint32_t server_ip; uint32_t requested_ip; char *assigned_ip; + time_t start; uint32_t lease_seconds; ListenMode listen_mode; int listener_sockfd; @@ -94,12 +102,15 @@ struct _GDHCPClient { uint8_t ack_retry_times; uint8_t conflicts; guint timeout; + guint t1_timeout; + guint t2_timeout; + guint lease_timeout; guint listener_watch; - GIOChannel *listener_channel; GList *require_list; GList *request_list; GHashTable *code_value_hash; GHashTable *send_value_hash; + GHashTable *secs_bcast_hash; GDHCPClientEventFunc lease_available_cb; gpointer lease_available_data; GDHCPClientEventFunc ipv4ll_available_cb; @@ -126,6 +137,12 @@ struct _GDHCPClient { gpointer renew_data; GDHCPClientEventFunc rebind_cb; gpointer rebind_data; + GDHCPClientEventFunc release_cb; + gpointer release_data; + GDHCPClientEventFunc confirm_cb; + gpointer confirm_data; + GDHCPClientEventFunc decline_cb; + gpointer decline_data; char *last_address; unsigned char *duid; int duid_len; @@ -136,9 +153,11 @@ struct _GDHCPClient { uint32_t T1, T2; struct in6_addr ia_na; struct in6_addr ia_ta; - time_t last_renew; - time_t last_rebind; - time_t expire; + time_t last_request; + uint32_t expire; + bool retransmit; + struct timeval start_time; + bool request_bcast; }; static inline void debug(GDHCPClient *client, const char *format, ...) @@ -146,7 +165,7 @@ static inline void debug(GDHCPClient *client, const char *format, ...) char str[256]; va_list ap; - if (client->debug_func == NULL) + if (!client->debug_func) return; va_start(ap, format); @@ -225,7 +244,7 @@ static void add_dhcpv6_send_options(GDHCPClient *dhcp_client, .ptr_buf = ptr_buf }; - if (dhcp_client->type == G_DHCP_IPV4) + if (dhcp_client->type != G_DHCP_IPV6) return; g_hash_table_foreach(dhcp_client->send_value_hash, @@ -241,28 +260,63 @@ static void copy_option(uint8_t *buf, uint16_t code, uint16_t len, buf[1] = code & 0xff; buf[2] = len >> 8; buf[3] = len & 0xff; - if (len > 0 && msg != NULL) + if (len > 0 && msg) memcpy(&buf[4], msg, len); } +static int32_t get_time_diff(struct timeval *tv) +{ + struct timeval now; + int32_t hsec; + + gettimeofday(&now, NULL); + + hsec = (now.tv_sec - tv->tv_sec) * 100; + hsec += (now.tv_usec - tv->tv_usec) / 10000; + + return hsec; +} + +static void remove_timeouts(GDHCPClient *dhcp_client) +{ + + if (dhcp_client->timeout > 0) + g_source_remove(dhcp_client->timeout); + if (dhcp_client->t1_timeout > 0) + g_source_remove(dhcp_client->t1_timeout); + if (dhcp_client->t2_timeout > 0) + g_source_remove(dhcp_client->t2_timeout); + if (dhcp_client->lease_timeout > 0) + g_source_remove(dhcp_client->lease_timeout); + + dhcp_client->timeout = 0; + dhcp_client->t1_timeout = 0; + dhcp_client->t2_timeout = 0; + dhcp_client->lease_timeout = 0; + +} + static void add_dhcpv6_request_options(GDHCPClient *dhcp_client, struct dhcpv6_packet *packet, unsigned char *buf, int max_buf, unsigned char **ptr_buf) { GList *list; - uint16_t code; + uint16_t code, value; + bool added; + int32_t diff; int len; - if (dhcp_client->type == G_DHCP_IPV4) + if (dhcp_client->type != G_DHCP_IPV6) return; for (list = dhcp_client->request_list; list; list = list->next) { code = (uint16_t) GPOINTER_TO_INT(list->data); + added = false; switch (code) { case G_DHCPV6_CLIENTID: - if (dhcp_client->duid == NULL) + if (!dhcp_client->duid) return; len = 2 + 2 + dhcp_client->duid_len; @@ -275,11 +329,12 @@ static void add_dhcpv6_request_options(GDHCPClient *dhcp_client, copy_option(*ptr_buf, G_DHCPV6_CLIENTID, dhcp_client->duid_len, dhcp_client->duid); (*ptr_buf) += len; + added = true; break; case G_DHCPV6_SERVERID: - if (dhcp_client->server_duid == NULL) - return; + if (!dhcp_client->server_duid) + break; len = 2 + 2 + dhcp_client->server_duid_len; if ((*ptr_buf + len) > (buf + max_buf)) { @@ -292,6 +347,7 @@ static void add_dhcpv6_request_options(GDHCPClient *dhcp_client, dhcp_client->server_duid_len, dhcp_client->server_duid); (*ptr_buf) += len; + added = true; break; case G_DHCPV6_RAPID_COMMIT: @@ -304,20 +360,53 @@ static void add_dhcpv6_request_options(GDHCPClient *dhcp_client, copy_option(*ptr_buf, G_DHCPV6_RAPID_COMMIT, 0, 0); (*ptr_buf) += len; + added = true; break; case G_DHCPV6_ORO: break; + case G_DHCPV6_ELAPSED_TIME: + if (!dhcp_client->retransmit) { + /* + * Initial message, elapsed time is 0. + */ + diff = 0; + } else { + diff = get_time_diff(&dhcp_client->start_time); + if (diff < 0 || diff > 0xffff) + diff = 0xffff; + } + + len = 2 + 2 + 2; + if ((*ptr_buf + len) > (buf + max_buf)) { + debug(dhcp_client, "Too long dhcpv6 message " + "when writing elapsed time option"); + return; + } + + value = htons((uint16_t)diff); + copy_option(*ptr_buf, G_DHCPV6_ELAPSED_TIME, + 2, (uint8_t *)&value); + (*ptr_buf) += len; + added = true; + break; + case G_DHCPV6_DNS_SERVERS: break; + case G_DHCPV6_DOMAIN_LIST: + break; + case G_DHCPV6_SNTP_SERVERS: break; default: break; } + + if (added) + debug(dhcp_client, "option %d len %d added", code, len); } } @@ -336,6 +425,17 @@ static void add_send_options(GDHCPClient *dhcp_client, add_binary_option, packet); } +/* + * Return an RFC 951- and 2131-complaint BOOTP 'secs' value that + * represents the number of seconds elapsed from the start of + * attempting DHCP to satisfy some DHCP servers that allow for an + * "authoritative" reply before responding. + */ +static uint16_t dhcp_attempt_secs(GDHCPClient *dhcp_client) +{ + return htons(MIN(time(NULL) - dhcp_client->start, UINT16_MAX)); +} + static int send_discover(GDHCPClient *dhcp_client, uint32_t requested) { struct dhcp_packet packet; @@ -345,99 +445,122 @@ static int send_discover(GDHCPClient *dhcp_client, uint32_t requested) init_packet(dhcp_client, &packet, DHCPDISCOVER); packet.xid = dhcp_client->xid; + packet.secs = dhcp_attempt_secs(dhcp_client); if (requested) - dhcp_add_simple_option(&packet, DHCP_REQUESTED_IP, requested); + dhcp_add_option_uint32(&packet, DHCP_REQUESTED_IP, requested); /* Explicitly saying that we want RFC-compliant packets helps * some buggy DHCP servers to NOT send bigger packets */ - dhcp_add_simple_option(&packet, DHCP_MAX_SIZE, htons(576)); + dhcp_add_option_uint16(&packet, DHCP_MAX_SIZE, 576); add_request_options(dhcp_client, &packet); add_send_options(dhcp_client, &packet); + /* + * If we do not get a reply to DISCOVER packet, then we try with + * broadcast flag set. So first packet is sent without broadcast flag, + * first retry is with broadcast flag, second retry is without it etc. + * Reason is various buggy routers/AP that either eat the other or vice + * versa. In the receiving side we then find out what kind of packet + * the server can send. + */ + dhcp_client->request_bcast = dhcp_client->retry_times % 2; + + if (dhcp_client->request_bcast) + g_hash_table_add(dhcp_client->secs_bcast_hash, + GINT_TO_POINTER(packet.secs)); + return dhcp_send_raw_packet(&packet, INADDR_ANY, CLIENT_PORT, - INADDR_BROADCAST, SERVER_PORT, - MAC_BCAST_ADDR, dhcp_client->ifindex); + INADDR_BROADCAST, SERVER_PORT, + MAC_BCAST_ADDR, dhcp_client->ifindex, + dhcp_client->request_bcast); } -static int send_select(GDHCPClient *dhcp_client) +int g_dhcp_client_decline(GDHCPClient *dhcp_client, uint32_t requested) { struct dhcp_packet packet; - debug(dhcp_client, "sending DHCP select request"); + dhcp_client->state = DECLINED; + dhcp_client->retry_times = 0; - init_packet(dhcp_client, &packet, DHCPREQUEST); + debug(dhcp_client, "sending DHCP decline"); - packet.xid = dhcp_client->xid; + init_packet(dhcp_client, &packet, DHCPDECLINE); - dhcp_add_simple_option(&packet, DHCP_REQUESTED_IP, - dhcp_client->requested_ip); - dhcp_add_simple_option(&packet, DHCP_SERVER_ID, dhcp_client->server_ip); + packet.xid = dhcp_client->xid; + packet.secs = dhcp_attempt_secs(dhcp_client); - add_request_options(dhcp_client, &packet); + if (requested) + dhcp_add_option_uint32(&packet, DHCP_REQUESTED_IP, requested); add_send_options(dhcp_client, &packet); return dhcp_send_raw_packet(&packet, INADDR_ANY, CLIENT_PORT, - INADDR_BROADCAST, SERVER_PORT, - MAC_BCAST_ADDR, dhcp_client->ifindex); + INADDR_BROADCAST, SERVER_PORT, MAC_BCAST_ADDR, + dhcp_client->ifindex, true); } -static int send_renew(GDHCPClient *dhcp_client) +static int send_request(GDHCPClient *dhcp_client) { struct dhcp_packet packet; - debug(dhcp_client, "sending DHCP renew request"); - - init_packet(dhcp_client , &packet, DHCPREQUEST); - packet.xid = dhcp_client->xid; - packet.ciaddr = dhcp_client->requested_ip; - - add_request_options(dhcp_client, &packet); + debug(dhcp_client, "sending DHCP request (state %d)", + dhcp_client->state); - add_send_options(dhcp_client, &packet); + init_packet(dhcp_client, &packet, DHCPREQUEST); - return dhcp_send_kernel_packet(&packet, - dhcp_client->requested_ip, CLIENT_PORT, - dhcp_client->server_ip, SERVER_PORT); -} + packet.xid = dhcp_client->xid; + packet.secs = dhcp_attempt_secs(dhcp_client); -static int send_rebound(GDHCPClient *dhcp_client) -{ - struct dhcp_packet packet; + if (dhcp_client->state == REQUESTING || dhcp_client->state == REBOOTING) + dhcp_add_option_uint32(&packet, DHCP_REQUESTED_IP, + dhcp_client->requested_ip); - debug(dhcp_client, "sending DHCP rebound request"); + if (dhcp_client->state == REQUESTING) + dhcp_add_option_uint32(&packet, DHCP_SERVER_ID, + dhcp_client->server_ip); - init_packet(dhcp_client , &packet, DHCPREQUEST); - packet.xid = dhcp_client->xid; - packet.ciaddr = dhcp_client->requested_ip; + dhcp_add_option_uint16(&packet, DHCP_MAX_SIZE, 576); add_request_options(dhcp_client, &packet); add_send_options(dhcp_client, &packet); + if (dhcp_client->state == RENEWING || dhcp_client->state == REBINDING) + packet.ciaddr = htonl(dhcp_client->requested_ip); + + if (dhcp_client->state == RENEWING) + return dhcp_send_kernel_packet(&packet, + dhcp_client->requested_ip, CLIENT_PORT, + dhcp_client->server_ip, SERVER_PORT, + dhcp_client->interface); + return dhcp_send_raw_packet(&packet, INADDR_ANY, CLIENT_PORT, - INADDR_BROADCAST, SERVER_PORT, - MAC_BCAST_ADDR, dhcp_client->ifindex); + INADDR_BROADCAST, SERVER_PORT, + MAC_BCAST_ADDR, dhcp_client->ifindex, + dhcp_client->request_bcast); } static int send_release(GDHCPClient *dhcp_client, uint32_t server, uint32_t ciaddr) { struct dhcp_packet packet; + uint64_t rand; debug(dhcp_client, "sending DHCP release request"); init_packet(dhcp_client, &packet, DHCPRELEASE); - packet.xid = rand(); - packet.ciaddr = ciaddr; + __connman_util_get_random(&rand); + packet.xid = rand; + packet.ciaddr = htonl(ciaddr); - dhcp_add_simple_option(&packet, DHCP_SERVER_ID, server); + dhcp_add_option_uint32(&packet, DHCP_SERVER_ID, server); return dhcp_send_kernel_packet(&packet, ciaddr, CLIENT_PORT, - server, SERVER_PORT); + server, SERVER_PORT, + dhcp_client->interface); } static gboolean ipv4ll_probe_timeout(gpointer dhcp_data); @@ -453,7 +576,7 @@ static gboolean send_probe_packet(gpointer dhcp_data) /* if requested_ip is not valid, pick a new address*/ if (dhcp_client->requested_ip == 0) { debug(dhcp_client, "pick a new random address"); - dhcp_client->requested_ip = ipv4ll_random_ip(0); + dhcp_client->requested_ip = arp_random_ip(); } debug(dhcp_client, "sending IPV4LL probe request"); @@ -462,12 +585,12 @@ static gboolean send_probe_packet(gpointer dhcp_data) dhcp_client->state = IPV4LL_PROBE; switch_listening_mode(dhcp_client, L_ARP); } - ipv4ll_send_arp_packet(dhcp_client->mac_address, 0, + arp_send_packet(dhcp_client->mac_address, 0, dhcp_client->requested_ip, dhcp_client->ifindex); if (dhcp_client->retry_times < PROBE_NUM) { /*add a random timeout in range of PROBE_MIN to PROBE_MAX*/ - timeout = ipv4ll_random_delay_ms(PROBE_MAX-PROBE_MIN); + timeout = __connman_util_random_delay_ms(PROBE_MAX-PROBE_MIN); timeout += PROBE_MIN*1000; } else timeout = (ANNOUNCE_WAIT * 1000); @@ -491,14 +614,12 @@ static gboolean send_announce_packet(gpointer dhcp_data) debug(dhcp_client, "sending IPV4LL announce request"); - ipv4ll_send_arp_packet(dhcp_client->mac_address, + arp_send_packet(dhcp_client->mac_address, dhcp_client->requested_ip, dhcp_client->requested_ip, dhcp_client->ifindex); - if (dhcp_client->timeout > 0) - g_source_remove(dhcp_client->timeout); - dhcp_client->timeout = 0; + remove_timeouts(dhcp_client); if (dhcp_client->state == IPV4LL_DEFEND) { dhcp_client->timeout = @@ -518,36 +639,20 @@ static gboolean send_announce_packet(gpointer dhcp_data) return TRUE; } -static void get_interface_mac_address(int index, uint8_t *mac_address) +void g_dhcpv6_client_set_retransmit(GDHCPClient *dhcp_client) { - struct ifreq ifr; - int sk, err; - - sk = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); - if (sk < 0) { - perror("Open socket error"); + if (!dhcp_client) return; - } - - memset(&ifr, 0, sizeof(ifr)); - ifr.ifr_ifindex = index; - - err = ioctl(sk, SIOCGIFNAME, &ifr); - if (err < 0) { - perror("Get interface name error"); - goto done; - } - err = ioctl(sk, SIOCGIFHWADDR, &ifr); - if (err < 0) { - perror("Get mac address error"); - goto done; - } + dhcp_client->retransmit = true; +} - memcpy(mac_address, ifr.ifr_hwaddr.sa_data, 6); +void g_dhcpv6_client_clear_retransmit(GDHCPClient *dhcp_client) +{ + if (!dhcp_client) + return; -done: - close(sk); + dhcp_client->retransmit = false; } int g_dhcpv6_create_duid(GDHCPDuidType duid_type, int index, int type, @@ -559,15 +664,15 @@ int g_dhcpv6_create_duid(GDHCPDuidType duid_type, int index, int type, case G_DHCPV6_DUID_LLT: *duid_len = 2 + 2 + 4 + ETH_ALEN; *duid = g_try_malloc(*duid_len); - if (*duid == NULL) + if (!*duid) return -ENOMEM; (*duid)[0] = 0; (*duid)[1] = 1; - get_interface_mac_address(index, &(*duid)[2 + 2 + 4]); + __connman_inet_get_interface_mac_address(index, &(*duid)[2 + 2 + 4]); (*duid)[2] = 0; (*duid)[3] = type; - duid_time = time(0) - DUID_TIME_EPOCH; + duid_time = time(NULL) - DUID_TIME_EPOCH; (*duid)[4] = duid_time >> 24; (*duid)[5] = duid_time >> 16; (*duid)[6] = duid_time >> 8; @@ -578,12 +683,12 @@ int g_dhcpv6_create_duid(GDHCPDuidType duid_type, int index, int type, case G_DHCPV6_DUID_LL: *duid_len = 2 + 2 + ETH_ALEN; *duid = g_try_malloc(*duid_len); - if (*duid == NULL) + if (!*duid) return -ENOMEM; (*duid)[0] = 0; (*duid)[1] = 3; - get_interface_mac_address(index, &(*duid)[2 + 2]); + __connman_inet_get_interface_mac_address(index, &(*duid)[2 + 2]); (*duid)[2] = 0; (*duid)[3] = type; break; @@ -592,10 +697,21 @@ int g_dhcpv6_create_duid(GDHCPDuidType duid_type, int index, int type, return 0; } +static gchar *convert_to_hex(unsigned char *buf, int len) +{ + gchar *ret = g_try_malloc(len * 2 + 1); + int i; + + for (i = 0; ret && i < len; i++) + g_snprintf(ret + i * 2, 3, "%02x", buf[i]); + + return ret; +} + int g_dhcpv6_client_set_duid(GDHCPClient *dhcp_client, unsigned char *duid, int duid_len) { - if (dhcp_client == NULL || dhcp_client->type == G_DHCP_IPV4) + if (!dhcp_client || dhcp_client->type != G_DHCP_IPV6) return -EINVAL; g_free(dhcp_client->duid); @@ -603,23 +719,101 @@ int g_dhcpv6_client_set_duid(GDHCPClient *dhcp_client, unsigned char *duid, dhcp_client->duid = duid; dhcp_client->duid_len = duid_len; + if (dhcp_client->debug_func) { + gchar *hex = convert_to_hex(duid, duid_len); + debug(dhcp_client, "DUID(%d) %s", duid_len, hex); + g_free(hex); + } + + return 0; +} + +int g_dhcpv6_client_set_pd(GDHCPClient *dhcp_client, uint32_t *T1, + uint32_t *T2, GSList *prefixes) +{ + uint8_t options[1452]; + unsigned int max_buf = sizeof(options); + int len, count = g_slist_length(prefixes); + + if (!dhcp_client || dhcp_client->type != G_DHCP_IPV6) + return -EINVAL; + + g_dhcp_client_set_request(dhcp_client, G_DHCPV6_IA_PD); + + memset(options, 0, sizeof(options)); + + options[0] = dhcp_client->iaid >> 24; + options[1] = dhcp_client->iaid >> 16; + options[2] = dhcp_client->iaid >> 8; + options[3] = dhcp_client->iaid; + + if (T1) { + uint32_t t = htonl(*T1); + memcpy(&options[4], &t, 4); + } + + if (T2) { + uint32_t t = htonl(*T2); + memcpy(&options[8], &t, 4); + } + + len = 12; + + if (count > 0) { + GSList *list; + + for (list = prefixes; list; list = list->next) { + GDHCPIAPrefix *prefix = list->data; + uint8_t sub_option[4+4+1+16]; + + if ((len + 2 + 2 + sizeof(sub_option)) >= max_buf) { + debug(dhcp_client, + "Too long dhcpv6 message " + "when writing IA prefix option"); + return -EINVAL; + } + + memset(&sub_option, 0, sizeof(sub_option)); + + /* preferred and validity time are left zero */ + + sub_option[8] = prefix->prefixlen; + memcpy(&sub_option[9], &prefix->prefix, 16); + + copy_option(&options[len], G_DHCPV6_IA_PREFIX, + sizeof(sub_option), sub_option); + len += 2 + 2 + sizeof(sub_option); + } + } + + g_dhcpv6_client_set_send(dhcp_client, G_DHCPV6_IA_PD, + options, len); + return 0; } uint32_t g_dhcpv6_client_get_iaid(GDHCPClient *dhcp_client) { - if (dhcp_client == NULL || dhcp_client->type == G_DHCP_IPV4) + if (!dhcp_client || dhcp_client->type != G_DHCP_IPV6) return 0; return dhcp_client->iaid; } +void g_dhcpv6_client_set_iaid(GDHCPClient *dhcp_client, uint32_t iaid) +{ + if (!dhcp_client || dhcp_client->type != G_DHCP_IPV6) + return; + + dhcp_client->iaid = 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); + __connman_inet_get_interface_mac_address(index, buf); memcpy(iaid, &buf[2], 4); dhcp_client->iaid = iaid[0] << 24 | @@ -628,26 +822,26 @@ void g_dhcpv6_client_create_iaid(GDHCPClient *dhcp_client, int index, int g_dhcpv6_client_get_timeouts(GDHCPClient *dhcp_client, uint32_t *T1, uint32_t *T2, - time_t *last_renew, time_t *last_rebind, + time_t *started, time_t *expire) { - if (dhcp_client == NULL || dhcp_client->type == G_DHCP_IPV4) + if (!dhcp_client || dhcp_client->type != G_DHCP_IPV6) return -EINVAL; - if (T1 != NULL) - *T1 = dhcp_client->T1; - - if (T2 != NULL) - *T2 = dhcp_client->T2; + if (T1) + *T1 = (dhcp_client->expire == 0xffffffff) ? 0xffffffff: + dhcp_client->T1; - if (last_renew != NULL) - *last_renew = dhcp_client->last_renew; + if (T2) + *T2 = (dhcp_client->expire == 0xffffffff) ? 0xffffffff: + dhcp_client->T2; - if (last_rebind != NULL) - *last_rebind = dhcp_client->last_rebind; + if (started) + *started = dhcp_client->last_request; - if (expire != NULL) - *expire = dhcp_client->expire; + if (expire) + *expire = (dhcp_client->expire == 0xffffffff) ? 0xffffffff: + dhcp_client->last_request + dhcp_client->expire; return 0; } @@ -665,6 +859,24 @@ static uint8_t *create_iaaddr(GDHCPClient *dhcp_client, uint8_t *buf, return buf; } +static uint8_t *append_iaaddr(GDHCPClient *dhcp_client, uint8_t *buf, + const char *address) +{ + struct in6_addr addr; + + if (inet_pton(AF_INET6, address, &addr) != 1) + return NULL; + + buf[0] = 0; + buf[1] = G_DHCPV6_IAADDR; + buf[2] = 0; + buf[3] = 24; + memcpy(&buf[4], &addr, 16); + memset(&buf[20], 0, 4); /* preferred */ + memset(&buf[24], 0, 4); /* valid */ + return &buf[28]; +} + static void put_iaid(GDHCPClient *dhcp_client, int index, uint8_t *buf) { uint32_t iaid; @@ -683,7 +895,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) + bool add_iaaddr, const char *ia_na) { if (code == G_DHCPV6_IA_TA) { uint8_t ia_options[4]; @@ -695,16 +907,32 @@ 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 && ((!ia_na && + !IN6_IS_ADDR_UNSPECIFIED(&dhcp_client->ia_na)) + || (ia_na && + 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) + memcpy(&dhcp_client->ia_na, &addr, + sizeof(struct in6_addr)); + put_iaid(dhcp_client, index, ia_options); - if (T1 != NULL) { + if (T1) { ia_options[4] = *T1 >> 24; ia_options[5] = *T1 >> 16; ia_options[6] = *T1 >> 8; @@ -712,7 +940,7 @@ int g_dhcpv6_client_set_ia(GDHCPClient *dhcp_client, int index, } else memset(&ia_options[4], 0x00, 4); - if (T2 != NULL) { + if (T2) { ia_options[8] = *T2 >> 24; ia_options[9] = *T2 >> 16; ia_options[10] = *T2 >> 8; @@ -743,6 +971,67 @@ int g_dhcpv6_client_set_ia(GDHCPClient *dhcp_client, int index, return 0; } +int g_dhcpv6_client_set_ias(GDHCPClient *dhcp_client, int index, + int code, uint32_t *T1, uint32_t *T2, + GSList *addresses) +{ + GSList *list; + uint8_t *ia_options, *pos; + int len, count, total_len; + + count = g_slist_length(addresses); + if (count == 0) + return -EINVAL; + + g_dhcp_client_set_request(dhcp_client, code); + + if (code == G_DHCPV6_IA_TA) + len = 4; /* IAID */ + else if (code == G_DHCPV6_IA_NA) + len = 4 + 4 + 4; /* IAID + T1 + T2 */ + else + return -EINVAL; + + total_len = len + count * (2 + 2 + 16 + 4 + 4); + ia_options = g_try_malloc0(total_len); + if (!ia_options) + return -ENOMEM; + + put_iaid(dhcp_client, index, ia_options); + + pos = &ia_options[len]; /* skip the IA_NA or IA_TA */ + + for (list = addresses; list; list = list->next) { + pos = append_iaaddr(dhcp_client, pos, list->data); + if (!pos) + break; + } + + if (code == G_DHCPV6_IA_NA) { + if (T1) { + 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) { + 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); + } + + g_dhcpv6_client_set_send(dhcp_client, code, ia_options, total_len); + + g_free(ia_options); + + return 0; +} + int g_dhcpv6_client_set_oro(GDHCPClient *dhcp_client, int args, ...) { va_list va; @@ -750,7 +1039,7 @@ int g_dhcpv6_client_set_oro(GDHCPClient *dhcp_client, int args, ...) uint8_t *values; values = g_try_malloc(len); - if (values == NULL) + if (!values) return -ENOMEM; va_start(va, args); @@ -763,6 +1052,8 @@ int g_dhcpv6_client_set_oro(GDHCPClient *dhcp_client, int args, ...) g_dhcpv6_client_set_send(dhcp_client, G_DHCPV6_ORO, values, len); + g_free(values); + return 0; } @@ -777,13 +1068,23 @@ static int send_dhcpv6_msg(GDHCPClient *dhcp_client, int type, char *msg) packet = (struct dhcpv6_packet *)&buf[0]; ptr = buf + sizeof(struct dhcpv6_packet); - debug(dhcp_client, "sending DHCPv6 %s message", msg); - init_packet(dhcp_client, packet, type); - dhcp_client->xid = packet->transaction_id[0] << 16 | - packet->transaction_id[1] << 8 | - packet->transaction_id[2]; + if (!dhcp_client->retransmit) { + dhcp_client->xid = packet->transaction_id[0] << 16 | + packet->transaction_id[1] << 8 | + packet->transaction_id[2]; + gettimeofday(&dhcp_client->start_time, NULL); + } else { + packet->transaction_id[0] = dhcp_client->xid >> 16; + packet->transaction_id[1] = dhcp_client->xid >> 8 ; + packet->transaction_id[2] = dhcp_client->xid; + } + + g_dhcp_client_set_request(dhcp_client, G_DHCPV6_ELAPSED_TIME); + + debug(dhcp_client, "sending DHCPv6 %s message xid 0x%04x", msg, + dhcp_client->xid); max_buf = MAX_DHCPV6_PKT_SIZE - sizeof(struct dhcpv6_packet); @@ -807,6 +1108,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"); @@ -817,6 +1123,16 @@ static int send_dhcpv6_rebind(GDHCPClient *dhcp_client) return send_dhcpv6_msg(dhcp_client, DHCPV6_REBIND, "rebind"); } +static int send_dhcpv6_decline(GDHCPClient *dhcp_client) +{ + return send_dhcpv6_msg(dhcp_client, DHCPV6_DECLINE, "decline"); +} + +static int send_dhcpv6_release(GDHCPClient *dhcp_client) +{ + return send_dhcpv6_msg(dhcp_client, DHCPV6_RELEASE, "release"); +} + static int send_information_req(GDHCPClient *dhcp_client) { return send_dhcpv6_msg(dhcp_client, DHCPV6_INFORMATION_REQ, @@ -834,6 +1150,7 @@ static void remove_option_value(gpointer data) GList *option_value = data; g_list_foreach(option_value, remove_value, NULL); + g_list_free(option_value); } GDHCPClient *g_dhcp_client_new(GDHCPType type, @@ -847,26 +1164,25 @@ GDHCPClient *g_dhcp_client_new(GDHCPType type, } dhcp_client = g_try_new0(GDHCPClient, 1); - if (dhcp_client == NULL) { + if (!dhcp_client) { *error = G_DHCP_CLIENT_ERROR_NOMEM; return NULL; } dhcp_client->interface = get_interface_name(ifindex); - if (dhcp_client->interface == NULL) { + if (!dhcp_client->interface) { *error = G_DHCP_CLIENT_ERROR_INTERFACE_UNAVAILABLE; goto error; } - if (interface_is_up(ifindex) == FALSE) { + if (!interface_is_up(ifindex)) { *error = G_DHCP_CLIENT_ERROR_INTERFACE_DOWN; goto error; } - get_interface_mac_address(ifindex, dhcp_client->mac_address); + __connman_inet_get_interface_mac_address(ifindex, dhcp_client->mac_address); dhcp_client->listener_sockfd = -1; - dhcp_client->listener_channel = NULL; dhcp_client->listen_mode = L_NONE; dhcp_client->ref_count = 1; dhcp_client->type = type; @@ -884,12 +1200,15 @@ GDHCPClient *g_dhcp_client_new(GDHCPType type, g_direct_equal, NULL, remove_option_value); dhcp_client->send_value_hash = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free); + dhcp_client->secs_bcast_hash = g_hash_table_new(g_direct_hash, + g_direct_equal); dhcp_client->request_list = NULL; dhcp_client->require_list = NULL; dhcp_client->duid = NULL; dhcp_client->duid_len = 0; - dhcp_client->last_renew = dhcp_client->last_rebind = time(0); + dhcp_client->last_request = time(NULL); dhcp_client->expire = 0; + dhcp_client->request_bcast = false; *error = G_DHCP_CLIENT_ERROR_NONE; @@ -954,9 +1273,9 @@ static int dhcp_l2_socket(int ifindex) .filter = (struct sock_filter *) filter_instr, }; - fd = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, htons(ETH_P_IP)); + fd = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, 0); if (fd < 0) - return fd; + return -errno; if (SERVER_PORT == 67 && CLIENT_PORT == 68) /* Use only if standard ports are in use */ @@ -969,34 +1288,36 @@ static int dhcp_l2_socket(int ifindex) sock.sll_ifindex = ifindex; if (bind(fd, (struct sockaddr *) &sock, sizeof(sock)) != 0) { + int err = -errno; close(fd); - return -errno; + return err; } return fd; } -static gboolean sanity_check(struct ip_udp_dhcp_packet *packet, int bytes) +static bool sanity_check(struct ip_udp_dhcp_packet *packet, int bytes) { if (packet->ip.protocol != IPPROTO_UDP) - return FALSE; + return false; if (packet->ip.version != IPVERSION) - return FALSE; + return false; if (packet->ip.ihl != sizeof(packet->ip) >> 2) - return FALSE; + return false; if (packet->udp.dest != htons(CLIENT_PORT)) - return FALSE; + return false; if (ntohs(packet->udp.len) != (uint16_t)(bytes - sizeof(packet->ip))) - return FALSE; + return false; - return TRUE; + return true; } -static int dhcp_recv_l2_packet(struct dhcp_packet *dhcp_pkt, int fd) +static int dhcp_recv_l2_packet(struct dhcp_packet *dhcp_pkt, int fd, + struct sockaddr_in *dst_addr) { int bytes; struct ip_udp_dhcp_packet packet; @@ -1018,7 +1339,7 @@ static int dhcp_recv_l2_packet(struct dhcp_packet *dhcp_pkt, int fd) /* ignore any extra garbage bytes */ bytes = ntohs(packet.ip.tot_len); - if (sanity_check(&packet, bytes) == FALSE) + if (!sanity_check(&packet, bytes)) return -1; check = packet.ip.check; @@ -1041,30 +1362,25 @@ static int dhcp_recv_l2_packet(struct dhcp_packet *dhcp_pkt, int fd) if (dhcp_pkt->cookie != htonl(DHCP_MAGIC)) return -1; + dst_addr->sin_addr.s_addr = packet.ip.daddr; + return bytes - (sizeof(packet.ip) + sizeof(packet.udp)); } static void ipv4ll_start(GDHCPClient *dhcp_client) { guint timeout; - int seed; - if (dhcp_client->timeout > 0) { - g_source_remove(dhcp_client->timeout); - dhcp_client->timeout = 0; - } + remove_timeouts(dhcp_client); switch_listening_mode(dhcp_client, L_NONE); - dhcp_client->type = G_DHCP_IPV4LL; dhcp_client->retry_times = 0; dhcp_client->requested_ip = 0; - /*try to start with a based mac address ip*/ - seed = (dhcp_client->mac_address[4] << 8 | dhcp_client->mac_address[4]); - dhcp_client->requested_ip = ipv4ll_random_ip(seed); + dhcp_client->requested_ip = arp_random_ip(); /*first wait a random delay to avoid storm of arp request on boot*/ - timeout = ipv4ll_random_delay_ms(PROBE_WAIT); + timeout = __connman_util_random_delay_ms(PROBE_WAIT); dhcp_client->retry_times++; dhcp_client->timeout = g_timeout_add_full(G_PRIORITY_HIGH, @@ -1079,8 +1395,7 @@ static void ipv4ll_stop(GDHCPClient *dhcp_client) switch_listening_mode(dhcp_client, L_NONE); - if (dhcp_client->timeout > 0) - g_source_remove(dhcp_client->timeout); + remove_timeouts(dhcp_client); if (dhcp_client->listener_watch > 0) { g_source_remove(dhcp_client->listener_watch); @@ -1102,9 +1417,9 @@ static int ipv4ll_recv_arp_packet(GDHCPClient *dhcp_client) uint32_t ip_requested; int source_conflict; int target_conflict; + guint timeout_ms; memset(&arp, 0, sizeof(arp)); - bytes = 0; bytes = read(dhcp_client->listener_sockfd, &arp, sizeof(arp)); if (bytes < 0) return bytes; @@ -1113,7 +1428,10 @@ static int ipv4ll_recv_arp_packet(GDHCPClient *dhcp_client) arp.arp_op != htons(ARPOP_REQUEST)) return -EINVAL; - ip_requested = ntohl(dhcp_client->requested_ip); + if (memcmp(arp.arp_sha, dhcp_client->mac_address, ETH_ALEN) == 0) + return 0; + + ip_requested = htonl(dhcp_client->requested_ip); source_conflict = !memcmp(arp.arp_spa, &ip_requested, sizeof(ip_requested)); @@ -1141,63 +1459,60 @@ static int ipv4ll_recv_arp_packet(GDHCPClient *dhcp_client) if (dhcp_client->state == IPV4LL_DEFEND) { if (!source_conflict) return 0; - else if (dhcp_client->ipv4ll_lost_cb != NULL) + else if (dhcp_client->ipv4ll_lost_cb) dhcp_client->ipv4ll_lost_cb(dhcp_client, dhcp_client->ipv4ll_lost_data); } ipv4ll_stop(dhcp_client); - if (dhcp_client->conflicts < MAX_CONFLICTS) { - /*restart whole state machine*/ - dhcp_client->retry_times++; - dhcp_client->timeout = - g_timeout_add_full(G_PRIORITY_HIGH, - ipv4ll_random_delay_ms(PROBE_WAIT), - send_probe_packet, - dhcp_client, - NULL); - } - /* Here we got a lot of conflicts, RFC3927 states that we have + /* If we got a lot of conflicts, RFC3927 states that we have * to wait RATE_LIMIT_INTERVAL before retrying, - * but we just report failure. */ - else if (dhcp_client->no_lease_cb != NULL) - dhcp_client->no_lease_cb(dhcp_client, - dhcp_client->no_lease_data); + if (dhcp_client->conflicts < MAX_CONFLICTS) + timeout_ms = __connman_util_random_delay_ms(PROBE_WAIT); + else + timeout_ms = RATE_LIMIT_INTERVAL * 1000; + dhcp_client->retry_times++; + dhcp_client->timeout = + g_timeout_add_full(G_PRIORITY_HIGH, + timeout_ms, + send_probe_packet, + dhcp_client, + NULL); return 0; } -static gboolean check_package_owner(GDHCPClient *dhcp_client, gpointer pkt) +static bool check_package_owner(GDHCPClient *dhcp_client, gpointer pkt) { if (dhcp_client->type == G_DHCP_IPV6) { struct dhcpv6_packet *packet6 = pkt; uint32_t xid; - if (packet6 == NULL) - return FALSE; + if (!packet6) + return false; xid = packet6->transaction_id[0] << 16 | packet6->transaction_id[1] << 8 | packet6->transaction_id[2]; if (xid != dhcp_client->xid) - return FALSE; + return false; } else { struct dhcp_packet *packet = pkt; if (packet->xid != dhcp_client->xid) - return FALSE; + return false; if (packet->hlen != 6) - return FALSE; + return false; if (memcmp(packet->chaddr, dhcp_client->mac_address, 6)) - return FALSE; + return false; } - return TRUE; + return true; } static void start_request(GDHCPClient *dhcp_client); @@ -1216,6 +1531,12 @@ static gboolean request_timeout(gpointer user_data) return FALSE; } +static void listener_watch_destroy(gpointer user_data) +{ + GDHCPClient *dhcp_client = user_data; + g_dhcp_client_unref(dhcp_client); +} + static gboolean listener_event(GIOChannel *channel, GIOCondition condition, gpointer user_data); @@ -1234,7 +1555,6 @@ static int switch_listening_mode(GDHCPClient *dhcp_client, if (dhcp_client->listen_mode != L_NONE) { if (dhcp_client->listener_watch > 0) g_source_remove(dhcp_client->listener_watch); - dhcp_client->listener_channel = NULL; dhcp_client->listen_mode = L_NONE; dhcp_client->listener_sockfd = -1; dhcp_client->listener_watch = 0; @@ -1255,7 +1575,7 @@ static int switch_listening_mode(GDHCPClient *dhcp_client, dhcp_client->interface, AF_INET); } else if (listen_mode == L_ARP) - listener_sockfd = ipv4ll_arp_socket(dhcp_client->ifindex); + listener_sockfd = arp_socket(dhcp_client->ifindex); else return -EIO; @@ -1263,7 +1583,7 @@ static int switch_listening_mode(GDHCPClient *dhcp_client, return -EIO; listener_channel = g_io_channel_unix_new(listener_sockfd); - if (listener_channel == NULL) { + if (!listener_channel) { /* Failed to create listener channel */ close(listener_sockfd); return -EIO; @@ -1271,15 +1591,14 @@ static int switch_listening_mode(GDHCPClient *dhcp_client, dhcp_client->listen_mode = listen_mode; dhcp_client->listener_sockfd = listener_sockfd; - dhcp_client->listener_channel = listener_channel; g_io_channel_set_close_on_unref(listener_channel, TRUE); dhcp_client->listener_watch = g_io_add_watch_full(listener_channel, G_PRIORITY_HIGH, G_IO_IN | G_IO_NVAL | G_IO_ERR | G_IO_HUP, - listener_event, dhcp_client, - NULL); - g_io_channel_unref(dhcp_client->listener_channel); + listener_event, g_dhcp_client_ref(dhcp_client), + listener_watch_destroy); + g_io_channel_unref(listener_channel); return 0; } @@ -1290,9 +1609,9 @@ static void start_request(GDHCPClient *dhcp_client) dhcp_client->retry_times); if (dhcp_client->retry_times == REQUEST_RETRIES) { - dhcp_client->state = INIT_SELECTING; - ipv4ll_start(dhcp_client); - + if (dhcp_client->no_lease_cb) + dhcp_client->no_lease_cb(dhcp_client, + dhcp_client->no_lease_data); return; } @@ -1301,7 +1620,7 @@ static void start_request(GDHCPClient *dhcp_client) switch_listening_mode(dhcp_client, L2); } - send_select(dhcp_client); + send_request(dhcp_client); dhcp_client->timeout = g_timeout_add_seconds_full(G_PRIORITY_HIGH, REQUEST_TIMEOUT, @@ -1312,17 +1631,15 @@ static void start_request(GDHCPClient *dhcp_client) static uint32_t get_lease(struct dhcp_packet *packet) { - uint8_t *option_u8; + uint8_t *option; uint32_t lease_seconds; - option_u8 = dhcp_get_option(packet, DHCP_LEASE_TIME); - if (option_u8 == NULL) + option = dhcp_get_option(packet, DHCP_LEASE_TIME); + if (!option) return 3600; - lease_seconds = dhcp_get_unaligned((uint32_t *) option_u8); - lease_seconds = ntohl(lease_seconds); - /* paranoia: must not be prone to overflows */ - lease_seconds &= 0x0fffffff; + lease_seconds = get_be32(option); + if (lease_seconds < 10) lease_seconds = 10; @@ -1333,93 +1650,124 @@ static void restart_dhcp(GDHCPClient *dhcp_client, int retry_times) { debug(dhcp_client, "restart DHCP (retries %d)", retry_times); - if (dhcp_client->timeout > 0) { - g_source_remove(dhcp_client->timeout); - dhcp_client->timeout = 0; - } + remove_timeouts(dhcp_client); dhcp_client->retry_times = retry_times; dhcp_client->requested_ip = 0; + dhcp_client->state = INIT_SELECTING; switch_listening_mode(dhcp_client, L2); g_dhcp_client_start(dhcp_client, dhcp_client->last_address); } -static gboolean start_rebound_timeout(gpointer user_data) +static gboolean start_expire(gpointer user_data) { GDHCPClient *dhcp_client = user_data; - debug(dhcp_client, "start rebound timeout"); + debug(dhcp_client, "lease expired"); - switch_listening_mode(dhcp_client, L2); + /*remove all timeouts if they are set*/ + remove_timeouts(dhcp_client); - dhcp_client->lease_seconds >>= 1; + restart_dhcp(dhcp_client, 0); - /* We need to have enough time to receive ACK package*/ - if (dhcp_client->lease_seconds <= 6) { + /* ip need to be cleared */ + if (dhcp_client->lease_lost_cb) + dhcp_client->lease_lost_cb(dhcp_client, + dhcp_client->lease_lost_data); - /* ip need to be cleared */ - if (dhcp_client->lease_lost_cb != NULL) - dhcp_client->lease_lost_cb(dhcp_client, - dhcp_client->lease_lost_data); + return false; +} - restart_dhcp(dhcp_client, 0); - } else { - send_rebound(dhcp_client); +static gboolean continue_rebound(gpointer user_data) +{ + GDHCPClient *dhcp_client = user_data; + uint64_t rand; - dhcp_client->timeout = - g_timeout_add_seconds_full(G_PRIORITY_HIGH, - dhcp_client->lease_seconds >> 1, - start_rebound_timeout, - dhcp_client, - NULL); + switch_listening_mode(dhcp_client, L2); + send_request(dhcp_client); + + if (dhcp_client->t2_timeout> 0) { + g_source_remove(dhcp_client->t2_timeout); + dhcp_client->t2_timeout = 0; + } + + /*recalculate remaining rebind time*/ + dhcp_client->T2 >>= 1; + if (dhcp_client->T2 > 60) { + __connman_util_get_random(&rand); + dhcp_client->t2_timeout = + g_timeout_add_full(G_PRIORITY_HIGH, + dhcp_client->T2 * 1000 + (rand % 2000) - 1000, + continue_rebound, + dhcp_client, + NULL); } return FALSE; } -static void start_rebound(GDHCPClient *dhcp_client) +static gboolean start_rebound(gpointer user_data) { - debug(dhcp_client, "start rebound"); + GDHCPClient *dhcp_client = user_data; + /*remove renew timer*/ + if (dhcp_client->t1_timeout > 0) + g_source_remove(dhcp_client->t1_timeout); + + debug(dhcp_client, "start rebound"); dhcp_client->state = REBINDING; - dhcp_client->timeout = g_timeout_add_seconds_full(G_PRIORITY_HIGH, - dhcp_client->lease_seconds >> 1, - start_rebound_timeout, - dhcp_client, - NULL); + /*calculate total rebind time*/ + dhcp_client->T2 = dhcp_client->expire - dhcp_client->T2; + + /*send the first rebound and reschedule*/ + continue_rebound(user_data); + + return FALSE; } -static gboolean start_renew_timeout(gpointer user_data) +static gboolean continue_renew (gpointer user_data) { GDHCPClient *dhcp_client = user_data; + uint64_t rand; - debug(dhcp_client, "start renew timeout"); + switch_listening_mode(dhcp_client, L3); + send_request(dhcp_client); - dhcp_client->state = RENEWING; + if (dhcp_client->t1_timeout > 0) + g_source_remove(dhcp_client->t1_timeout); - dhcp_client->lease_seconds >>= 1; + dhcp_client->t1_timeout = 0; - switch_listening_mode(dhcp_client, L3); - if (dhcp_client->lease_seconds <= 60) - start_rebound(dhcp_client); - else { - send_renew(dhcp_client); - - if (dhcp_client->timeout > 0) - g_source_remove(dhcp_client->timeout); + dhcp_client->T1 >>= 1; - dhcp_client->timeout = - g_timeout_add_seconds_full(G_PRIORITY_HIGH, - dhcp_client->lease_seconds >> 1, - start_renew_timeout, - dhcp_client, - NULL); + if (dhcp_client->T1 > 60) { + __connman_util_get_random(&rand); + dhcp_client->t1_timeout = g_timeout_add_full(G_PRIORITY_HIGH, + dhcp_client->T1 * 1000 + (rand % 2000) - 1000, + continue_renew, + dhcp_client, + NULL); } return FALSE; } +static gboolean start_renew(gpointer user_data) +{ + GDHCPClient *dhcp_client = user_data; + + debug(dhcp_client, "start renew"); + dhcp_client->state = RENEWING; + + /*calculate total renew period*/ + dhcp_client->T1 = dhcp_client->T2 - dhcp_client->T1; + + /*send first renew and reschedule for half the remaining time.*/ + continue_renew(user_data); + + return FALSE; +} static void start_bound(GDHCPClient *dhcp_client) { @@ -1427,12 +1775,30 @@ static void start_bound(GDHCPClient *dhcp_client) dhcp_client->state = BOUND; - if (dhcp_client->timeout > 0) - g_source_remove(dhcp_client->timeout); + remove_timeouts(dhcp_client); - dhcp_client->timeout = g_timeout_add_seconds_full(G_PRIORITY_HIGH, - dhcp_client->lease_seconds >> 1, - start_renew_timeout, dhcp_client, + /* + *TODO: T1 and T2 should be set through options instead of + * defaults as they are here. + */ + + dhcp_client->T1 = dhcp_client->lease_seconds >> 1; + dhcp_client->T2 = dhcp_client->lease_seconds * 0.875; + dhcp_client->expire = dhcp_client->lease_seconds; + + dhcp_client->t1_timeout = g_timeout_add_seconds_full(G_PRIORITY_HIGH, + dhcp_client->T1, + start_renew, dhcp_client, + NULL); + + dhcp_client->t2_timeout = g_timeout_add_seconds_full(G_PRIORITY_HIGH, + dhcp_client->T2, + start_rebound, dhcp_client, + NULL); + + dhcp_client->lease_timeout= g_timeout_add_seconds_full(G_PRIORITY_HIGH, + dhcp_client->expire, + start_expire, dhcp_client, NULL); } @@ -1442,10 +1808,14 @@ static gboolean restart_dhcp_timeout(gpointer user_data) debug(dhcp_client, "restart DHCP timeout"); - dhcp_client->ack_retry_times++; - - restart_dhcp(dhcp_client, dhcp_client->ack_retry_times); - + if (dhcp_client->state == REBOOTING) { + g_free(dhcp_client->last_address); + dhcp_client->last_address = NULL; + restart_dhcp(dhcp_client, 0); + } else { + dhcp_client->ack_retry_times++; + restart_dhcp(dhcp_client, dhcp_client->ack_retry_times); + } return FALSE; } @@ -1486,8 +1856,8 @@ static char *malloc_option_value_string(uint8_t *option, GDHCPOptionType type) return NULL; upper_length = len_of_option_as_string[type] * ((unsigned)len / (unsigned)optlen); - dest = ret = malloc(upper_length + 1); - if (ret == NULL) + dest = ret = g_malloc(upper_length + 1); + if (!ret) return NULL; while (len >= optlen) { @@ -1496,16 +1866,13 @@ static char *malloc_option_value_string(uint8_t *option, GDHCPOptionType type) dest += sprint_nip(dest, "", option); break; case OPTION_U16: { - uint16_t val_u16 = dhcp_get_unaligned( - (uint16_t *) option); - dest += sprintf(dest, "%u", ntohs(val_u16)); + uint16_t val_u16 = get_be16(option); + dest += sprintf(dest, "%u", val_u16); break; } case OPTION_U32: { - uint32_t val_u32 = dhcp_get_unaligned( - (uint32_t *) option); - dest += sprintf(dest, type == OPTION_U32 ? "%lu" : - "%ld", (unsigned long) ntohl(val_u32)); + uint32_t val_u32 = get_be32(option); + dest += sprintf(dest, "%u", val_u32); break; } case OPTION_STRING: @@ -1531,13 +1898,13 @@ static GList *get_option_value_list(char *value, GDHCPOptionType type) char *pos = value; GList *list = NULL; - if (pos == NULL) + if (!pos) return NULL; if (type == OPTION_STRING) return g_list_append(list, g_strdup(value)); - while ((pos = strchr(pos, ' ')) != NULL) { + while ((pos = strchr(pos, ' '))) { *pos = '\0'; list = g_list_append(list, g_strdup(value)); @@ -1561,6 +1928,33 @@ static inline uint16_t get_uint16(unsigned char *value) return value[0] << 8 | value[1]; } +static GList *add_prefix(GDHCPClient *dhcp_client, GList *list, + struct in6_addr *addr, + unsigned char prefixlen, uint32_t preferred, + uint32_t valid) +{ + GDHCPIAPrefix *ia_prefix; + + ia_prefix = g_try_new(GDHCPIAPrefix, 1); + if (!ia_prefix) + return list; + + if (dhcp_client->debug_func) { + char addr_str[INET6_ADDRSTRLEN + 1]; + inet_ntop(AF_INET6, addr, addr_str, INET6_ADDRSTRLEN); + debug(dhcp_client, "prefix %s/%d preferred %u valid %u", + addr_str, prefixlen, preferred, valid); + } + + memcpy(&ia_prefix->prefix, addr, sizeof(struct in6_addr)); + ia_prefix->prefixlen = prefixlen; + ia_prefix->preferred = preferred; + ia_prefix->valid = valid; + ia_prefix->expire = time(NULL) + valid; + + return g_list_prepend(list, ia_prefix); +} + static GList *get_addresses(GDHCPClient *dhcp_client, int code, int len, unsigned char *value, @@ -1570,20 +1964,26 @@ static GList *get_addresses(GDHCPClient *dhcp_client, 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; + int addr_count = 0, prefix_count = 0, i, pos; + unsigned char prefixlen; + unsigned int shortest_valid = 0; uint8_t *option; char *str; + if (!value || len < 4) + return NULL; + iaid = get_uint32(&value[0]); if (dhcp_client->iaid != iaid) return NULL; - if (code == G_DHCPV6_IA_NA) { + if (code == G_DHCPV6_IA_NA || code == G_DHCPV6_IA_PD) { T1 = get_uint32(&value[4]); T2 = get_uint32(&value[8]); if (T1 > T2) - /* RFC 3315, 22.4 */ + /* IA_NA: RFC 3315, 22.4 */ + /* IA_PD: RFC 3633, ch 9 */ return NULL; pos = 12; @@ -1595,6 +1995,8 @@ static GList *get_addresses(GDHCPClient *dhcp_client, max_len = len - pos; + debug(dhcp_client, "header %d sub-option max len %d", pos, max_len); + /* We have more sub-options in this packet. */ do { option = dhcpv6_get_sub_option(&value[pos], max_len, @@ -1603,10 +2005,10 @@ static GList *get_addresses(GDHCPClient *dhcp_client, debug(dhcp_client, "pos %d option %p code %d len %d", pos, option, option_code, option_len); - if (option == NULL) + if (!option) break; - if (pos >= max_len) + if (pos >= len) break; switch (option_code) { @@ -1633,15 +2035,35 @@ static GList *get_addresses(GDHCPClient *dhcp_client, *status = st; break; + + case G_DHCPV6_IA_PREFIX: + i = 0; + preferred = get_uint32(&option[i]); + i += 4; + valid = get_uint32(&option[i]); + i += 4; + prefixlen = option[i]; + i += 1; + memcpy(&addr, &option[i], sizeof(addr)); + i += sizeof(addr); + if (preferred < valid) { + /* RFC 3633, ch 10 */ + list = add_prefix(dhcp_client, list, &addr, + prefixlen, preferred, valid); + if (shortest_valid > valid) + shortest_valid = valid; + prefix_count++; + } + break; } pos += 2 + 2 + option_len; - } while (option != NULL); + } while (pos < len); if (addr_count > 0 && st == 0) { /* We only support one address atm */ - char str[INET6_ADDRSTRLEN + 1]; + char addr_str[INET6_ADDRSTRLEN + 1]; if (preferred > valid) /* RFC 3315, 22.6 */ @@ -1650,11 +2072,11 @@ static GList *get_addresses(GDHCPClient *dhcp_client, 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); + inet_ntop(AF_INET6, &addr, addr_str, INET6_ADDRSTRLEN); + debug(dhcp_client, "address count %d addr %s T1 %u T2 %u", + addr_count, addr_str, T1, T2); - list = g_list_append(list, g_strdup(str)); + list = g_list_append(list, g_strdup(addr_str)); if (code == G_DHCPV6_IA_NA) memcpy(&dhcp_client->ia_na, &addr, @@ -1663,12 +2085,59 @@ static GList *get_addresses(GDHCPClient *dhcp_client, memcpy(&dhcp_client->ia_ta, &addr, sizeof(struct in6_addr)); - g_dhcpv6_client_set_expire(dhcp_client, valid); + if (valid != dhcp_client->expire) + dhcp_client->expire = valid; } + if (prefix_count > 0 && list) { + /* + * This means we have a list of prefixes to delegate. + */ + list = g_list_reverse(list); + + debug(dhcp_client, "prefix count %d T1 %u T2 %u", + prefix_count, T1, T2); + + dhcp_client->T1 = T1; + dhcp_client->T2 = T2; + + dhcp_client->expire = shortest_valid; + } + + if (status && *status != 0) + debug(dhcp_client, "status %d", *status); + return list; } +static GList *get_domains(int maxlen, unsigned char *value) + +{ + GList *list = NULL; + int pos = 0; + unsigned char *c; + char dns_name[NS_MAXDNAME + 1]; + + if (!value || maxlen < 3) + return NULL; + + while (pos < maxlen) { + strncpy(dns_name, (char *)&value[pos], NS_MAXDNAME); + + c = (unsigned char *)dns_name; + while (c && *c) { + int jump; + jump = *c; + *c = '.'; + c += jump + 1; + } + list = g_list_prepend(list, g_strdup(&dns_name[1])); + pos += (char *)c - dns_name + 1; + } + + return g_list_reverse(list); +} + static GList *get_dhcpv6_option_value_list(GDHCPClient *dhcp_client, int code, int len, unsigned char *value, @@ -1678,6 +2147,9 @@ static GList *get_dhcpv6_option_value_list(GDHCPClient *dhcp_client, char *str; int i; + if (!value) + return NULL; + switch (code) { case G_DHCPV6_DNS_SERVERS: /* RFC 3646, chapter 3 */ case G_DHCPV6_SNTP_SERVERS: /* RFC 4075, chapter 4 */ @@ -1691,11 +2163,11 @@ static GList *get_dhcpv6_option_value_list(GDHCPClient *dhcp_client, for (i = 0; i < len; i += 16) { str = g_try_malloc0(INET6_ADDRSTRLEN+1); - if (str == NULL) + if (!str) return list; - if (inet_ntop(AF_INET6, &value[i], str, - INET6_ADDRSTRLEN) == NULL) + if (!inet_ntop(AF_INET6, &value[i], str, + INET6_ADDRSTRLEN)) g_free(str); else list = g_list_append(list, str); @@ -1704,9 +2176,14 @@ static GList *get_dhcpv6_option_value_list(GDHCPClient *dhcp_client, case G_DHCPV6_IA_NA: /* RFC 3315, chapter 22.4 */ case G_DHCPV6_IA_TA: /* RFC 3315, chapter 22.5 */ + case G_DHCPV6_IA_PD: /* RFC 3633, chapter 9 */ list = get_addresses(dhcp_client, code, len, value, status); break; + case G_DHCPV6_DOMAIN_LIST: + list = get_domains(len, value); + break; + default: break; } @@ -1728,7 +2205,7 @@ static void get_dhcpv6_request(GDHCPClient *dhcp_client, option = dhcpv6_get_option(packet, pkt_len, code, &option_len, NULL); - if (option == NULL) { + if (!option) { g_hash_table_remove(dhcp_client->code_value_hash, GINT_TO_POINTER((int) code)); continue; @@ -1740,7 +2217,7 @@ static void get_dhcpv6_request(GDHCPClient *dhcp_client, debug(dhcp_client, "code %d %p len %d list %p", code, option, option_len, value_list); - if (value_list == NULL) + if (!value_list) g_hash_table_remove(dhcp_client->code_value_hash, GINT_TO_POINTER((int) code)); else @@ -1761,7 +2238,7 @@ static void get_request(GDHCPClient *dhcp_client, struct dhcp_packet *packet) code = (uint8_t) GPOINTER_TO_INT(list->data); option = dhcp_get_option(packet, code); - if (option == NULL) { + if (!option) { g_hash_table_remove(dhcp_client->code_value_hash, GINT_TO_POINTER((int) code)); continue; @@ -1770,7 +2247,7 @@ static void get_request(GDHCPClient *dhcp_client, struct dhcp_packet *packet) type = dhcp_get_code_type(code); option_value = malloc_option_value_string(option, type); - if (option_value == NULL) + if (!option_value) g_hash_table_remove(dhcp_client->code_value_hash, GINT_TO_POINTER((int) code)); @@ -1778,7 +2255,7 @@ static void get_request(GDHCPClient *dhcp_client, struct dhcp_packet *packet) g_free(option_value); - if (value_list == NULL) + if (!value_list) g_hash_table_remove(dhcp_client->code_value_hash, GINT_TO_POINTER((int) code)); else @@ -1791,11 +2268,13 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition, gpointer user_data) { GDHCPClient *dhcp_client = user_data; + struct sockaddr_in dst_addr = { 0 }; struct dhcp_packet packet; struct dhcpv6_packet *packet6 = NULL; - uint8_t *message_type = NULL, *client_id = NULL, *option_u8, + uint8_t *message_type = NULL, *client_id = NULL, *option, *server_id = NULL; uint16_t option_len = 0, status = 0; + uint32_t xid = 0; gpointer pkt; unsigned char buf[MAX_DHCPV6_PKT_SIZE]; uint16_t pkt_len = 0; @@ -1812,37 +2291,50 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition, pkt = &packet; - if (dhcp_client->listen_mode == L2) + dhcp_client->status_code = 0; + + if (dhcp_client->listen_mode == L2) { re = dhcp_recv_l2_packet(&packet, - dhcp_client->listener_sockfd); - else if (dhcp_client->listen_mode == L3) { + dhcp_client->listener_sockfd, + &dst_addr); + xid = packet.xid; + } else if (dhcp_client->listen_mode == L3) { if (dhcp_client->type == G_DHCP_IPV6) { re = dhcpv6_recv_l3_packet(&packet6, buf, sizeof(buf), dhcp_client->listener_sockfd); + if (re < 0) + return TRUE; pkt_len = re; pkt = packet6; - } else + xid = packet6->transaction_id[0] << 16 | + packet6->transaction_id[1] << 8 | + packet6->transaction_id[2]; + } else { re = dhcp_recv_l3_packet(&packet, dhcp_client->listener_sockfd); + xid = packet.xid; + } } else if (dhcp_client->listen_mode == L_ARP) { - re = ipv4ll_recv_arp_packet(dhcp_client); + ipv4ll_recv_arp_packet(dhcp_client); return TRUE; - } - else + } else re = -EIO; if (re < 0) return TRUE; - if (check_package_owner(dhcp_client, pkt) == FALSE) + if (!check_package_owner(dhcp_client, pkt)) return TRUE; if (dhcp_client->type == G_DHCP_IPV6) { + if (!packet6) + return TRUE; + count = 0; client_id = dhcpv6_get_option(packet6, pkt_len, G_DHCPV6_CLIENTID, &option_len, &count); - if (client_id == NULL || count == 0 || option_len == 0 || + if (!client_id || count == 0 || option_len == 0 || memcmp(dhcp_client->duid, client_id, dhcp_client->duid_len) != 0) { debug(dhcp_client, @@ -1851,15 +2343,15 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition, return TRUE; } - option_u8 = dhcpv6_get_option(packet6, pkt_len, + option = dhcpv6_get_option(packet6, pkt_len, G_DHCPV6_STATUS_CODE, &option_len, NULL); - if (option_u8 != 0 && option_len > 0) { - status = option_u8[0]<<8 | option_u8[1]; + if (option != 0 && option_len > 0) { + status = option[0]<<8 | option[1]; if (status != 0) { debug(dhcp_client, "error code %d", status); if (option_len > 2) { gchar *txt = g_strndup( - (gchar *)&option_u8[2], + (gchar *)&option[2], option_len - 2); debug(dhcp_client, "error text: %s", txt); @@ -1867,47 +2359,74 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition, } } dhcp_client->status_code = status; - } else - dhcp_client->status_code = 0; - - } else + } + } else { message_type = dhcp_get_option(&packet, DHCP_MESSAGE_TYPE); + if (!message_type) + return TRUE; + } - if (message_type == NULL && client_id == NULL) - /* No message type / client id option, ignore package */ - return TRUE; - - debug(dhcp_client, "received DHCP packet (current state %d)", - dhcp_client->state); + debug(dhcp_client, "received DHCP packet xid 0x%04x " + "(current state %d)", ntohl(xid), dhcp_client->state); switch (dhcp_client->state) { case INIT_SELECTING: if (*message_type != DHCPOFFER) return TRUE; - g_source_remove(dhcp_client->timeout); + remove_timeouts(dhcp_client); dhcp_client->timeout = 0; dhcp_client->retry_times = 0; - option_u8 = dhcp_get_option(&packet, DHCP_SERVER_ID); - dhcp_client->server_ip = - dhcp_get_unaligned((uint32_t *) option_u8); - dhcp_client->requested_ip = packet.yiaddr; + option = dhcp_get_option(&packet, DHCP_SERVER_ID); + dhcp_client->server_ip = get_be32(option); + dhcp_client->requested_ip = ntohl(packet.yiaddr); dhcp_client->state = REQUESTING; + /* + * RFC2131: + * + * If unicasting is not possible, the message MAY be + * sent as an IP broadcast using an IP broadcast address + * (preferably 0xffffffff) as the IP destination address + * and the link-layer broadcast address as the link-layer + * destination address. + * + * For interoperability reasons, if the response is an IP + * broadcast, let's reuse broadcast flag from DHCPDISCOVER + * to which the server has responded. Some servers are picky + * about this flag. + */ + dhcp_client->request_bcast = + dst_addr.sin_addr.s_addr == INADDR_BROADCAST && + g_hash_table_contains(dhcp_client->secs_bcast_hash, + GINT_TO_POINTER(packet.secs)); + + debug(dhcp_client, "init ip %s secs %hu -> broadcast flag %s", + inet_ntoa(dst_addr.sin_addr), packet.secs, + dhcp_client->request_bcast ? "on" : "off"); + start_request(dhcp_client); return TRUE; + case REBOOTING: + if (dst_addr.sin_addr.s_addr == INADDR_BROADCAST) + dhcp_client->request_bcast = true; + else + dhcp_client->request_bcast = false; + + debug(dhcp_client, "ip %s -> %sadding broadcast flag", + inet_ntoa(dst_addr.sin_addr), + dhcp_client->request_bcast ? "" : "not "); + /* fall through */ case REQUESTING: case RENEWING: case REBINDING: if (*message_type == DHCPACK) { dhcp_client->retry_times = 0; - if (dhcp_client->timeout > 0) - g_source_remove(dhcp_client->timeout); - dhcp_client->timeout = 0; + remove_timeouts(dhcp_client); dhcp_client->lease_seconds = get_lease(&packet); @@ -1918,8 +2437,14 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition, g_free(dhcp_client->assigned_ip); dhcp_client->assigned_ip = get_ip(packet.yiaddr); + if (dhcp_client->state == REBOOTING) { + option = dhcp_get_option(&packet, + DHCP_SERVER_ID); + dhcp_client->server_ip = get_be32(option); + } + /* Address should be set up here */ - if (dhcp_client->lease_available_cb != NULL) + if (dhcp_client->lease_available_cb) dhcp_client->lease_available_cb(dhcp_client, dhcp_client->lease_available_data); @@ -1927,8 +2452,7 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition, } else if (*message_type == DHCPNAK) { dhcp_client->retry_times = 0; - if (dhcp_client->timeout > 0) - g_source_remove(dhcp_client->timeout); + remove_timeouts(dhcp_client); dhcp_client->timeout = g_timeout_add_seconds_full( G_PRIORITY_HIGH, 3, @@ -1949,7 +2473,7 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition, 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) { + if (!server_id || count != 1 || option_len == 0) { /* RFC 3315, 15.10 */ debug(dhcp_client, "server duid error, discarding msg %p/%d/%d", @@ -1957,7 +2481,7 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition, return TRUE; } dhcp_client->server_duid = g_try_malloc(option_len); - if (dhcp_client->server_duid == NULL) + if (!dhcp_client->server_duid) return TRUE; memcpy(dhcp_client->server_duid, server_id, option_len); dhcp_client->server_duid_len = option_len; @@ -1969,7 +2493,7 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition, rapid_commit = dhcpv6_get_option(packet6, pkt_len, G_DHCPV6_RAPID_COMMIT, &option_len, &count); - if (rapid_commit == NULL || option_len == 0 || + if (!rapid_commit || option_len != 0 || count != 1) /* RFC 3315, 17.1.4 */ return TRUE; @@ -1982,13 +2506,13 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition, &dhcp_client->status_code); if (packet6->message == DHCPV6_ADVERTISE) { - if (dhcp_client->advertise_cb != NULL) + if (dhcp_client->advertise_cb) dhcp_client->advertise_cb(dhcp_client, dhcp_client->advertise_data); return TRUE; } - if (dhcp_client->solicitation_cb != NULL) { + if (dhcp_client->solicitation_cb) { /* * The dhcp_client might not be valid after the * callback call so just return immediately. @@ -1998,10 +2522,31 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition, return TRUE; } break; + case REBIND: + if (dhcp_client->type != G_DHCP_IPV6) + return TRUE; + + server_id = dhcpv6_get_option(packet6, pkt_len, + G_DHCPV6_SERVERID, &option_len, &count); + if (!dhcp_client->server_duid && server_id && + count == 1) { + /* + * If we do not have server duid yet, then get it now. + * Prefix delegation renew support needs it. + */ + dhcp_client->server_duid = g_try_malloc(option_len); + if (!dhcp_client->server_duid) + return TRUE; + memcpy(dhcp_client->server_duid, server_id, option_len); + dhcp_client->server_duid_len = option_len; + } + /* fall through */ case INFORMATION_REQ: case REQUEST: case RENEW: - case REBIND: + case RELEASE: + case CONFIRM: + case DECLINE: if (dhcp_client->type != G_DHCP_IPV6) return TRUE; @@ -2012,7 +2557,7 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition, option_len = 0; server_id = dhcpv6_get_option(packet6, pkt_len, G_DHCPV6_SERVERID, &option_len, &count); - if (server_id == NULL || count != 1 || option_len == 0 || + if (!server_id || count != 1 || option_len == 0 || (dhcp_client->server_duid_len > 0 && memcmp(dhcp_client->server_duid, server_id, dhcp_client->server_duid_len) != 0)) { @@ -2025,11 +2570,10 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition, switch_listening_mode(dhcp_client, L_NONE); - dhcp_client->status_code = 0; get_dhcpv6_request(dhcp_client, packet6, pkt_len, &dhcp_client->status_code); - if (dhcp_client->information_req_cb != NULL) { + if (dhcp_client->information_req_cb) { /* * The dhcp_client might not be valid after the * callback call so just return immediately. @@ -2038,21 +2582,55 @@ static gboolean listener_event(GIOChannel *channel, GIOCondition condition, dhcp_client->information_req_data); return TRUE; } - if (dhcp_client->request_cb != NULL) { + if (dhcp_client->request_cb) { dhcp_client->request_cb(dhcp_client, dhcp_client->request_data); return TRUE; } - if (dhcp_client->renew_cb != NULL) { + if (dhcp_client->renew_cb) { dhcp_client->renew_cb(dhcp_client, dhcp_client->renew_data); return TRUE; } - if (dhcp_client->rebind_cb != NULL) { + if (dhcp_client->rebind_cb) { dhcp_client->rebind_cb(dhcp_client, dhcp_client->rebind_data); return TRUE; } + if (dhcp_client->release_cb) { + dhcp_client->release_cb(dhcp_client, + dhcp_client->release_data); + return TRUE; + } + if (dhcp_client->decline_cb) { + dhcp_client->decline_cb(dhcp_client, + dhcp_client->decline_data); + return TRUE; + } + if (dhcp_client->confirm_cb) { + count = 0; + server_id = dhcpv6_get_option(packet6, pkt_len, + G_DHCPV6_SERVERID, &option_len, + &count); + if (!server_id || 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) + 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; @@ -2080,6 +2658,22 @@ static gboolean discover_timeout(gpointer user_data) return FALSE; } +static gboolean reboot_timeout(gpointer user_data) +{ + GDHCPClient *dhcp_client = user_data; + dhcp_client->retry_times = 0; + dhcp_client->requested_ip = 0; + dhcp_client->state = INIT_SELECTING; + /* + * We do not send the REQUESTED IP option because the server didn't + * respond when we send DHCPREQUEST with the REQUESTED IP option in + * init-reboot state + */ + g_dhcp_client_start(dhcp_client, NULL); + + return FALSE; +} + static gboolean ipv4ll_defend_timeout(gpointer dhcp_data) { GDHCPClient *dhcp_client = dhcp_data; @@ -2100,7 +2694,7 @@ static gboolean ipv4ll_announce_timeout(gpointer dhcp_data) debug(dhcp_client, "request timeout (retries %d)", dhcp_client->retry_times); - if (dhcp_client->retry_times != ANNOUNCE_NUM){ + if (dhcp_client->retry_times != ANNOUNCE_NUM) { dhcp_client->retry_times++; send_announce_packet(dhcp_client); return FALSE; @@ -2111,10 +2705,11 @@ static gboolean ipv4ll_announce_timeout(gpointer dhcp_data) dhcp_client->state = IPV4LL_MONITOR; dhcp_client->assigned_ip = get_ip(ip); - if (dhcp_client->ipv4ll_available_cb != NULL) + if (dhcp_client->ipv4ll_available_cb) dhcp_client->ipv4ll_available_cb(dhcp_client, dhcp_client->ipv4ll_available_data); dhcp_client->conflicts = 0; + dhcp_client->timeout = 0; return FALSE; } @@ -2145,6 +2740,10 @@ int g_dhcp_client_start(GDHCPClient *dhcp_client, const char *last_address) { int re; uint32_t addr; + uint64_t rand; + ClientState oldstate = dhcp_client->state; + + remove_timeouts(dhcp_client); if (dhcp_client->type == G_DHCP_IPV6) { if (dhcp_client->information_req_cb) { @@ -2177,6 +2776,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); @@ -2196,16 +2805,44 @@ int g_dhcp_client_start(GDHCPClient *dhcp_client, const char *last_address) return re; } send_dhcpv6_rebind(dhcp_client); + + } else if (dhcp_client->release_cb) { + dhcp_client->state = RENEW; + 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_release(dhcp_client); + } else if (dhcp_client->decline_cb) { + dhcp_client->state = DECLINE; + 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_decline(dhcp_client); } return 0; } - if (dhcp_client->retry_times == DISCOVER_RETRIES) { + if (dhcp_client->type == G_DHCP_IPV4LL) { + dhcp_client->state = INIT_SELECTING; ipv4ll_start(dhcp_client); return 0; } + if (dhcp_client->retry_times == DISCOVER_RETRIES) { + if (dhcp_client->no_lease_cb) + dhcp_client->no_lease_cb(dhcp_client, + dhcp_client->no_lease_data); + dhcp_client->retry_times = 0; + return 0; + } + if (dhcp_client->retry_times == 0) { g_free(dhcp_client->assigned_ip); dhcp_client->assigned_ip = NULL; @@ -2215,20 +2852,39 @@ int g_dhcp_client_start(GDHCPClient *dhcp_client, const char *last_address) if (re != 0) return re; - dhcp_client->xid = rand(); + __connman_util_get_random(&rand); + dhcp_client->xid = rand; + dhcp_client->start = time(NULL); + g_hash_table_remove_all(dhcp_client->secs_bcast_hash); } - if (last_address == NULL) { + if (!last_address || oldstate == DECLINED) { addr = 0; } else { - addr = inet_addr(last_address); - if (addr == 0xFFFFFFFF) { + addr = ntohl(inet_addr(last_address)); + if (addr == 0xFFFFFFFF || ((addr & LINKLOCAL_ADDR) == + LINKLOCAL_ADDR)) { addr = 0; - } else { + } else if (dhcp_client->last_address != last_address) { g_free(dhcp_client->last_address); dhcp_client->last_address = g_strdup(last_address); } } + + if ((addr != 0) && (dhcp_client->type != G_DHCP_IPV4LL)) { + debug(dhcp_client, "DHCP client start with state init_reboot"); + dhcp_client->requested_ip = addr; + dhcp_client->state = REBOOTING; + send_request(dhcp_client); + + dhcp_client->timeout = g_timeout_add_seconds_full( + G_PRIORITY_HIGH, + REQUEST_TIMEOUT, + reboot_timeout, + dhcp_client, + NULL); + return 0; + } send_discover(dhcp_client, addr); dhcp_client->timeout = g_timeout_add_seconds_full(G_PRIORITY_HIGH, @@ -2249,24 +2905,20 @@ void g_dhcp_client_stop(GDHCPClient *dhcp_client) send_release(dhcp_client, dhcp_client->server_ip, dhcp_client->requested_ip); - if (dhcp_client->timeout > 0) { - g_source_remove(dhcp_client->timeout); - dhcp_client->timeout = 0; - } + remove_timeouts(dhcp_client); if (dhcp_client->listener_watch > 0) { g_source_remove(dhcp_client->listener_watch); dhcp_client->listener_watch = 0; } - dhcp_client->listener_channel = NULL; - dhcp_client->retry_times = 0; dhcp_client->ack_retry_times = 0; dhcp_client->requested_ip = 0; dhcp_client->state = RELEASED; dhcp_client->lease_seconds = 0; + dhcp_client->request_bcast = false; } GList *g_dhcp_client_get_option(GDHCPClient *dhcp_client, @@ -2311,41 +2963,59 @@ void g_dhcp_client_register_event(GDHCPClient *dhcp_client, dhcp_client->address_conflict_data = data; return; case G_DHCP_CLIENT_EVENT_INFORMATION_REQ: - if (dhcp_client->type == G_DHCP_IPV4) + if (dhcp_client->type != G_DHCP_IPV6) return; 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) + if (dhcp_client->type != G_DHCP_IPV6) 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) + if (dhcp_client->type != G_DHCP_IPV6) return; dhcp_client->advertise_cb = func; dhcp_client->advertise_data = data; return; case G_DHCP_CLIENT_EVENT_REQUEST: - if (dhcp_client->type == G_DHCP_IPV4) + if (dhcp_client->type != G_DHCP_IPV6) return; dhcp_client->request_cb = func; dhcp_client->request_data = data; return; case G_DHCP_CLIENT_EVENT_RENEW: - if (dhcp_client->type == G_DHCP_IPV4) + if (dhcp_client->type != G_DHCP_IPV6) return; dhcp_client->renew_cb = func; dhcp_client->renew_data = data; return; case G_DHCP_CLIENT_EVENT_REBIND: - if (dhcp_client->type == G_DHCP_IPV4) + if (dhcp_client->type != G_DHCP_IPV6) return; dhcp_client->rebind_cb = func; dhcp_client->rebind_data = data; return; + case G_DHCP_CLIENT_EVENT_RELEASE: + if (dhcp_client->type != G_DHCP_IPV6) + return; + dhcp_client->release_cb = func; + dhcp_client->release_data = data; + return; + case G_DHCP_CLIENT_EVENT_CONFIRM: + if (dhcp_client->type != G_DHCP_IPV6) + return; + dhcp_client->confirm_cb = func; + dhcp_client->confirm_data = data; + return; + case G_DHCP_CLIENT_EVENT_DECLINE: + if (dhcp_client->type != G_DHCP_IPV6) + return; + dhcp_client->decline_cb = func; + dhcp_client->decline_data = data; + return; } } @@ -2354,6 +3024,14 @@ int g_dhcp_client_get_index(GDHCPClient *dhcp_client) return dhcp_client->ifindex; } +char *g_dhcp_client_get_server_address(GDHCPClient *dhcp_client) +{ + if (!dhcp_client) + return NULL; + + return get_ip(dhcp_client->server_ip); +} + char *g_dhcp_client_get_address(GDHCPClient *dhcp_client) { return g_strdup(dhcp_client->assigned_ip); @@ -2374,18 +3052,23 @@ char *g_dhcp_client_get_netmask(GDHCPClient *dhcp_client) case RENEWING: case REBINDING: option = g_dhcp_client_get_option(dhcp_client, G_DHCP_SUBNET); - if (option != NULL) + if (option) return g_strdup(option->data); case INIT_SELECTING: + case REBOOTING: case REQUESTING: case RELEASED: + case DECLINED: case IPV4LL_PROBE: case IPV4LL_ANNOUNCE: case INFORMATION_REQ: case SOLICITATION: case REQUEST: + case CONFIRM: case RENEW: case REBIND: + case RELEASE: + case DECLINE: break; } return NULL; @@ -2394,8 +3077,8 @@ char *g_dhcp_client_get_netmask(GDHCPClient *dhcp_client) GDHCPClientError g_dhcp_client_set_request(GDHCPClient *dhcp_client, unsigned int option_code) { - if (g_list_find(dhcp_client->request_list, - GINT_TO_POINTER((int) option_code)) == NULL) + if (!g_list_find(dhcp_client->request_list, + GINT_TO_POINTER((int)option_code))) dhcp_client->request_list = g_list_prepend( dhcp_client->request_list, (GINT_TO_POINTER((int) option_code))); @@ -2414,28 +3097,66 @@ 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) +static uint8_t *alloc_dhcp_option(int code, const uint8_t *data, unsigned size) { uint8_t *storage; - int len = strnlen(str, 255); - storage = malloc(len + extra + OPT_DATA); + storage = g_try_malloc(size + OPT_DATA); + if (!storage) + return NULL; + storage[OPT_CODE] = code; - storage[OPT_LEN] = len + extra; - memcpy(storage + extra + OPT_DATA, str, len); + storage[OPT_LEN] = size; + memcpy(&storage[OPT_DATA], data, size); return storage; } -/* Now only support send hostname */ +static uint8_t *alloc_dhcp_data_option(int code, const uint8_t *data, + unsigned size) +{ + return alloc_dhcp_option(code, data, MIN(size, 255)); +} + +static uint8_t *alloc_dhcp_string_option(int code, const char *str) +{ + return alloc_dhcp_data_option(code, (const uint8_t *)str, strlen(str)); +} + +GDHCPClientError g_dhcp_client_set_id(GDHCPClient *dhcp_client) +{ + const unsigned maclen = 6; + const unsigned idlen = maclen + 1; + const uint8_t option_code = G_DHCP_CLIENT_ID; + uint8_t idbuf[idlen]; + uint8_t *data_option; + + idbuf[0] = ARPHRD_ETHER; + + memcpy(&idbuf[1], dhcp_client->mac_address, maclen); + + data_option = alloc_dhcp_data_option(option_code, idbuf, idlen); + if (!data_option) + return G_DHCP_CLIENT_ERROR_NOMEM; + + g_hash_table_insert(dhcp_client->send_value_hash, + GINT_TO_POINTER((int) option_code), data_option); + + return G_DHCP_CLIENT_ERROR_NONE; +} + +/* Now only support send hostname and vendor class ID */ GDHCPClientError g_dhcp_client_set_send(GDHCPClient *dhcp_client, unsigned char option_code, const char *option_value) { uint8_t *binary_option; - if (option_code == G_DHCP_HOST_NAME && option_value != NULL) { - binary_option = alloc_dhcp_option(option_code, - option_value, 0); + if ((option_code == G_DHCP_HOST_NAME || + option_code == G_DHCP_VENDOR_CLASS_ID) && option_value) { + binary_option = alloc_dhcp_string_option(option_code, + option_value); + if (!binary_option) + return G_DHCP_CLIENT_ERROR_NOMEM; g_hash_table_insert(dhcp_client->send_value_hash, GINT_TO_POINTER((int) option_code), binary_option); @@ -2450,7 +3171,7 @@ static uint8_t *alloc_dhcpv6_option(uint16_t code, uint8_t *option, uint8_t *storage; storage = g_malloc(2 + 2 + len); - if (storage == NULL) + if (!storage) return NULL; storage[0] = code >> 8; @@ -2462,12 +3183,18 @@ static uint8_t *alloc_dhcpv6_option(uint16_t code, uint8_t *option, return storage; } +gboolean g_dhcpv6_client_clear_send(GDHCPClient *dhcp_client, uint16_t code) +{ + return g_hash_table_remove(dhcp_client->send_value_hash, + GINT_TO_POINTER((int)code)); +} + void g_dhcpv6_client_set_send(GDHCPClient *dhcp_client, uint16_t option_code, uint8_t *option_value, uint16_t option_len) { - if (option_value != NULL) { + if (option_value) { uint8_t *binary_option; debug(dhcp_client, "setting option %d to %p len %d", @@ -2475,40 +3202,24 @@ void g_dhcpv6_client_set_send(GDHCPClient *dhcp_client, binary_option = alloc_dhcpv6_option(option_code, option_value, option_len); - if (binary_option != NULL) + if (binary_option) g_hash_table_insert(dhcp_client->send_value_hash, GINT_TO_POINTER((int) option_code), binary_option); } } -void g_dhcpv6_client_reset_renew(GDHCPClient *dhcp_client) +void g_dhcpv6_client_reset_request(GDHCPClient *dhcp_client) { - if (dhcp_client == NULL || dhcp_client->type == G_DHCP_IPV4) + if (!dhcp_client || dhcp_client->type != G_DHCP_IPV6) return; - dhcp_client->last_renew = time(0); -} - -void g_dhcpv6_client_reset_rebind(GDHCPClient *dhcp_client) -{ - if (dhcp_client == NULL || dhcp_client->type == G_DHCP_IPV4) - return; - - dhcp_client->last_rebind = time(0); -} - -void g_dhcpv6_client_set_expire(GDHCPClient *dhcp_client, uint32_t timeout) -{ - if (dhcp_client == NULL || dhcp_client->type == G_DHCP_IPV4) - return; - - dhcp_client->expire = time(0) + timeout; + dhcp_client->last_request = time(NULL); } uint16_t g_dhcpv6_client_get_status(GDHCPClient *dhcp_client) { - if (dhcp_client == NULL || dhcp_client->type == G_DHCP_IPV4) + if (!dhcp_client || dhcp_client->type != G_DHCP_IPV6) return 0; return dhcp_client->status_code; @@ -2516,7 +3227,7 @@ uint16_t g_dhcpv6_client_get_status(GDHCPClient *dhcp_client) GDHCPClient *g_dhcp_client_ref(GDHCPClient *dhcp_client) { - if (dhcp_client == NULL) + if (!dhcp_client) return NULL; __sync_fetch_and_add(&dhcp_client->ref_count, 1); @@ -2526,7 +3237,7 @@ GDHCPClient *g_dhcp_client_ref(GDHCPClient *dhcp_client) void g_dhcp_client_unref(GDHCPClient *dhcp_client) { - if (dhcp_client == NULL) + if (!dhcp_client) return; if (__sync_fetch_and_sub(&dhcp_client->ref_count, 1) != 1) @@ -2545,6 +3256,7 @@ void g_dhcp_client_unref(GDHCPClient *dhcp_client) g_hash_table_destroy(dhcp_client->code_value_hash); g_hash_table_destroy(dhcp_client->send_value_hash); + g_hash_table_destroy(dhcp_client->secs_bcast_hash); g_free(dhcp_client); } @@ -2552,9 +3264,33 @@ void g_dhcp_client_unref(GDHCPClient *dhcp_client) void g_dhcp_client_set_debug(GDHCPClient *dhcp_client, GDHCPDebugFunc func, gpointer user_data) { - if (dhcp_client == NULL) + if (!dhcp_client) return; dhcp_client->debug_func = func; dhcp_client->debug_data = user_data; } + +static GDHCPIAPrefix *copy_prefix(gpointer data) +{ + GDHCPIAPrefix *copy, *prefix = data; + + copy = g_try_new(GDHCPIAPrefix, 1); + if (!copy) + return NULL; + + memcpy(copy, prefix, sizeof(GDHCPIAPrefix)); + + return copy; +} + +GSList *g_dhcpv6_copy_prefixes(GSList *prefixes) +{ + GSList *copy = NULL; + GSList *list; + + for (list = prefixes; list; list = list->next) + copy = g_slist_prepend(copy, copy_prefix(list->data)); + + return copy; +}