X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=gdhcp%2Fclient.c;h=2bdc61639a4ad9e297bebef585500baf990acb4e;hb=0f1b235df50bb53099060b789aad8e8f6d7821b9;hp=21b274568be7f619902311d00c4890d72387c39b;hpb=15aa3f3f8240bf4f5dd30582258b7366c18173c0;p=platform%2Fupstream%2Fconnman.git diff --git a/gdhcp/client.c b/gdhcp/client.c old mode 100644 new mode 100755 index 21b2745..2bdc616 --- 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-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 @@ -30,42 +30,82 @@ #include #include #include +#include +#include #include +#include #include -#include #include #include #include +#include "../src/connman.h" +#include "../src/shared/arp.h" #include "gdhcp.h" #include "common.h" -#define DISCOVER_TIMEOUT 3 -#define DISCOVER_RETRIES 3 +#define DISCOVER_TIMEOUT 5 +#define DISCOVER_RETRIES 6 -#define REQUEST_TIMEOUT 3 +#if defined TIZEN_EXT +#define REQUEST_TIMEOUT 1 +#else +#define REQUEST_TIMEOUT 5 +#endif #define REQUEST_RETRIES 3 +#if defined TIZEN_EXT +#define DISCOVER_TIMEOUT_WIFI 1 +#define DISCOVER_RETRIES_WIFI 10 +static int dhcp_discover_timeout = DISCOVER_TIMEOUT_WIFI; +static int dhcp_discover_max_retry = DISCOVER_RETRIES_WIFI; + +void set_dhcp_discover_timeout(int timeout_value) +{ + dhcp_discover_timeout = timeout_value; +} + +void set_dhcp_discover_retry_count(int retry_count) +{ + dhcp_discover_max_retry = retry_count; +} +#endif + typedef enum _listen_mode { L_NONE, L2, L3, + L_ARP, } ListenMode; typedef enum _dhcp_client_state { INIT_SELECTING, + REBOOTING, REQUESTING, BOUND, + DECLINED, RENEWING, REBINDING, RELEASED, + IPV4LL_PROBE, + IPV4LL_ANNOUNCE, + IPV4LL_MONITOR, + IPV4LL_DEFEND, + INFORMATION_REQ, + SOLICITATION, + REQUEST, + CONFIRM, + RENEW, + REBIND, + RELEASE, + DECLINE, } ClientState; struct _GDHCPClient { - gint ref_count; + int ref_count; GDHCPType type; ClientState state; int ifindex; @@ -75,39 +115,103 @@ 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; uint8_t retry_times; 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; + gpointer ipv4ll_available_data; GDHCPClientEventFunc no_lease_cb; gpointer no_lease_data; GDHCPClientEventFunc lease_lost_cb; gpointer lease_lost_data; + GDHCPClientEventFunc ipv4ll_lost_cb; + gpointer ipv4ll_lost_data; GDHCPClientEventFunc address_conflict_cb; gpointer address_conflict_data; GDHCPDebugFunc debug_func; gpointer debug_data; + GDHCPClientEventFunc information_req_cb; + gpointer information_req_data; + GDHCPClientEventFunc solicitation_cb; + gpointer solicitation_data; + GDHCPClientEventFunc advertise_cb; + gpointer advertise_data; + GDHCPClientEventFunc request_cb; + gpointer request_data; + GDHCPClientEventFunc renew_cb; + 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; + 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; + time_t last_request; + uint32_t expire; + bool retransmit; + struct timeval start_time; + bool request_bcast; +#if defined TIZEN_EXT + uint32_t dhcp_lease_seconds; + gboolean init_reboot; +#endif }; -static GTimer *timer = NULL; +static inline void debug(GDHCPClient *client, const char *format, ...) +{ + char str[256]; + va_list ap; + + if (!client->debug_func) + return; + + va_start(ap, format); + + if (vsnprintf(str, sizeof(str), format, ap) > 0) + client->debug_func(str, client->debug_data); + + va_end(ap); +} /* Initialize the packet with the proper defaults */ -static void init_packet(GDHCPClient *dhcp_client, - struct dhcp_packet *packet, char type) +static void init_packet(GDHCPClient *dhcp_client, gpointer pkt, char type) { - dhcp_init_header(packet, type); + if (dhcp_client->type == G_DHCP_IPV6) + dhcpv6_init_header(pkt, type); + else { + struct dhcp_packet *packet = pkt; - memcpy(packet->chaddr, dhcp_client->mac_address, 6); + dhcp_init_header(packet, type); + memcpy(packet->chaddr, dhcp_client->mac_address, 6); + } } static void add_request_options(GDHCPClient *dhcp_client, @@ -132,6 +236,205 @@ static void add_request_options(GDHCPClient *dhcp_client, } } +struct hash_params { + unsigned char *buf; + int max_buf; + unsigned char **ptr_buf; +}; + +static void add_dhcpv6_binary_option(gpointer key, gpointer value, + gpointer user_data) +{ + uint8_t *option = value; + uint16_t len; + struct hash_params *params = user_data; + + /* option[0][1] contains option code */ + len = option[2] << 8 | option[3]; + + if ((*params->ptr_buf + len + 2 + 2) > (params->buf + params->max_buf)) + return; + + memcpy(*params->ptr_buf, option, len + 2 + 2); + (*params->ptr_buf) += len + 2 + 2; +} + +static void add_dhcpv6_send_options(GDHCPClient *dhcp_client, + unsigned char *buf, int max_buf, + unsigned char **ptr_buf) +{ + struct hash_params params = { + .buf = buf, + .max_buf = max_buf, + .ptr_buf = ptr_buf + }; + + if (dhcp_client->type != G_DHCP_IPV6) + return; + + g_hash_table_foreach(dhcp_client->send_value_hash, + add_dhcpv6_binary_option, ¶ms); + + *ptr_buf = *params.ptr_buf; +} + +static void copy_option(uint8_t *buf, uint16_t code, uint16_t len, + uint8_t *msg) +{ + buf[0] = code >> 8; + buf[1] = code & 0xff; + buf[2] = len >> 8; + buf[3] = len & 0xff; + 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, value; + bool added; + int32_t diff; + int len; + + 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) + return; + + len = 2 + 2 + dhcp_client->duid_len; + if ((*ptr_buf + len) > (buf + max_buf)) { + debug(dhcp_client, "Too long dhcpv6 message " + "when writing client id option"); + return; + } + + 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) + break; + + 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; + added = true; + 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; + 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); + } +} + static void add_binary_option(gpointer key, gpointer value, gpointer user_data) { uint8_t *option = value; @@ -147,198 +450,719 @@ 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; + debug(dhcp_client, "sending DHCP discover request"); + 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; - struct in_addr addr; - init_packet(dhcp_client, &packet, DHCPREQUEST); + dhcp_client->state = DECLINED; + dhcp_client->retry_times = 0; - packet.xid = dhcp_client->xid; + debug(dhcp_client, "sending DHCP decline"); - dhcp_add_simple_option(&packet, DHCP_REQUESTED_IP, - dhcp_client->requested_ip); - dhcp_add_simple_option(&packet, DHCP_SERVER_ID, dhcp_client->server_ip); + init_packet(dhcp_client, &packet, DHCPDECLINE); - add_request_options(dhcp_client, &packet); + packet.xid = dhcp_client->xid; + packet.secs = dhcp_attempt_secs(dhcp_client); - add_send_options(dhcp_client, &packet); + if (requested) + dhcp_add_option_uint32(&packet, DHCP_REQUESTED_IP, requested); - addr.s_addr = dhcp_client->requested_ip; + 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; - init_packet(dhcp_client , &packet, DHCPREQUEST); - packet.xid = dhcp_client->xid; - packet.ciaddr = dhcp_client->requested_ip; + debug(dhcp_client, "sending DHCP request (state %d)", + dhcp_client->state); - add_request_options(dhcp_client, &packet); + init_packet(dhcp_client, &packet, DHCPREQUEST); - add_send_options(dhcp_client, &packet); + packet.xid = dhcp_client->xid; +#if defined TIZEN_EXT + if (dhcp_client->init_reboot != TRUE) +#endif + packet.secs = dhcp_attempt_secs(dhcp_client); - return dhcp_send_kernel_packet(&packet, - dhcp_client->requested_ip, CLIENT_PORT, - dhcp_client->server_ip, SERVER_PORT); -} + if (dhcp_client->state == REQUESTING || dhcp_client->state == REBOOTING) + dhcp_add_option_uint32(&packet, DHCP_REQUESTED_IP, + dhcp_client->requested_ip); -static int send_rebound(GDHCPClient *dhcp_client) -{ - struct dhcp_packet packet; + 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); + 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); } -static gboolean interface_is_up(int index) +static gboolean ipv4ll_probe_timeout(gpointer dhcp_data); +static int switch_listening_mode(GDHCPClient *dhcp_client, + ListenMode listen_mode); + +static gboolean send_probe_packet(gpointer dhcp_data) { - int sk, err; - struct ifreq ifr; - gboolean ret = FALSE; + GDHCPClient *dhcp_client; + guint timeout; - sk = socket(PF_INET, SOCK_DGRAM, 0); - if (sk < 0) { - perror("Open socket error"); - return FALSE; + dhcp_client = 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 = arp_random_ip(); } - memset(&ifr, 0, sizeof(ifr)); - ifr.ifr_ifindex = index; + debug(dhcp_client, "sending IPV4LL probe request"); - err = ioctl(sk, SIOCGIFNAME, &ifr); - if (err < 0) { - perror("Get interface name error"); - goto done; + if (dhcp_client->retry_times == 1) { + dhcp_client->state = IPV4LL_PROBE; + switch_listening_mode(dhcp_client, L_ARP); } + 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 = __connman_util_random_delay_ms(PROBE_MAX-PROBE_MIN); + timeout += PROBE_MIN*1000; + } else + timeout = (ANNOUNCE_WAIT * 1000); + + dhcp_client->timeout = g_timeout_add_full(G_PRIORITY_HIGH, + timeout, + ipv4ll_probe_timeout, + dhcp_client, + NULL); + return FALSE; +} - err = ioctl(sk, SIOCGIFFLAGS, &ifr); - if (err < 0) { - perror("Get interface flags error"); - goto done; +static gboolean ipv4ll_announce_timeout(gpointer dhcp_data); +static gboolean ipv4ll_defend_timeout(gpointer dhcp_data); + +static gboolean send_announce_packet(gpointer dhcp_data) +{ + GDHCPClient *dhcp_client; + + dhcp_client = dhcp_data; + + debug(dhcp_client, "sending IPV4LL announce request"); + + arp_send_packet(dhcp_client->mac_address, + dhcp_client->requested_ip, + dhcp_client->requested_ip, + dhcp_client->ifindex); + + remove_timeouts(dhcp_client); + + if (dhcp_client->state == IPV4LL_DEFEND) { + dhcp_client->timeout = + g_timeout_add_seconds_full(G_PRIORITY_HIGH, + DEFEND_INTERVAL, + ipv4ll_defend_timeout, + dhcp_client, + NULL); + return TRUE; + } else + dhcp_client->timeout = + g_timeout_add_seconds_full(G_PRIORITY_HIGH, + ANNOUNCE_INTERVAL, + ipv4ll_announce_timeout, + dhcp_client, + NULL); + return TRUE; +} + +void g_dhcpv6_client_set_retransmit(GDHCPClient *dhcp_client) +{ + if (!dhcp_client) + return; + + dhcp_client->retransmit = true; +} + +void g_dhcpv6_client_clear_retransmit(GDHCPClient *dhcp_client) +{ + if (!dhcp_client) + return; + + dhcp_client->retransmit = false; +} + +int g_dhcpv6_create_duid(GDHCPDuidType duid_type, int index, int type, + unsigned char **duid, int *duid_len) +{ + time_t duid_time; + + switch (duid_type) { + case G_DHCPV6_DUID_LLT: + *duid_len = 2 + 2 + 4 + ETH_ALEN; + *duid = g_try_malloc(*duid_len); + if (!*duid) + return -ENOMEM; + + (*duid)[0] = 0; + (*duid)[1] = 1; + __connman_inet_get_interface_mac_address(index, &(*duid)[2 + 2 + 4]); + (*duid)[2] = 0; + (*duid)[3] = type; + duid_time = time(NULL) - DUID_TIME_EPOCH; + (*duid)[4] = duid_time >> 24; + (*duid)[5] = duid_time >> 16; + (*duid)[6] = duid_time >> 8; + (*duid)[7] = duid_time & 0xff; + break; + case G_DHCPV6_DUID_EN: + return -EINVAL; + case G_DHCPV6_DUID_LL: + *duid_len = 2 + 2 + ETH_ALEN; + *duid = g_try_malloc(*duid_len); + if (!*duid) + return -ENOMEM; + + (*duid)[0] = 0; + (*duid)[1] = 3; + __connman_inet_get_interface_mac_address(index, &(*duid)[2 + 2]); + (*duid)[2] = 0; + (*duid)[3] = type; + break; } - if (ifr.ifr_flags & IFF_UP) - ret = TRUE; + return 0; +} + +static gchar *convert_to_hex(unsigned char *buf, int len) +{ + gchar *ret = g_try_malloc(len * 2 + 1); + int i; -done: - close(sk); + for (i = 0; ret && i < len; i++) + g_snprintf(ret + i * 2, 3, "%02x", buf[i]); return ret; } -static char *get_interface_name(int index) +int g_dhcpv6_client_set_duid(GDHCPClient *dhcp_client, unsigned char *duid, + int duid_len) { - struct ifreq ifr; - int sk, err; + if (!dhcp_client || dhcp_client->type != G_DHCP_IPV6) + return -EINVAL; - if (index < 0) - return NULL; + g_free(dhcp_client->duid); - sk = socket(PF_INET, SOCK_DGRAM, 0); - if (sk < 0) { - perror("Open socket error"); - return NULL; + 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); } - memset(&ifr, 0, sizeof(ifr)); - ifr.ifr_ifindex = index; + return 0; +} - err = ioctl(sk, SIOCGIFNAME, &ifr); - if (err < 0) { - perror("Get interface name error"); - close(sk); - return NULL; +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); + } } - close(sk); + g_dhcpv6_client_set_send(dhcp_client, G_DHCPV6_IA_PD, + options, len); - return g_strdup(ifr.ifr_name); + return 0; } -static void get_interface_mac_address(int index, uint8_t *mac_address) +uint32_t g_dhcpv6_client_get_iaid(GDHCPClient *dhcp_client) { - struct ifreq ifr; - int sk, err; + if (!dhcp_client || dhcp_client->type != G_DHCP_IPV6) + return 0; + + return dhcp_client->iaid; +} - sk = socket(PF_INET, SOCK_DGRAM, 0); - if (sk < 0) { - perror("Open socket error"); +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]; + + __connman_inet_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, + time_t *started, + time_t *expire) +{ + if (!dhcp_client || dhcp_client->type != G_DHCP_IPV6) + return -EINVAL; + + if (T1) + *T1 = (dhcp_client->expire == 0xffffffff) ? 0xffffffff: + dhcp_client->T1; + + if (T2) + *T2 = (dhcp_client->expire == 0xffffffff) ? 0xffffffff: + dhcp_client->T2; + + if (started) + *started = dhcp_client->last_request; + + if (expire) + *expire = (dhcp_client->expire == 0xffffffff) ? 0xffffffff: + dhcp_client->last_request + dhcp_client->expire; + + 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 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; + + iaid = g_dhcpv6_client_get_iaid(dhcp_client); + if (iaid == 0) { + g_dhcpv6_client_create_iaid(dhcp_client, index, buf); return; } - memset(&ifr, 0, sizeof(ifr)); - ifr.ifr_ifindex = index; + 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, + bool add_iaaddr, const char *ia_na) +{ + 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) { + struct in6_addr addr; + + g_dhcp_client_set_request(dhcp_client, G_DHCPV6_IA_NA); + + /* + * 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) { + 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); + + 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)); + } - err = ioctl(sk, SIOCGIFNAME, &ifr); - if (err < 0) { - perror("Get interface name error"); - goto done; + } else + return -EINVAL; + + 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); } - err = ioctl(sk, SIOCGIFHWADDR, &ifr); - if (err < 0) { - perror("Get mac address error"); - goto done; + 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; + int i, j, len = sizeof(uint16_t) * args; + uint8_t *values; + + values = g_try_malloc(len); + if (!values) + return -ENOMEM; + + va_start(va, args); + for (i = 0, j = 0; i < args; i++) { + uint16_t value = va_arg(va, int); + values[j++] = value >> 8; + values[j++] = value & 0xff; + } + va_end(va); + + g_dhcpv6_client_set_send(dhcp_client, G_DHCPV6_ORO, values, len); + + g_free(values); + + return 0; +} + +static int send_dhcpv6_msg(GDHCPClient *dhcp_client, int type, char *msg) +{ + struct dhcpv6_packet *packet; + uint8_t buf[MAX_DHCPV6_PKT_SIZE]; + unsigned char *ptr; + int ret, max_buf; + + memset(buf, 0, sizeof(buf)); + packet = (struct dhcpv6_packet *)&buf[0]; + ptr = buf + sizeof(struct dhcpv6_packet); + + init_packet(dhcp_client, packet, type); + + 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; } - memcpy(mac_address, ifr.ifr_hwaddr.sa_data, 6); + 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); + + add_dhcpv6_request_options(dhcp_client, packet, buf, max_buf, &ptr); + + add_dhcpv6_send_options(dhcp_client, buf, max_buf, &ptr); + + ret = dhcpv6_send_packet(dhcp_client->ifindex, packet, ptr - buf); + + debug(dhcp_client, "sent %d pkt %p len %d", ret, packet, ptr - buf); + return ret; +} + +static int send_solicitation(GDHCPClient *dhcp_client) +{ + return send_dhcpv6_msg(dhcp_client, DHCPV6_SOLICIT, "solicit"); +} + +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"); +} + +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"); +} -done: - close(sk); +static int send_information_req(GDHCPClient *dhcp_client) +{ + return send_dhcpv6_msg(dhcp_client, DHCPV6_INFORMATION_REQ, + "information-req"); } static void remove_value(gpointer data, gpointer user_data) @@ -352,6 +1176,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, @@ -365,33 +1190,34 @@ 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; dhcp_client->ifindex = ifindex; dhcp_client->lease_available_cb = NULL; + dhcp_client->ipv4ll_available_cb = NULL; dhcp_client->no_lease_cb = NULL; dhcp_client->lease_lost_cb = NULL; + dhcp_client->ipv4ll_lost_cb = NULL; dhcp_client->address_conflict_cb = NULL; dhcp_client->listener_watch = 0; dhcp_client->retry_times = 0; @@ -400,8 +1226,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_request = time(NULL); + dhcp_client->expire = 0; + dhcp_client->request_bcast = false; *error = G_DHCP_CLIENT_ERROR_NONE; @@ -466,48 +1299,51 @@ static int dhcp_l2_socket(int ifindex) .filter = (struct sock_filter *) filter_instr, }; - fd = socket(PF_PACKET, SOCK_DGRAM, 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 */ setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter_prog, sizeof(filter_prog)); + memset(&sock, 0, sizeof(sock)); sock.sll_family = AF_PACKET; sock.sll_protocol = htons(ETH_P_IP); 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; @@ -526,48 +1362,183 @@ static int dhcp_recv_l2_packet(struct dhcp_packet *dhcp_pkt, int fd) /* packet is bigger than sizeof(packet), we did partial read */ return -1; - /* ignore any extra garbage bytes */ - bytes = ntohs(packet.ip.tot_len); + /* ignore any extra garbage bytes */ + bytes = ntohs(packet.ip.tot_len); + + if (!sanity_check(&packet, bytes)) + return -1; + + check = packet.ip.check; + packet.ip.check = 0; + if (check != dhcp_checksum(&packet.ip, sizeof(packet.ip))) + return -1; + + /* verify UDP checksum. IP header has to be modified for this */ + memset(&packet.ip, 0, offsetof(struct iphdr, protocol)); + /* ip.xx fields which are not memset: protocol, check, saddr, daddr */ + packet.ip.tot_len = packet.udp.len; /* yes, this is needed */ + check = packet.udp.check; + packet.udp.check = 0; + if (check && check != dhcp_checksum(&packet, bytes)) + return -1; + + memcpy(dhcp_pkt, &packet.data, bytes - (sizeof(packet.ip) + + sizeof(packet.udp))); + + 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; + + remove_timeouts(dhcp_client); + + switch_listening_mode(dhcp_client, L_NONE); + dhcp_client->retry_times = 0; + dhcp_client->requested_ip = 0; + + dhcp_client->requested_ip = arp_random_ip(); + + /*first wait a random delay to avoid storm of arp request on boot*/ + timeout = __connman_util_random_delay_ms(PROBE_WAIT); + + dhcp_client->retry_times++; + dhcp_client->timeout = g_timeout_add_full(G_PRIORITY_HIGH, + timeout, + send_probe_packet, + dhcp_client, + NULL); +} + +static void ipv4ll_stop(GDHCPClient *dhcp_client) +{ + + switch_listening_mode(dhcp_client, L_NONE); + + remove_timeouts(dhcp_client); + + if (dhcp_client->listener_watch > 0) { + g_source_remove(dhcp_client->listener_watch); + dhcp_client->listener_watch = 0; + } + + dhcp_client->state = IPV4LL_PROBE; + dhcp_client->retry_times = 0; + dhcp_client->requested_ip = 0; + + g_free(dhcp_client->assigned_ip); + dhcp_client->assigned_ip = NULL; +} + +static int ipv4ll_recv_arp_packet(GDHCPClient *dhcp_client) +{ + int bytes; + struct ether_arp arp; + uint32_t ip_requested; + int source_conflict; + int target_conflict; + guint timeout_ms; + + memset(&arp, 0, sizeof(arp)); + bytes = read(dhcp_client->listener_sockfd, &arp, sizeof(arp)); + if (bytes < 0) + return bytes; + + if (arp.arp_op != htons(ARPOP_REPLY) && + arp.arp_op != htons(ARPOP_REQUEST)) + return -EINVAL; + + 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)); + + target_conflict = !memcmp(arp.arp_tpa, &ip_requested, + sizeof(ip_requested)); + + if (!source_conflict && !target_conflict) + return 0; + + dhcp_client->conflicts++; - if (sanity_check(&packet, bytes) == FALSE) - return -1; + debug(dhcp_client, "IPV4LL conflict detected"); - check = packet.ip.check; - packet.ip.check = 0; - if (check != dhcp_checksum(&packet.ip, sizeof(packet.ip))) - return -1; + if (dhcp_client->state == IPV4LL_MONITOR) { + if (!source_conflict) + return 0; + dhcp_client->state = IPV4LL_DEFEND; + debug(dhcp_client, "DEFEND mode conflicts : %d", + dhcp_client->conflicts); + /*Try to defend with a single announce*/ + send_announce_packet(dhcp_client); + return 0; + } - /* verify UDP checksum. IP header has to be modified for this */ - memset(&packet.ip, 0, offsetof(struct iphdr, protocol)); - /* ip.xx fields which are not memset: protocol, check, saddr, daddr */ - packet.ip.tot_len = packet.udp.len; /* yes, this is needed */ - check = packet.udp.check; - packet.udp.check = 0; - if (check && check != dhcp_checksum(&packet, bytes)) - return -1; + if (dhcp_client->state == IPV4LL_DEFEND) { + if (!source_conflict) + return 0; + else if (dhcp_client->ipv4ll_lost_cb) + dhcp_client->ipv4ll_lost_cb(dhcp_client, + dhcp_client->ipv4ll_lost_data); + } - memcpy(dhcp_pkt, &packet.data, bytes - (sizeof(packet.ip) + - sizeof(packet.udp))); + ipv4ll_stop(dhcp_client); - if (dhcp_pkt->cookie != htonl(DHCP_MAGIC)) - return -1; + /* If we got a lot of conflicts, RFC3927 states that we have + * to wait RATE_LIMIT_INTERVAL before retrying, + */ + 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 bytes - (sizeof(packet.ip) + sizeof(packet.udp)); + return 0; } -static gboolean check_package_owner(GDHCPClient *dhcp_client, - struct dhcp_packet *packet) +static bool check_package_owner(GDHCPClient *dhcp_client, gpointer pkt) { - if (packet->xid != dhcp_client->xid) - return FALSE; + if (dhcp_client->type == G_DHCP_IPV6) { + struct dhcpv6_packet *packet6 = pkt; + uint32_t xid; - if (packet->hlen != 6) - return FALSE; + if (!packet6) + return false; - if (memcmp(packet->chaddr, dhcp_client->mac_address, 6)) - return FALSE; + xid = packet6->transaction_id[0] << 16 | + packet6->transaction_id[1] << 8 | + packet6->transaction_id[2]; - return TRUE; + if (xid != dhcp_client->xid) + return false; + } else { + struct dhcp_packet *packet = pkt; + + if (packet->xid != dhcp_client->xid) + return false; + + if (packet->hlen != 6) + return false; + + if (memcmp(packet->chaddr, dhcp_client->mac_address, 6)) + return false; + } + + return true; } static void start_request(GDHCPClient *dhcp_client); @@ -576,6 +1547,24 @@ static gboolean request_timeout(gpointer user_data) { GDHCPClient *dhcp_client = user_data; +#if defined TIZEN_EXT + if (dhcp_client->init_reboot) { + debug(dhcp_client, "DHCPREQUEST of INIT-REBOOT has failed"); + + /* Start DHCPDISCOVERY when DHCPREQUEST of INIT-REBOOT has failed */ + g_dhcp_client_set_address_known(dhcp_client, FALSE); + + dhcp_client->retry_times = 0; + dhcp_client->requested_ip = 0; + + g_dhcp_client_start(dhcp_client, dhcp_client->last_address); + + return FALSE; + } +#endif + debug(dhcp_client, "request timeout (retries %d)", + dhcp_client->retry_times); + dhcp_client->retry_times++; start_request(dhcp_client); @@ -583,6 +1572,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); @@ -595,9 +1590,12 @@ static int switch_listening_mode(GDHCPClient *dhcp_client, if (dhcp_client->listen_mode == listen_mode) return 0; + debug(dhcp_client, "switch listening mode (%d ==> %d)", + dhcp_client->listen_mode, listen_mode); + if (dhcp_client->listen_mode != L_NONE) { - g_source_remove(dhcp_client->listener_watch); - dhcp_client->listener_channel = NULL; + if (dhcp_client->listener_watch > 0) + g_source_remove(dhcp_client->listener_watch); dhcp_client->listen_mode = L_NONE; dhcp_client->listener_sockfd = -1; dhcp_client->listener_watch = 0; @@ -608,9 +1606,17 @@ static int switch_listening_mode(GDHCPClient *dhcp_client, if (listen_mode == L2) listener_sockfd = dhcp_l2_socket(dhcp_client->ifindex); - else if (listen_mode == L3) - listener_sockfd = dhcp_l3_socket(CLIENT_PORT, - dhcp_client->interface); + else if (listen_mode == L3) { + if (dhcp_client->type == G_DHCP_IPV6) + listener_sockfd = dhcp_l3_socket(DHCPV6_CLIENT_PORT, + dhcp_client->interface, + AF_INET6); + else + listener_sockfd = dhcp_l3_socket(CLIENT_PORT, + dhcp_client->interface, + AF_INET); + } else if (listen_mode == L_ARP) + listener_sockfd = arp_socket(dhcp_client->ifindex); else return -EIO; @@ -618,7 +1624,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; @@ -626,28 +1632,27 @@ 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, - listener_event, dhcp_client, - NULL); - g_io_channel_unref(dhcp_client->listener_channel); + g_io_add_watch_full(listener_channel, G_PRIORITY_HIGH, + G_IO_IN | G_IO_NVAL | G_IO_ERR | G_IO_HUP, + listener_event, g_dhcp_client_ref(dhcp_client), + listener_watch_destroy); + g_io_channel_unref(listener_channel); return 0; } static void start_request(GDHCPClient *dhcp_client) { - if (dhcp_client->retry_times == REQUEST_RETRIES) { - dhcp_client->state = INIT_SELECTING; + debug(dhcp_client, "start request (retries %d)", + dhcp_client->retry_times); - if (dhcp_client->no_lease_cb != NULL) + if (dhcp_client->retry_times == REQUEST_RETRIES) { + if (dhcp_client->no_lease_cb) dhcp_client->no_lease_cb(dhcp_client, - dhcp_client->no_lease_data); - + dhcp_client->no_lease_data); return; } @@ -656,7 +1661,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, @@ -667,17 +1672,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; @@ -686,102 +1689,157 @@ static uint32_t get_lease(struct dhcp_packet *packet) static void restart_dhcp(GDHCPClient *dhcp_client, int retry_times) { - if (dhcp_client->timeout > 0) { - g_source_remove(dhcp_client->timeout); - dhcp_client->timeout = 0; - } + debug(dhcp_client, "restart DHCP (retries %d)", retry_times); + + 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); + 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; - switch_listening_mode(dhcp_client, L2); + debug(dhcp_client, "lease expired"); - dhcp_client->lease_seconds >>= 1; + /*remove all timeouts if they are set*/ + remove_timeouts(dhcp_client); - /* We need to have enough time to receive ACK package*/ - if (dhcp_client->lease_seconds <= 6) { + restart_dhcp(dhcp_client, 0); - /* ip need to be cleared */ - if (dhcp_client->lease_lost_cb != NULL) - dhcp_client->lease_lost_cb(dhcp_client, - dhcp_client->lease_lost_data); + /* ip need to be cleared */ + if (dhcp_client->lease_lost_cb) + dhcp_client->lease_lost_cb(dhcp_client, + dhcp_client->lease_lost_data); - restart_dhcp(dhcp_client, 0); - } else { - send_rebound(dhcp_client); + return false; +} - dhcp_client->timeout = - g_timeout_add_seconds_full(G_PRIORITY_HIGH, - dhcp_client->lease_seconds >> 1, - start_rebound_timeout, - dhcp_client, - NULL); +static gboolean continue_rebound(gpointer user_data) +{ + GDHCPClient *dhcp_client = user_data; + uint64_t rand; + + 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) { + 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; - gdouble elapse; - gulong microseconds; + uint64_t rand; - elapse = g_timer_elapsed(timer, µseconds); - - g_timer_start(timer); + 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); + 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) { + debug(dhcp_client, "start bound"); + dhcp_client->state = BOUND; - if (timer == NULL) - timer = g_timer_new(); + 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); } @@ -789,10 +1847,16 @@ static gboolean restart_dhcp_timeout(gpointer user_data) { GDHCPClient *dhcp_client = user_data; - dhcp_client->ack_retry_times++; - - restart_dhcp(dhcp_client, dhcp_client->ack_retry_times); + debug(dhcp_client, "restart DHCP timeout"); + 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; } @@ -829,9 +1893,13 @@ static char *malloc_option_value_string(uint8_t *option, GDHCPOptionType type) len = option[OPT_LEN - OPT_DATA]; type &= OPTION_TYPE_MASK; optlen = dhcp_option_lengths[type]; + if (optlen == 0) + return NULL; upper_length = len_of_option_as_string[type] * ((unsigned)len / (unsigned)optlen); - dest = ret = malloc(upper_length + 1); + dest = ret = g_malloc(upper_length + 1); + if (!ret) + return NULL; while (len >= optlen) { switch (type) { @@ -839,16 +1907,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: @@ -869,24 +1934,339 @@ static char *malloc_option_value_string(uint8_t *option, GDHCPOptionType type) return ret; } -static GList *get_option_value_list(char *value) +static GList *get_option_value_list(char *value, GDHCPOptionType type) { char *pos = value; GList *list = NULL; - while ((pos = strchr(pos, ' ')) != NULL) { + if (!pos) + return NULL; + + if (type == OPTION_STRING) + return g_list_append(list, g_strdup(value)); + + while ((pos = strchr(pos, ' '))) { *pos = '\0'; - list = g_list_append(list, g_strdup(value)); + list = g_list_append(list, g_strdup(value)); value = ++pos; } - list = g_list_append(list, g_strdup(value)); + list = g_list_append(list, g_strdup(value)); + + 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 *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, + 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, 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 || code == G_DHCPV6_IA_PD) { + T1 = get_uint32(&value[4]); + T2 = get_uint32(&value[8]); + + if (T1 > T2) + /* IA_NA: RFC 3315, 22.4 */ + /* IA_PD: RFC 3633, ch 9 */ + return NULL; + + pos = 12; + } else + pos = 4; + + if (len <= pos) + return NULL; + + 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, + &option_code, &option_len); + + debug(dhcp_client, "pos %d option %p code %d len %d", + pos, option, option_code, option_len); + + if (!option) + break; + + if (pos >= 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; + + 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 (pos < len); + + if (addr_count > 0 && st == 0) { + /* We only support one address atm */ + char addr_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, 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(addr_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)); + + 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, + uint16_t *status) +{ + GList *list = NULL; + 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 */ + if (len % 16) { + debug(dhcp_client, + "%s server list length (%d) is invalid", + code == G_DHCPV6_DNS_SERVERS ? "DNS" : "SNTP", + len); + return NULL; + } + for (i = 0; i < len; i += 16) { + + str = g_try_malloc0(INET6_ADDRSTRLEN+1); + if (!str) + return list; + + if (!inet_ntop(AF_INET6, &value[i], str, + INET6_ADDRSTRLEN)) + g_free(str); + else + list = g_list_append(list, str); + } + break; + + 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; + } return list; } +static void get_dhcpv6_request(GDHCPClient *dhcp_client, + struct dhcpv6_packet *packet, + uint16_t pkt_len, uint16_t *status) +{ + GList *list, *value_list; + uint8_t *option; + uint16_t code; + uint16_t option_len; + + for (list = dhcp_client->request_list; list; list = list->next) { + code = (uint16_t) GPOINTER_TO_INT(list->data); + + option = dhcpv6_get_option(packet, pkt_len, code, &option_len, + NULL); + if (!option) { + g_hash_table_remove(dhcp_client->code_value_hash, + GINT_TO_POINTER((int) code)); + continue; + } + + value_list = get_dhcpv6_option_value_list(dhcp_client, code, + option_len, option, status); + + debug(dhcp_client, "code %d %p len %d list %p", code, option, + option_len, value_list); + + if (!value_list) + g_hash_table_remove(dhcp_client->code_value_hash, + GINT_TO_POINTER((int) code)); + else + g_hash_table_insert(dhcp_client->code_value_hash, + GINT_TO_POINTER((int) code), value_list); + } +} + static void get_request(GDHCPClient *dhcp_client, struct dhcp_packet *packet) { GDHCPOptionType type; @@ -899,7 +2279,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; @@ -908,12 +2288,15 @@ 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) + g_hash_table_remove(dhcp_client->code_value_hash, + GINT_TO_POINTER((int) code)); - value_list = get_option_value_list(option_value); + value_list = get_option_value_list(option_value, type); 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 @@ -926,67 +2309,180 @@ 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; - uint8_t *message_type, *option_u8; + struct dhcpv6_packet *packet6 = NULL; + 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; + int count; int re; if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) { dhcp_client->listener_watch = 0; +#if defined TIZEN_EXT + /* re-register event listener when socket failed */ + int retry_count = 0; + int ret = -1; + while (retry_count++ < GIO_SOCKET_RETRY_COUNT && ret < 0) + ret = switch_listening_mode(dhcp_client, + dhcp_client->type); +#endif /* defined TIZEN_EXT */ return FALSE; } if (dhcp_client->listen_mode == L_NONE) return FALSE; - if (dhcp_client->listen_mode == L2) - re = dhcp_recv_l2_packet(&packet, dhcp_client->listener_sockfd); - else if (dhcp_client->listen_mode == L3) - re = dhcp_recv_l3_packet(&packet, dhcp_client->listener_sockfd); - else + pkt = &packet; + + dhcp_client->status_code = 0; + + if (dhcp_client->listen_mode == L2) { + re = dhcp_recv_l2_packet(&packet, + 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; + 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) { + ipv4ll_recv_arp_packet(dhcp_client); + return TRUE; + } else re = -EIO; - if (re < 0) - return TRUE; + if (re < 0) + return TRUE; + + 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 || count == 0 || option_len == 0 || + memcmp(dhcp_client->duid, client_id, + dhcp_client->duid_len) != 0) { + debug(dhcp_client, + "client duid error, discarding msg %p/%d/%d", + client_id, option_len, count); + return TRUE; + } - if (check_package_owner(dhcp_client, &packet) == FALSE) - return TRUE; + option = dhcpv6_get_option(packet6, pkt_len, + G_DHCPV6_STATUS_CODE, &option_len, NULL); + 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[2], + option_len - 2); + debug(dhcp_client, "error text: %s", + txt); + g_free(txt); + } + } + dhcp_client->status_code = status; + } + } else { + message_type = dhcp_get_option(&packet, DHCP_MESSAGE_TYPE); + if (!message_type) + return TRUE; + } - message_type = dhcp_get_option(&packet, DHCP_MESSAGE_TYPE); - if (message_type == NULL) - /* No message type option, ignore pakcage */ - return TRUE; + 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); +#if defined TIZEN_EXT + dhcp_client->dhcp_lease_seconds = dhcp_client->lease_seconds; +#endif + get_request(dhcp_client, &packet); switch_listening_mode(dhcp_client, L_NONE); @@ -994,8 +2490,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); @@ -1003,9 +2505,20 @@ 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); + +#if defined TIZEN_EXT + if (dhcp_client->init_reboot) { + g_dhcp_client_set_address_known(dhcp_client, FALSE); + dhcp_client->timeout = g_idle_add_full( + G_PRIORITY_HIGH, + restart_dhcp_timeout, + dhcp_client, + NULL); + break; + } +#endif dhcp_client->timeout = g_timeout_add_seconds_full( G_PRIORITY_HIGH, 3, restart_dhcp_timeout, @@ -1014,10 +2527,183 @@ 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 || 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) + 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 || 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) + dhcp_client->advertise_cb(dhcp_client, + dhcp_client->advertise_data); + return TRUE; + } + + if (dhcp_client->solicitation_cb) { + /* + * 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 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 RELEASE: + case CONFIRM: + case DECLINE: + if (dhcp_client->type != G_DHCP_IPV6) + return TRUE; + + if (packet6->message != DHCPV6_REPLY) + return TRUE; + + count = 0; + option_len = 0; + server_id = dhcpv6_get_option(packet6, pkt_len, + G_DHCPV6_SERVERID, &option_len, &count); + 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)) { + /* RFC 3315, 15.10 */ + debug(dhcp_client, + "server duid error, discarding msg %p/%d/%d", + server_id, option_len, count); + return TRUE; + } + + switch_listening_mode(dhcp_client, L_NONE); + + get_dhcpv6_request(dhcp_client, packet6, pkt_len, + &dhcp_client->status_code); + + if (dhcp_client->information_req_cb) { + /* + * The dhcp_client might not be valid after the + * callback call so just return immediately. + */ + dhcp_client->information_req_cb(dhcp_client, + dhcp_client->information_req_data); + return TRUE; + } + if (dhcp_client->request_cb) { + dhcp_client->request_cb(dhcp_client, + dhcp_client->request_data); + return TRUE; + } + if (dhcp_client->renew_cb) { + dhcp_client->renew_cb(dhcp_client, + dhcp_client->renew_data); + return TRUE; + } + 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; } + debug(dhcp_client, "processed DHCP packet (new state %d)", + dhcp_client->state); + return TRUE; } @@ -1027,20 +2713,227 @@ static gboolean discover_timeout(gpointer user_data) dhcp_client->retry_times++; - g_dhcp_client_start(dhcp_client); + /* + * We do not send the REQUESTED IP option if we are retrying because + * if the server is non-authoritative it will ignore the request if the + * option is present. + */ + g_dhcp_client_start(dhcp_client, NULL); + + 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; + + debug(dhcp_client, "back to MONITOR mode"); + + dhcp_client->conflicts = 0; + dhcp_client->state = IPV4LL_MONITOR; + + return FALSE; +} + +static gboolean ipv4ll_announce_timeout(gpointer dhcp_data) +{ + GDHCPClient *dhcp_client = dhcp_data; + uint32_t ip; + +#if defined TIZEN_EXT + if (!dhcp_client) + return FALSE; +#endif + + debug(dhcp_client, "request timeout (retries %d)", + dhcp_client->retry_times); + + if (dhcp_client->retry_times != ANNOUNCE_NUM) { + dhcp_client->retry_times++; + send_announce_packet(dhcp_client); + return FALSE; + } + + ip = htonl(dhcp_client->requested_ip); + debug(dhcp_client, "switching to monitor mode"); + dhcp_client->state = IPV4LL_MONITOR; + dhcp_client->assigned_ip = get_ip(ip); + + 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; +} + +static gboolean ipv4ll_probe_timeout(gpointer dhcp_data) +{ + + GDHCPClient *dhcp_client = dhcp_data; + + debug(dhcp_client, "IPV4LL probe timeout (retries %d)", + dhcp_client->retry_times); + + if (dhcp_client->retry_times == PROBE_NUM) { + dhcp_client->state = IPV4LL_ANNOUNCE; + dhcp_client->retry_times = 0; + + dhcp_client->retry_times++; + send_announce_packet(dhcp_client); + return FALSE; + } + dhcp_client->retry_times++; + send_probe_packet(dhcp_client); return FALSE; } -int g_dhcp_client_start(GDHCPClient *dhcp_client) +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; + +#if defined TIZEN_EXT + int discover_retry = 0; + int timeout = 0; +#endif + + remove_timeouts(dhcp_client); + + if (dhcp_client->type == G_DHCP_IPV6) { + if (dhcp_client->information_req_cb) { + dhcp_client->state = INFORMATION_REQ; + re = switch_listening_mode(dhcp_client, L3); + if (re != 0) { + switch_listening_mode(dhcp_client, L_NONE); + dhcp_client->state = 0; + 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); + + } else if (dhcp_client->request_cb) { + dhcp_client->state = REQUEST; + 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_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); + if (re != 0) { + switch_listening_mode(dhcp_client, L_NONE); + dhcp_client->state = 0; + return re; + } + send_dhcpv6_renew(dhcp_client); + + } else if (dhcp_client->rebind_cb) { + dhcp_client->state = REBIND; + 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_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 defined TIZEN_EXT + if (g_ascii_strncasecmp(dhcp_client->interface, "wlan", 4) + == 0) { + discover_retry = DISCOVER_RETRIES_WIFI; + timeout = DISCOVER_TIMEOUT_WIFI; + } else { + discover_retry = DISCOVER_RETRIES; + timeout = DISCOVER_TIMEOUT; + } + + debug(dhcp_client, "[DHCPC] Discover retry/total : [%d]/[%d] timeout [%d]", + dhcp_client->retry_times, discover_retry, timeout); +#endif + + + if (dhcp_client->type == G_DHCP_IPV4LL) { + dhcp_client->state = INIT_SELECTING; + ipv4ll_start(dhcp_client); + return 0; + } +#if defined TIZEN_EXT + if (dhcp_client->retry_times == discover_retry) { +#else if (dhcp_client->retry_times == DISCOVER_RETRIES) { - if (dhcp_client->no_lease_cb != NULL) +#endif + if (dhcp_client->no_lease_cb) dhcp_client->no_lease_cb(dhcp_client, - dhcp_client->no_lease_data); - + dhcp_client->no_lease_data); + dhcp_client->retry_times = 0; return 0; } @@ -1053,13 +2946,51 @@ int g_dhcp_client_start(GDHCPClient *dhcp_client) 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 || oldstate == DECLINED) { + addr = 0; + } else { + addr = ntohl(inet_addr(last_address)); + if (addr == 0xFFFFFFFF || ((addr & LINKLOCAL_ADDR) == + LINKLOCAL_ADDR)) { + addr = 0; + } else if (dhcp_client->last_address != last_address) { + g_free(dhcp_client->last_address); + dhcp_client->last_address = g_strdup(last_address); + } } - send_discover(dhcp_client, 0); + 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, +#if defined TIZEN_EXT + timeout, +#else + REQUEST_TIMEOUT, +#endif + reboot_timeout, + dhcp_client, + NULL); + return 0; + } + send_discover(dhcp_client, addr); dhcp_client->timeout = g_timeout_add_seconds_full(G_PRIORITY_HIGH, +#if defined TIZEN_EXT + timeout, +#else DISCOVER_TIMEOUT, +#endif discover_timeout, dhcp_client, NULL); @@ -1076,24 +3007,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, @@ -1113,6 +3040,12 @@ void g_dhcp_client_register_event(GDHCPClient *dhcp_client, dhcp_client->lease_available_cb = func; dhcp_client->lease_available_data = data; return; + case G_DHCP_CLIENT_EVENT_IPV4LL_AVAILABLE: + if (dhcp_client->type == G_DHCP_IPV6) + return; + dhcp_client->ipv4ll_available_cb = func; + dhcp_client->ipv4ll_available_data = data; + return; case G_DHCP_CLIENT_EVENT_NO_LEASE: dhcp_client->no_lease_cb = func; dhcp_client->no_lease_data = data; @@ -1121,10 +3054,70 @@ void g_dhcp_client_register_event(GDHCPClient *dhcp_client, dhcp_client->lease_lost_cb = func; dhcp_client->lease_lost_data = data; return; + case G_DHCP_CLIENT_EVENT_IPV4LL_LOST: + if (dhcp_client->type == G_DHCP_IPV6) + return; + dhcp_client->ipv4ll_lost_cb = func; + dhcp_client->ipv4ll_lost_data = data; + return; case G_DHCP_CLIENT_EVENT_ADDRESS_CONFLICT: dhcp_client->address_conflict_cb = func; dhcp_client->address_conflict_data = data; return; + case G_DHCP_CLIENT_EVENT_INFORMATION_REQ: + 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_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_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_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_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_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; } } @@ -1133,16 +3126,72 @@ 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; + +#if defined TIZEN_EXT + return get_ip(htonl(dhcp_client->server_ip)); +#else + return get_ip(dhcp_client->server_ip); +#endif +} + +#if defined TIZEN_EXT +int g_dhcp_client_get_dhcp_lease_duration(GDHCPClient *dhcp_client) +{ + return dhcp_client->dhcp_lease_seconds; +} +#endif + char *g_dhcp_client_get_address(GDHCPClient *dhcp_client) { return g_strdup(dhcp_client->assigned_ip); } +char *g_dhcp_client_get_netmask(GDHCPClient *dhcp_client) +{ + GList *option = NULL; + + if (dhcp_client->type == G_DHCP_IPV6) + return NULL; + + switch (dhcp_client->state) { + case IPV4LL_DEFEND: + case IPV4LL_MONITOR: + return g_strdup("255.255.0.0"); + case BOUND: + case RENEWING: + case REBINDING: + option = g_dhcp_client_get_option(dhcp_client, G_DHCP_SUBNET); + 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; +} + GDHCPClientError g_dhcp_client_set_request(GDHCPClient *dhcp_client, - unsigned char option_code) + 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))); @@ -1150,45 +3199,77 @@ GDHCPClientError g_dhcp_client_set_request(GDHCPClient *dhcp_client, return G_DHCP_CLIENT_ERROR_NONE; } -static uint8_t *alloc_dhcp_option(int code, const char *str, int extra) +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 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; } -static const char *get_hostname(const char *host) +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) { - char local_host_name[HOST_NAME_MAX + 1]; + 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; - if (g_strcmp0("", host) != 0) - return g_strdup(host); + idbuf[0] = ARPHRD_ETHER; - if (gethostname(local_host_name, HOST_NAME_MAX) != 0) - return NULL; + memcpy(&idbuf[1], dhcp_client->mac_address, maclen); - local_host_name[HOST_NAME_MAX] = 0; + data_option = alloc_dhcp_data_option(option_code, idbuf, idlen); + if (!data_option) + return G_DHCP_CLIENT_ERROR_NOMEM; - return g_strdup(local_host_name); + 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 */ +/* 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; - const char *hostname; - if (option_code == G_DHCP_HOST_NAME) { - hostname = get_hostname(option_value); - - binary_option = alloc_dhcp_option(option_code, hostname, 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); @@ -1197,33 +3278,151 @@ GDHCPClientError g_dhcp_client_set_send(GDHCPClient *dhcp_client, return G_DHCP_CLIENT_ERROR_NONE; } -void g_dhcp_client_ref(GDHCPClient *dhcp_client) +static uint8_t *alloc_dhcpv6_option(uint16_t code, uint8_t *option, + uint16_t len) +{ + uint8_t *storage; + + storage = g_malloc(2 + 2 + len); + if (!storage) + return NULL; + + storage[0] = code >> 8; + storage[1] = code & 0xff; + storage[2] = len >> 8; + storage[3] = len & 0xff; + memcpy(storage + 2 + 2, option, len); + + 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) { + uint8_t *binary_option; + + debug(dhcp_client, "setting option %d to %p len %d", + option_code, option_value, option_len); + + binary_option = alloc_dhcpv6_option(option_code, option_value, + option_len); + 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_request(GDHCPClient *dhcp_client) +{ + if (!dhcp_client || dhcp_client->type != G_DHCP_IPV6) + return; + + dhcp_client->last_request = time(NULL); +} + +uint16_t g_dhcpv6_client_get_status(GDHCPClient *dhcp_client) +{ + if (!dhcp_client || dhcp_client->type != G_DHCP_IPV6) + return 0; + + return dhcp_client->status_code; +} + +GDHCPClient *g_dhcp_client_ref(GDHCPClient *dhcp_client) { - g_atomic_int_inc(&dhcp_client->ref_count); + if (!dhcp_client) + return NULL; + + __sync_fetch_and_add(&dhcp_client->ref_count, 1); + + return dhcp_client; } void g_dhcp_client_unref(GDHCPClient *dhcp_client) { - if (g_atomic_int_dec_and_test(&dhcp_client->ref_count) == FALSE) + if (!dhcp_client) + return; + + if (__sync_fetch_and_sub(&dhcp_client->ref_count, 1) != 1) return; g_dhcp_client_stop(dhcp_client); g_free(dhcp_client->interface); 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); 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); +#if defined TIZEN_EXT + dhcp_client = NULL; +#endif } void g_dhcp_client_set_debug(GDHCPClient *dhcp_client, - GDHCPDebugFunc func, gpointer data) + GDHCPDebugFunc func, gpointer user_data) { + if (!dhcp_client) + return; + dhcp_client->debug_func = func; - dhcp_client->debug_data = data; + 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; } + +#if defined TIZEN_EXT +void g_dhcp_client_set_address_known(GDHCPClient *dhcp_client, gboolean known) +{ + /* DHCPREQUEST during INIT-REBOOT state (rfc2131) + * 4.4.3 Initialization with known network address + * 4.3.2 DHCPREQUEST generated during INIT-REBOOT state + */ + debug(dhcp_client, "known network address (%d)", known); + + if (dhcp_client->init_reboot == known) + return; + + dhcp_client->init_reboot = known; +} +#endif