|  | /* | 
|  | * | 
|  | *  DHCP client library with GLib integration | 
|  | * | 
|  | *  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 | 
|  | *  published by the Free Software Foundation. | 
|  | * | 
|  | *  This program is distributed in the hope that it will be useful, | 
|  | *  but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|  | *  GNU General Public License for more details. | 
|  | * | 
|  | *  You should have received a copy of the GNU General Public License | 
|  | *  along with this program; if not, write to the Free Software | 
|  | *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | 
|  | * | 
|  | */ | 
|  |  | 
|  | #ifdef HAVE_CONFIG_H | 
|  | #include <config.h> | 
|  | #endif | 
|  |  | 
|  | #define _GNU_SOURCE | 
|  | #include <stdio.h> | 
|  | #include <errno.h> | 
|  | #include <unistd.h> | 
|  | #include <stdlib.h> | 
|  | #include <string.h> | 
|  | #include <sys/ioctl.h> | 
|  | #include <arpa/inet.h> | 
|  | #include <sys/time.h> | 
|  | #include <sys/timerfd.h> | 
|  | #include <resolv.h> | 
|  |  | 
|  | #include <netpacket/packet.h> | 
|  | #include <netinet/if_ether.h> | 
|  | #include <net/ethernet.h> | 
|  |  | 
|  | #include <linux/if.h> | 
|  | #include <linux/filter.h> | 
|  |  | 
|  | #include <glib.h> | 
|  |  | 
|  | #include "gdhcp.h" | 
|  | #include "common.h" | 
|  | #include "ipv4ll.h" | 
|  | #include "timer.h" | 
|  |  | 
|  | #define DISCOVER_TIMEOUT 10 | 
|  | #define DISCOVER_RETRIES 6 | 
|  |  | 
|  | #define REQUEST_TIMEOUT 5 | 
|  | #define REQUEST_RETRIES 3 | 
|  |  | 
|  | typedef enum _listen_mode { | 
|  | L_NONE, | 
|  | L2, | 
|  | L3, | 
|  | L_ARP, | 
|  | } ListenMode; | 
|  |  | 
|  | typedef enum _dhcp_client_state { | 
|  | INIT_SELECTING, | 
|  | REBOOTING, | 
|  | REQUESTING, | 
|  | BOUND, | 
|  | RENEWING, | 
|  | REBINDING, | 
|  | RELEASED, | 
|  | IPV4LL_PROBE, | 
|  | IPV4LL_ANNOUNCE, | 
|  | IPV4LL_MONITOR, | 
|  | IPV4LL_DEFEND, | 
|  | INFORMATION_REQ, | 
|  | SOLICITATION, | 
|  | REQUEST, | 
|  | CONFIRM, | 
|  | RENEW, | 
|  | REBIND, | 
|  | RELEASE, | 
|  | DECLINE, | 
|  | } ClientState; | 
|  |  | 
|  | struct _GDHCPClient { | 
|  | int ref_count; | 
|  | GDHCPType type; | 
|  | ClientState state; | 
|  | int ifindex; | 
|  | char *interface; | 
|  | uint8_t mac_address[6]; | 
|  | uint32_t xid; | 
|  | 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; | 
|  | GList *require_list; | 
|  | GList *request_list; | 
|  | GHashTable *code_value_hash; | 
|  | GHashTable *send_value_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 wake_event_cb; | 
|  | gpointer wake_event_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; | 
|  | uint32_t next_event; | 
|  | int can_sleep; | 
|  | 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; | 
|  | }; | 
|  |  | 
|  | 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); | 
|  | } | 
|  |  | 
|  | static void set_wake(GDHCPClient *dhcp_client, uint32_t timeout) | 
|  | { | 
|  | debug(dhcp_client, "Stay awake for %d", timeout); | 
|  |  | 
|  | dhcp_client->next_event = timeout + time(NULL); | 
|  | dhcp_client->can_sleep = FALSE; | 
|  | if (dhcp_client->wake_event_cb) | 
|  | dhcp_client->wake_event_cb(dhcp_client, | 
|  | dhcp_client->wake_event_data); | 
|  | } | 
|  |  | 
|  | static void release_wake(GDHCPClient *dhcp_client, uint32_t timeout) | 
|  | { | 
|  | debug(dhcp_client, "may sleep for %d", timeout); | 
|  | dhcp_client->next_event = timeout + time(NULL); | 
|  | dhcp_client->can_sleep = TRUE; | 
|  | if (dhcp_client->wake_event_cb) | 
|  | dhcp_client->wake_event_cb(dhcp_client, | 
|  | dhcp_client->wake_event_data); | 
|  | } | 
|  |  | 
|  | int g_dhcp_get_next_event(GDHCPClient *dhcp_client, time_t *next_event) | 
|  | { | 
|  | *next_event = dhcp_client->next_event; | 
|  | return dhcp_client->can_sleep; | 
|  | } | 
|  |  | 
|  | /* Initialize the packet with the proper defaults */ | 
|  | static void init_packet(GDHCPClient *dhcp_client, gpointer pkt, char type) | 
|  | { | 
|  | if (dhcp_client->type == G_DHCP_IPV6) | 
|  | dhcpv6_init_header(pkt, type); | 
|  | else { | 
|  | struct dhcp_packet *packet = pkt; | 
|  |  | 
|  | dhcp_init_header(packet, type); | 
|  | memcpy(packet->chaddr, dhcp_client->mac_address, 6); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void add_request_options(GDHCPClient *dhcp_client, | 
|  | struct dhcp_packet *packet) | 
|  | { | 
|  | int len = 0; | 
|  | GList *list; | 
|  | uint8_t code; | 
|  | int end = dhcp_end_option(packet->options); | 
|  |  | 
|  | for (list = dhcp_client->request_list; list; list = list->next) { | 
|  | code = (uint8_t) GPOINTER_TO_INT(list->data); | 
|  |  | 
|  | packet->options[end + OPT_DATA + len] = code; | 
|  | len++; | 
|  | } | 
|  |  | 
|  | if (len) { | 
|  | packet->options[end + OPT_CODE] = DHCP_PARAM_REQ; | 
|  | packet->options[end + OPT_LEN] = len; | 
|  | packet->options[end + OPT_DATA + len] = DHCP_END; | 
|  | } | 
|  | } | 
|  |  | 
|  | 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_rttimeout_source_remove(dhcp_client->timeout); | 
|  | if (dhcp_client->t1_timeout > 0) | 
|  | g_rttimeout_source_remove(dhcp_client->t1_timeout); | 
|  | if (dhcp_client->t2_timeout > 0) | 
|  | g_rttimeout_source_remove(dhcp_client->t2_timeout); | 
|  | if (dhcp_client->lease_timeout > 0) | 
|  | g_rttimeout_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; | 
|  | struct dhcp_packet *packet = user_data; | 
|  |  | 
|  | dhcp_add_binary_option(packet, option); | 
|  | } | 
|  |  | 
|  | static void add_send_options(GDHCPClient *dhcp_client, | 
|  | struct dhcp_packet *packet) | 
|  | { | 
|  | g_hash_table_foreach(dhcp_client->send_value_hash, | 
|  | add_binary_option, packet); | 
|  | } | 
|  |  | 
|  | static const char *get_message_type(uint8_t type) | 
|  | { | 
|  | switch (type) { | 
|  | case DHCPDISCOVER: | 
|  | return "DISCOVER"; | 
|  | case DHCPOFFER: | 
|  | return "OFFER"; | 
|  | case DHCPREQUEST: | 
|  | return "REQUEST"; | 
|  | case DHCPDECLINE: | 
|  | return "DECLINE"; | 
|  | case DHCPACK: | 
|  | return "ACK"; | 
|  | case DHCPNAK: | 
|  | return "NAK"; | 
|  | case DHCPRELEASE: | 
|  | return "RELEASE"; | 
|  | case DHCPINFORM: | 
|  | return "INFORM"; | 
|  | default: | 
|  | return ""; | 
|  | } | 
|  | } | 
|  | /* | 
|  | * 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) | 
|  | { | 
|  | const uint8_t type = DHCPDISCOVER; | 
|  | struct dhcp_packet packet; | 
|  | struct in_addr dest; | 
|  | char destbuf[INET_ADDRSTRLEN]; | 
|  |  | 
|  | init_packet(dhcp_client, &packet, type); | 
|  |  | 
|  | packet.xid = dhcp_client->xid; | 
|  | packet.secs = dhcp_attempt_secs(dhcp_client); | 
|  |  | 
|  | if (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_option_uint16(&packet, DHCP_MAX_SIZE, 576); | 
|  |  | 
|  | add_request_options(dhcp_client, &packet); | 
|  |  | 
|  | add_send_options(dhcp_client, &packet); | 
|  |  | 
|  | dest.s_addr = INADDR_BROADCAST; | 
|  |  | 
|  | debug(dhcp_client, "%s on %s to %s port %d interval %d", | 
|  | get_message_type(type), | 
|  | dhcp_client->interface, | 
|  | inet_ntop(AF_INET, &dest, destbuf, sizeof(destbuf)), | 
|  | SERVER_PORT, | 
|  | DISCOVER_TIMEOUT); | 
|  |  | 
|  | set_wake(dhcp_client, DISCOVER_TIMEOUT); | 
|  |  | 
|  | return dhcp_send_raw_packet(&packet, INADDR_ANY, CLIENT_PORT, | 
|  | dest.s_addr, SERVER_PORT, | 
|  | MAC_BCAST_ADDR, dhcp_client->ifindex, | 
|  | dhcp_client->retry_times % 2); | 
|  | } | 
|  |  | 
|  | static int send_request(GDHCPClient *dhcp_client) | 
|  | { | 
|  | const uint8_t type = DHCPREQUEST; | 
|  | struct dhcp_packet packet; | 
|  | struct in_addr rlog, dlog; | 
|  | char requestbuf[INET_ADDRSTRLEN], destbuf[INET_ADDRSTRLEN]; | 
|  |  | 
|  | debug(dhcp_client, "sending DHCP request (state %d)", | 
|  | dhcp_client->state); | 
|  |  | 
|  | init_packet(dhcp_client, &packet, type); | 
|  |  | 
|  | packet.xid = dhcp_client->xid; | 
|  | packet.secs = dhcp_attempt_secs(dhcp_client); | 
|  |  | 
|  | if (dhcp_client->state == REQUESTING || dhcp_client->state == REBOOTING) | 
|  | dhcp_add_option_uint32(&packet, DHCP_REQUESTED_IP, | 
|  | dhcp_client->requested_ip); | 
|  |  | 
|  | if (dhcp_client->state == REQUESTING) | 
|  | dhcp_add_option_uint32(&packet, DHCP_SERVER_ID, | 
|  | dhcp_client->server_ip); | 
|  |  | 
|  | dhcp_add_option_uint16(&packet, DHCP_MAX_SIZE, 576); | 
|  |  | 
|  | add_request_options(dhcp_client, &packet); | 
|  |  | 
|  | add_send_options(dhcp_client, &packet); | 
|  |  | 
|  | rlog.s_addr = htonl(dhcp_client->requested_ip); | 
|  | dlog.s_addr = INADDR_BROADCAST; | 
|  | debug(dhcp_client, "%s of %s on %s to %s port %d interval %d", | 
|  | get_message_type(type), | 
|  | inet_ntop(AF_INET, &rlog, requestbuf, sizeof(requestbuf)), | 
|  | dhcp_client->interface, | 
|  | inet_ntop(AF_INET, &dlog, destbuf, sizeof(destbuf)), | 
|  | SERVER_PORT, | 
|  | REQUEST_TIMEOUT); | 
|  |  | 
|  | if (dhcp_client->state == RENEWING || dhcp_client->state == REBINDING) | 
|  | packet.ciaddr = htonl(dhcp_client->requested_ip); | 
|  |  | 
|  | set_wake(dhcp_client, REQUEST_TIMEOUT); | 
|  |  | 
|  | 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->listener_sockfd); | 
|  |  | 
|  | return dhcp_send_raw_packet(&packet, INADDR_ANY, CLIENT_PORT, | 
|  | 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) | 
|  | { | 
|  | const uint8_t type = DHCPRELEASE; | 
|  | struct dhcp_packet packet; | 
|  | uint64_t rand; | 
|  | struct in_addr rlog, dlog; | 
|  | char releasebuf[INET_ADDRSTRLEN], destbuf[INET_ADDRSTRLEN]; | 
|  |  | 
|  | debug(dhcp_client, "sending DHCP release request"); | 
|  |  | 
|  | init_packet(dhcp_client, &packet, DHCPRELEASE); | 
|  | dhcp_get_random(&rand); | 
|  | packet.xid = rand; | 
|  | packet.ciaddr = htonl(ciaddr); | 
|  |  | 
|  | dhcp_add_option_uint32(&packet, DHCP_SERVER_ID, server); | 
|  |  | 
|  | rlog.s_addr = htonl(ciaddr); | 
|  | dlog.s_addr = htonl(server); | 
|  | debug(dhcp_client, "%s of %s on %s to %s port %d", | 
|  | get_message_type(type), | 
|  | inet_ntop(AF_INET, &rlog, releasebuf, sizeof(releasebuf)), | 
|  | dhcp_client->interface, | 
|  | inet_ntop(AF_INET, &dlog, destbuf, sizeof(destbuf)), | 
|  | SERVER_PORT); | 
|  |  | 
|  | return dhcp_send_kernel_packet(&packet, ciaddr, CLIENT_PORT, | 
|  | server, SERVER_PORT, | 
|  | dhcp_client->listener_sockfd); | 
|  | } | 
|  |  | 
|  | 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) | 
|  | { | 
|  | GDHCPClient *dhcp_client; | 
|  | guint timeout; | 
|  |  | 
|  | 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 = ipv4ll_random_ip(); | 
|  | } | 
|  |  | 
|  | debug(dhcp_client, "sending IPV4LL probe request"); | 
|  |  | 
|  | if (dhcp_client->retry_times == 1) { | 
|  | dhcp_client->state = IPV4LL_PROBE; | 
|  | switch_listening_mode(dhcp_client, L_ARP); | 
|  | } | 
|  | ipv4ll_send_arp_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 += PROBE_MIN*1000; | 
|  | } else | 
|  | timeout = (ANNOUNCE_WAIT * 1000); | 
|  |  | 
|  | dhcp_client->timeout = g_rttimeout_add_full(CLOCK_BOOTTIME_ALARM, G_PRIORITY_HIGH, | 
|  | timeout, | 
|  | ipv4ll_probe_timeout, | 
|  | dhcp_client, | 
|  | NULL); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | 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"); | 
|  |  | 
|  | ipv4ll_send_arp_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_rttimeout_add_seconds_full(CLOCK_BOOTTIME_ALARM, G_PRIORITY_HIGH, | 
|  | DEFEND_INTERVAL, | 
|  | ipv4ll_defend_timeout, | 
|  | dhcp_client, | 
|  | NULL); | 
|  | return TRUE; | 
|  | } else | 
|  | dhcp_client->timeout = | 
|  | g_rttimeout_add_seconds_full(CLOCK_BOOTTIME_ALARM, G_PRIORITY_HIGH, | 
|  | ANNOUNCE_INTERVAL, | 
|  | ipv4ll_announce_timeout, | 
|  | dhcp_client, | 
|  | NULL); | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | static void get_interface_mac_address(int index, uint8_t *mac_address) | 
|  | { | 
|  | struct ifreq ifr; | 
|  | int sk, err; | 
|  |  | 
|  | sk = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); | 
|  | if (sk < 0) { | 
|  | perror("Open socket error"); | 
|  | 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; | 
|  | } | 
|  |  | 
|  | memcpy(mac_address, ifr.ifr_hwaddr.sa_data, 6); | 
|  |  | 
|  | done: | 
|  | close(sk); | 
|  | } | 
|  |  | 
|  | 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; | 
|  | 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; | 
|  | get_interface_mac_address(index, &(*duid)[2 + 2]); | 
|  | (*duid)[2] = 0; | 
|  | (*duid)[3] = type; | 
|  | break; | 
|  | } | 
|  |  | 
|  | 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 || dhcp_client->type != G_DHCP_IPV6) | 
|  | return -EINVAL; | 
|  |  | 
|  | g_free(dhcp_client->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 || 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); | 
|  |  | 
|  | 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->T1; | 
|  |  | 
|  | if (T2) | 
|  | *T2 = dhcp_client->T2; | 
|  |  | 
|  | if (started) | 
|  | *started = dhcp_client->last_request; | 
|  |  | 
|  | if (expire) | 
|  | *expire = 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; | 
|  | } | 
|  |  | 
|  | 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)); | 
|  | } | 
|  |  | 
|  | } 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); | 
|  | } | 
|  |  | 
|  | 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); | 
|  |  | 
|  | debug(dhcp_client, "sending DHCPv6 %s message", msg); | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | 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"); | 
|  | } | 
|  |  | 
|  | 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) | 
|  | { | 
|  | char *value = data; | 
|  | g_free(value); | 
|  | } | 
|  |  | 
|  | 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, | 
|  | int ifindex, GDHCPClientError *error) | 
|  | { | 
|  | GDHCPClient *dhcp_client; | 
|  |  | 
|  | if (ifindex < 0) { | 
|  | *error = G_DHCP_CLIENT_ERROR_INVALID_INDEX; | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | dhcp_client = g_try_new0(GDHCPClient, 1); | 
|  | if (!dhcp_client) { | 
|  | *error = G_DHCP_CLIENT_ERROR_NOMEM; | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | dhcp_client->interface = get_interface_name(ifindex); | 
|  | if (!dhcp_client->interface) { | 
|  | *error = G_DHCP_CLIENT_ERROR_INTERFACE_UNAVAILABLE; | 
|  | goto error; | 
|  | } | 
|  |  | 
|  | if (!interface_is_up(ifindex)) { | 
|  | *error = G_DHCP_CLIENT_ERROR_INTERFACE_DOWN; | 
|  | goto error; | 
|  | } | 
|  |  | 
|  | get_interface_mac_address(ifindex, dhcp_client->mac_address); | 
|  |  | 
|  | dhcp_client->listener_sockfd = -1; | 
|  | 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; | 
|  | dhcp_client->ack_retry_times = 0; | 
|  | dhcp_client->code_value_hash = g_hash_table_new_full(g_direct_hash, | 
|  | 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->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; | 
|  |  | 
|  | return dhcp_client; | 
|  |  | 
|  | error: | 
|  | g_free(dhcp_client->interface); | 
|  | g_free(dhcp_client); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | #define SERVER_AND_CLIENT_PORTS  ((67 << 16) + 68) | 
|  |  | 
|  | static int dhcp_l2_socket(int ifindex) | 
|  | { | 
|  | int fd; | 
|  | struct sockaddr_ll sock; | 
|  |  | 
|  | /* | 
|  | * Comment: | 
|  | * | 
|  | *	I've selected not to see LL header, so BPF doesn't see it, too. | 
|  | *	The filter may also pass non-IP and non-ARP packets, but we do | 
|  | *	a more complete check when receiving the message in userspace. | 
|  | * | 
|  | * and filter shamelessly stolen from: | 
|  | * | 
|  | *	http://www.flamewarmaster.de/software/dhcpclient/ | 
|  | * | 
|  | * There are a few other interesting ideas on that page (look under | 
|  | * "Motivation").  Use of netlink events is most interesting.  Think | 
|  | * of various network servers listening for events and reconfiguring. | 
|  | * That would obsolete sending HUP signals and/or make use of restarts. | 
|  | * | 
|  | * Copyright: 2006, 2007 Stefan Rompf <sux@loplof.de>. | 
|  | * License: GPL v2. | 
|  | * | 
|  | * TODO: make conditional? | 
|  | */ | 
|  | static const struct sock_filter filter_instr[] = { | 
|  | /* check for udp */ | 
|  | BPF_STMT(BPF_LD|BPF_B|BPF_ABS, 9), | 
|  | /* L5, L1, is UDP? */ | 
|  | BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, IPPROTO_UDP, 2, 0), | 
|  | /* ugly check for arp on ethernet-like and IPv4 */ | 
|  | BPF_STMT(BPF_LD|BPF_W|BPF_ABS, 2), /* L1: */ | 
|  | BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, 0x08000604, 3, 4),/* L3, L4 */ | 
|  | /* skip IP header */ | 
|  | BPF_STMT(BPF_LDX|BPF_B|BPF_MSH, 0), /* L5: */ | 
|  | /* check udp source and destination ports */ | 
|  | BPF_STMT(BPF_LD|BPF_W|BPF_IND, 0), | 
|  | /* L3, L4 */ | 
|  | BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, SERVER_AND_CLIENT_PORTS, 0, 1), | 
|  | /* returns */ | 
|  | BPF_STMT(BPF_RET|BPF_K, 0x0fffffff), /* L3: pass */ | 
|  | BPF_STMT(BPF_RET|BPF_K, 0), /* L4: reject */ | 
|  | }; | 
|  |  | 
|  | static const struct sock_fprog filter_prog = { | 
|  | .len = sizeof(filter_instr) / sizeof(filter_instr[0]), | 
|  | /* casting const away: */ | 
|  | .filter = (struct sock_filter *) filter_instr, | 
|  | }; | 
|  |  | 
|  | fd = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, htons(ETH_P_IP)); | 
|  | if (fd < 0) | 
|  | 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 err; | 
|  | } | 
|  |  | 
|  | return fd; | 
|  | } | 
|  |  | 
|  | static bool sanity_check(struct ip_udp_dhcp_packet *packet, int bytes) | 
|  | { | 
|  | if (packet->ip.protocol != IPPROTO_UDP) | 
|  | return false; | 
|  |  | 
|  | if (packet->ip.version != IPVERSION) | 
|  | return false; | 
|  |  | 
|  | if (packet->ip.ihl != sizeof(packet->ip) >> 2) | 
|  | return false; | 
|  |  | 
|  | if (packet->udp.dest != htons(CLIENT_PORT)) | 
|  | return false; | 
|  |  | 
|  | if (ntohs(packet->udp.len) != (uint16_t)(bytes - sizeof(packet->ip))) | 
|  | return false; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | 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; | 
|  | uint16_t check; | 
|  |  | 
|  | memset(&packet, 0, sizeof(packet)); | 
|  |  | 
|  | bytes = read(fd, &packet, sizeof(packet)); | 
|  | if (bytes < 0) | 
|  | return -1; | 
|  |  | 
|  | if (bytes < (int) (sizeof(packet.ip) + sizeof(packet.udp))) | 
|  | return -1; | 
|  |  | 
|  | if (bytes < ntohs(packet.ip.tot_len)) | 
|  | /* packet is bigger than sizeof(packet), we did partial read */ | 
|  | return -1; | 
|  |  | 
|  | /* 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 = ipv4ll_random_ip(); | 
|  |  | 
|  | /*first wait a random delay to avoid storm of arp request on boot*/ | 
|  | timeout = ipv4ll_random_delay_ms(PROBE_WAIT); | 
|  |  | 
|  | dhcp_client->retry_times++; | 
|  | dhcp_client->timeout = g_rttimeout_add_full(CLOCK_BOOTTIME_ALARM, 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; | 
|  |  | 
|  | 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; | 
|  |  | 
|  | 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++; | 
|  |  | 
|  | debug(dhcp_client, "IPV4LL conflict detected"); | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | 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); | 
|  | } | 
|  |  | 
|  | ipv4ll_stop(dhcp_client); | 
|  |  | 
|  | if (dhcp_client->conflicts < MAX_CONFLICTS) { | 
|  | /*restart whole state machine*/ | 
|  | dhcp_client->retry_times++; | 
|  | dhcp_client->timeout = | 
|  | g_rttimeout_add_full(CLOCK_BOOTTIME_ALARM, 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 | 
|  | * to wait RATE_LIMIT_INTERVAL before retrying, | 
|  | * but we just report failure. | 
|  | */ | 
|  | else if (dhcp_client->no_lease_cb) | 
|  | dhcp_client->no_lease_cb(dhcp_client, | 
|  | dhcp_client->no_lease_data); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | 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) | 
|  | return false; | 
|  |  | 
|  | xid = packet6->transaction_id[0] << 16 | | 
|  | packet6->transaction_id[1] << 8 | | 
|  | packet6->transaction_id[2]; | 
|  |  | 
|  | 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); | 
|  |  | 
|  | static gboolean request_timeout(gpointer user_data) | 
|  | { | 
|  | GDHCPClient *dhcp_client = user_data; | 
|  |  | 
|  | debug(dhcp_client, "request timeout (retries %d)", | 
|  | dhcp_client->retry_times); | 
|  |  | 
|  | dhcp_client->retry_times++; | 
|  |  | 
|  | start_request(dhcp_client); | 
|  |  | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | static gboolean listener_event(GIOChannel *channel, GIOCondition condition, | 
|  | gpointer user_data); | 
|  |  | 
|  | static int switch_listening_mode(GDHCPClient *dhcp_client, | 
|  | ListenMode listen_mode) | 
|  | { | 
|  | GIOChannel *listener_channel; | 
|  | int listener_sockfd; | 
|  |  | 
|  | 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) { | 
|  | 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; | 
|  | } | 
|  |  | 
|  | if (listen_mode == L_NONE) | 
|  | return 0; | 
|  |  | 
|  | if (listen_mode == L2) | 
|  | listener_sockfd = dhcp_l2_socket(dhcp_client->ifindex); | 
|  | 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 = ipv4ll_arp_socket(dhcp_client->ifindex); | 
|  | else | 
|  | return -EIO; | 
|  |  | 
|  | if (listener_sockfd < 0) | 
|  | return -EIO; | 
|  |  | 
|  | listener_channel = g_io_channel_unix_new(listener_sockfd); | 
|  | if (!listener_channel) { | 
|  | /* Failed to create listener channel */ | 
|  | close(listener_sockfd); | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | dhcp_client->listen_mode = listen_mode; | 
|  | dhcp_client->listener_sockfd = listener_sockfd; | 
|  |  | 
|  | 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(listener_channel); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void start_request(GDHCPClient *dhcp_client) | 
|  | { | 
|  | debug(dhcp_client, "start request (retries %d)", | 
|  | dhcp_client->retry_times); | 
|  |  | 
|  | 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); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (dhcp_client->retry_times == 0) { | 
|  | dhcp_client->state = REQUESTING; | 
|  | switch_listening_mode(dhcp_client, L2); | 
|  | } | 
|  |  | 
|  | send_request(dhcp_client); | 
|  |  | 
|  | dhcp_client->timeout = g_rttimeout_add_seconds_full(CLOCK_BOOTTIME_ALARM, G_PRIORITY_HIGH, | 
|  | REQUEST_TIMEOUT, | 
|  | request_timeout, | 
|  | dhcp_client, | 
|  | NULL); | 
|  | } | 
|  | #define MINIMUM_LEASE_DURATION 3600 | 
|  | #define MAXIMUM_LEASE_DURATION (30*24*60*60) | 
|  | static uint32_t get_lease(GDHCPClient *dhcp_client, struct dhcp_packet *packet) | 
|  | { | 
|  | uint8_t *option; | 
|  | uint32_t lease_seconds; | 
|  |  | 
|  | option = dhcp_get_option(packet, DHCP_LEASE_TIME); | 
|  | if (!option) | 
|  | return 3600; | 
|  |  | 
|  | lease_seconds = get_be32(option); | 
|  | if (lease_seconds < MINIMUM_LEASE_DURATION) | 
|  | { | 
|  | debug(dhcp_client, "actual lease %d, new lease %d", lease_seconds, MINIMUM_LEASE_DURATION); | 
|  | lease_seconds = MINIMUM_LEASE_DURATION; | 
|  | } | 
|  |  | 
|  | if (lease_seconds > MAXIMUM_LEASE_DURATION) | 
|  | { | 
|  | debug(dhcp_client, "actual lease %d, new lease %d", lease_seconds, MAXIMUM_LEASE_DURATION); | 
|  | lease_seconds = MAXIMUM_LEASE_DURATION; | 
|  | } | 
|  |  | 
|  | return lease_seconds; | 
|  | } | 
|  |  | 
|  | static void restart_dhcp(GDHCPClient *dhcp_client, int retry_times) | 
|  | { | 
|  | 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, dhcp_client->last_address); | 
|  | } | 
|  |  | 
|  | static gboolean start_expire(gpointer user_data) | 
|  | { | 
|  | GDHCPClient *dhcp_client = user_data; | 
|  |  | 
|  | debug(dhcp_client, "lease expired"); | 
|  |  | 
|  | /*remove all timeouts if they are set*/ | 
|  | remove_timeouts(dhcp_client); | 
|  |  | 
|  | restart_dhcp(dhcp_client, 0); | 
|  |  | 
|  | /* ip need to be cleared */ | 
|  | if (dhcp_client->lease_lost_cb) | 
|  | dhcp_client->lease_lost_cb(dhcp_client, | 
|  | dhcp_client->lease_lost_data); | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | 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); | 
|  |  | 
|  | /*recalculate remaining rebind time*/ | 
|  | dhcp_client->T2 >>= 1; | 
|  | if (dhcp_client->T2 > 60) { | 
|  | dhcp_get_random(&rand); | 
|  | dhcp_client->t2_timeout = | 
|  | g_rttimeout_add_full(CLOCK_BOOTTIME_ALARM, G_PRIORITY_HIGH, | 
|  | dhcp_client->T2 * 1000 + (rand % 2000) - 1000, | 
|  | continue_rebound, | 
|  | dhcp_client, | 
|  | NULL); | 
|  | } | 
|  | set_wake(dhcp_client, REQUEST_TIMEOUT); | 
|  |  | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | 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; | 
|  |  | 
|  | /*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 continue_renew (gpointer user_data) | 
|  | { | 
|  | GDHCPClient *dhcp_client = user_data; | 
|  | uint64_t rand; | 
|  |  | 
|  | switch_listening_mode(dhcp_client, L3); | 
|  | send_request(dhcp_client); | 
|  |  | 
|  | if (dhcp_client->t1_timeout > 0) | 
|  | g_source_remove(dhcp_client->t1_timeout); | 
|  |  | 
|  | dhcp_client->t1_timeout = 0; | 
|  |  | 
|  | dhcp_client->T1 >>= 1; | 
|  |  | 
|  | if (dhcp_client->T1 > 60) { | 
|  | dhcp_get_random(&rand); | 
|  | dhcp_client->t1_timeout = g_rttimeout_add_full(CLOCK_BOOTTIME_ALARM, G_PRIORITY_HIGH, | 
|  | dhcp_client->T1 * 1000 + (rand % 2000) - 1000, | 
|  | continue_renew, | 
|  | dhcp_client, | 
|  | NULL); | 
|  | } | 
|  | set_wake(dhcp_client, REQUEST_TIMEOUT); | 
|  |  | 
|  | 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, "bound to %s -- renewal in %ld seconds.", | 
|  | dhcp_client->assigned_ip, | 
|  | dhcp_client->lease_seconds); | 
|  |  | 
|  | dhcp_client->state = BOUND; | 
|  |  | 
|  | remove_timeouts(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_rttimeout_add_seconds_full(CLOCK_BOOTTIME_ALARM, | 
|  | G_PRIORITY_HIGH, | 
|  | dhcp_client->T1, | 
|  | start_renew, dhcp_client, | 
|  | NULL); | 
|  |  | 
|  | dhcp_client->t2_timeout = g_rttimeout_add_seconds_full(CLOCK_BOOTTIME_ALARM, G_PRIORITY_HIGH, | 
|  | dhcp_client->T2, | 
|  | start_rebound, dhcp_client, | 
|  | NULL); | 
|  |  | 
|  | dhcp_client->lease_timeout= g_rttimeout_add_seconds_full(CLOCK_BOOTTIME_ALARM, G_PRIORITY_HIGH, | 
|  | dhcp_client->expire, | 
|  | start_expire, dhcp_client, | 
|  | NULL); | 
|  | release_wake(dhcp_client, dhcp_client->T1); | 
|  | } | 
|  |  | 
|  | static gboolean restart_dhcp_timeout(gpointer user_data) | 
|  | { | 
|  | GDHCPClient *dhcp_client = user_data; | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | static char *get_ip(uint32_t ip) | 
|  | { | 
|  | struct in_addr addr; | 
|  |  | 
|  | addr.s_addr = ip; | 
|  |  | 
|  | return g_strdup(inet_ntoa(addr)); | 
|  | } | 
|  |  | 
|  | /* get a rough idea of how long an option will be */ | 
|  | static const uint8_t len_of_option_as_string[] = { | 
|  | [OPTION_IP] = sizeof("255.255.255.255 "), | 
|  | [OPTION_STRING] = 1, | 
|  | [OPTION_U8] = sizeof("255 "), | 
|  | [OPTION_U16] = sizeof("65535 "), | 
|  | [OPTION_U32] = sizeof("4294967295 "), | 
|  | }; | 
|  |  | 
|  | static int sprint_nip(char *dest, const char *pre, const uint8_t *ip) | 
|  | { | 
|  | return sprintf(dest, "%s%u.%u.%u.%u", pre, ip[0], ip[1], ip[2], ip[3]); | 
|  | } | 
|  |  | 
|  | /* Create "opt_value1 option_value2 ..." string */ | 
|  | static char *malloc_option_value_string(uint8_t *option, GDHCPOptionType type) | 
|  | { | 
|  | unsigned upper_length; | 
|  | int len, optlen; | 
|  | char *dest, *ret; | 
|  |  | 
|  | 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 = g_malloc(upper_length + 1); | 
|  | if (!ret) | 
|  | return NULL; | 
|  |  | 
|  | while (len >= optlen) { | 
|  | switch (type) { | 
|  | case OPTION_IP: | 
|  | dest += sprint_nip(dest, "", option); | 
|  | break; | 
|  | case OPTION_U16: { | 
|  | uint16_t val_u16 = get_be16(option); | 
|  | dest += sprintf(dest, "%u", val_u16); | 
|  | break; | 
|  | } | 
|  | case OPTION_U32: { | 
|  | uint32_t val_u32 = get_be32(option); | 
|  | dest += sprintf(dest, "%u", val_u32); | 
|  | break; | 
|  | } | 
|  | case OPTION_STRING: | 
|  | memcpy(dest, option, len); | 
|  | dest[len] = '\0'; | 
|  | return ret; | 
|  | default: | 
|  | break; | 
|  | } | 
|  | option += optlen; | 
|  | len -= optlen; | 
|  | if (len <= 0) | 
|  | break; | 
|  | *dest++ = ' '; | 
|  | *dest = '\0'; | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static GList *get_option_value_list(char *value, GDHCPOptionType type) | 
|  | { | 
|  | char *pos = value; | 
|  | GList *list = 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)); | 
|  |  | 
|  | value = ++pos; | 
|  | } | 
|  |  | 
|  | 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; | 
|  | GList *list, *value_list; | 
|  | char *option_value; | 
|  | uint8_t *option; | 
|  | uint8_t code; | 
|  |  | 
|  | for (list = dhcp_client->request_list; list; list = list->next) { | 
|  | code = (uint8_t) GPOINTER_TO_INT(list->data); | 
|  |  | 
|  | option = dhcp_get_option(packet, code); | 
|  | if (!option) { | 
|  | g_hash_table_remove(dhcp_client->code_value_hash, | 
|  | GINT_TO_POINTER((int) code)); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | 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, type); | 
|  |  | 
|  | g_free(option_value); | 
|  |  | 
|  | 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 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, | 
|  | *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; | 
|  | const char *p = NULL; | 
|  | struct in_addr slog; | 
|  | char serverbuf[INET_ADDRSTRLEN], requestbuf[INET_ADDRSTRLEN], gatebuf[INET_ADDRSTRLEN]; | 
|  |  | 
|  | if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) { | 
|  | dhcp_client->listener_watch = 0; | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | if (dhcp_client->listen_mode == L_NONE) | 
|  | return FALSE; | 
|  |  | 
|  | 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); | 
|  | 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 (!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; | 
|  | } | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | if (!message_type && !client_id) | 
|  | /* No message type / client id option, ignore package */ | 
|  | return TRUE; | 
|  |  | 
|  | debug(dhcp_client, "received DHCP packet xid 0x%04x " | 
|  | "(current state %d)", ntohl(xid), dhcp_client->state); | 
|  |  | 
|  | if (message_type) { | 
|  | p = get_message_type(*message_type); | 
|  |  | 
|  | debug(dhcp_client, "received DHCP%s (%#02x) packet " | 
|  | "(current state %d)", | 
|  | p, | 
|  | *message_type, | 
|  | dhcp_client->state); | 
|  | } else { | 
|  | debug(dhcp_client, "received DHCPv6 packet " | 
|  | "(current state %d)", | 
|  | dhcp_client->state); | 
|  | } | 
|  |  | 
|  | switch (dhcp_client->state) { | 
|  | case INIT_SELECTING: | 
|  | if (message_type == NULL ) { | 
|  | debug(dhcp_client, "INIT_SELECTING but message_type is NULL" ); | 
|  | return TRUE; | 
|  | } | 
|  | if (*message_type != DHCPOFFER) | 
|  | return TRUE; | 
|  |  | 
|  | remove_timeouts(dhcp_client); | 
|  | dhcp_client->timeout = 0; | 
|  | dhcp_client->retry_times = 0; | 
|  |  | 
|  | 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; | 
|  |  | 
|  | if (dst_addr.sin_addr.s_addr == INADDR_BROADCAST) | 
|  | dhcp_client->request_bcast = true; | 
|  | else | 
|  | dhcp_client->request_bcast = false; | 
|  |  | 
|  | debug(dhcp_client, "init ip %s -> %sadding broadcast flag", | 
|  | inet_ntoa(dst_addr.sin_addr), | 
|  | dhcp_client->request_bcast ? "" : "not "); | 
|  |  | 
|  | slog.s_addr = htonl(dhcp_client->server_ip); | 
|  | debug(dhcp_client, "%s of %s from %s (%s)", | 
|  | p, | 
|  | inet_ntop(AF_INET, &(packet.yiaddr), | 
|  | requestbuf, sizeof(requestbuf)), | 
|  | inet_ntop(AF_INET, &slog, | 
|  | serverbuf, sizeof(serverbuf)), | 
|  | inet_ntop(AF_INET, &(packet.gateway_nip), | 
|  | gatebuf, sizeof(gatebuf))); | 
|  |  | 
|  | 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 == NULL ) { | 
|  | debug(dhcp_client, "REQUESTING/RENEWING/REBINDING/REBOOTING but message_type is NULL" ); | 
|  | return TRUE; | 
|  | } | 
|  | if (*message_type == DHCPACK) { | 
|  | dhcp_client->retry_times = 0; | 
|  |  | 
|  | remove_timeouts(dhcp_client); | 
|  |  | 
|  | dhcp_client->lease_seconds = get_lease(dhcp_client, &packet); | 
|  |  | 
|  | get_request(dhcp_client, &packet); | 
|  |  | 
|  | switch_listening_mode(dhcp_client, L_NONE); | 
|  |  | 
|  | 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); | 
|  | } | 
|  |  | 
|  | slog.s_addr = htonl(dhcp_client->server_ip); | 
|  | debug(dhcp_client, "%s of %s from %s (%s)", | 
|  | p, | 
|  | inet_ntop(AF_INET, &(packet.yiaddr), | 
|  | requestbuf, sizeof(requestbuf)), | 
|  | inet_ntop(AF_INET, &slog, | 
|  | serverbuf, sizeof(serverbuf)), | 
|  | inet_ntop(AF_INET, &(packet.gateway_nip), | 
|  | gatebuf, sizeof(gatebuf))); | 
|  |  | 
|  | /* Address should be set up here */ | 
|  | if (dhcp_client->lease_available_cb) | 
|  | dhcp_client->lease_available_cb(dhcp_client, | 
|  | dhcp_client->lease_available_data); | 
|  |  | 
|  | start_bound(dhcp_client); | 
|  | } else if (*message_type == DHCPNAK) { | 
|  | dhcp_client->retry_times = 0; | 
|  |  | 
|  | remove_timeouts(dhcp_client); | 
|  |  | 
|  | dhcp_client->timeout = g_rttimeout_add_seconds_full(CLOCK_BOOTTIME_ALARM, | 
|  | G_PRIORITY_HIGH, 3, | 
|  | restart_dhcp_timeout, | 
|  | dhcp_client, | 
|  | NULL); | 
|  | } | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | if (message_type) { | 
|  | debug(dhcp_client, "processed DHCP%s (%#02x) packet (new state %d)", | 
|  | p, | 
|  | *message_type, | 
|  | dhcp_client->state); | 
|  | } else { | 
|  | debug(dhcp_client, "processed DHCPv6 packet (new state %d)", | 
|  | dhcp_client->state); | 
|  | } | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | static gboolean discover_timeout(gpointer user_data) | 
|  | { | 
|  | GDHCPClient *dhcp_client = user_data; | 
|  |  | 
|  | dhcp_client->retry_times++; | 
|  |  | 
|  | /* | 
|  | * 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; | 
|  |  | 
|  | 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; | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | #define DQUAD(_a,_b,_c,_d) ( ((_a)<<24) | ((_b)<<16) | ((_c)<<8) | (_d) ) | 
|  | #define V4MATCH(addr, a,b,c,d, m) ( !(((addr) ^ DQUAD(a,b,c,d)) >> (32 - (m))) ) | 
|  |  | 
|  | int g_dhcp_client_start(GDHCPClient *dhcp_client, const char *last_address) | 
|  | { | 
|  | int re; | 
|  | uint32_t addr; | 
|  | uint64_t rand; | 
|  |  | 
|  | debug(dhcp_client, "%s: type %d retry_times %d last_address %s", | 
|  | __func__, dhcp_client->type, dhcp_client->retry_times, last_address); | 
|  |  | 
|  | 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 (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; | 
|  |  | 
|  | dhcp_client->state = INIT_SELECTING; | 
|  | re = switch_listening_mode(dhcp_client, L2); | 
|  | if (re != 0) | 
|  | return re; | 
|  |  | 
|  | dhcp_get_random(&rand); | 
|  | dhcp_client->xid = rand; | 
|  | dhcp_client->start = time(NULL); | 
|  | } | 
|  |  | 
|  | if (!last_address) { | 
|  | addr = 0; | 
|  | } else { | 
|  | addr = ntohl(inet_addr(last_address)); | 
|  | if (addr == 0xFFFFFFFF || V4MATCH(addr, 169, 254, 0, 0, 16)) { | 
|  | addr = 0; | 
|  | } else { | 
|  | 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_rttimeout_add_seconds_full(CLOCK_BOOTTIME_ALARM, | 
|  | G_PRIORITY_HIGH, | 
|  | REQUEST_TIMEOUT, | 
|  | reboot_timeout, | 
|  | dhcp_client, | 
|  | NULL); | 
|  | return 0; | 
|  | } | 
|  | send_discover(dhcp_client, addr); | 
|  |  | 
|  | dhcp_client->timeout = g_rttimeout_add_seconds_full(CLOCK_BOOTTIME_ALARM, G_PRIORITY_HIGH, | 
|  | DISCOVER_TIMEOUT, | 
|  | discover_timeout, | 
|  | dhcp_client, | 
|  | NULL); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void g_dhcp_client_stop(GDHCPClient *dhcp_client) | 
|  | { | 
|  | switch_listening_mode(dhcp_client, L_NONE); | 
|  |  | 
|  | if (dhcp_client->state == BOUND || | 
|  | dhcp_client->state == RENEWING || | 
|  | dhcp_client->state == REBINDING) | 
|  | send_release(dhcp_client, dhcp_client->server_ip, | 
|  | dhcp_client->requested_ip); | 
|  |  | 
|  | remove_timeouts(dhcp_client); | 
|  |  | 
|  | if (dhcp_client->listener_watch > 0) { | 
|  | g_source_remove(dhcp_client->listener_watch); | 
|  | dhcp_client->listener_watch = 0; | 
|  | } | 
|  |  | 
|  | 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, | 
|  | unsigned char option_code) | 
|  | { | 
|  | return g_hash_table_lookup(dhcp_client->code_value_hash, | 
|  | GINT_TO_POINTER((int) option_code)); | 
|  | } | 
|  |  | 
|  | void g_dhcp_client_register_event(GDHCPClient *dhcp_client, | 
|  | GDHCPClientEvent event, | 
|  | GDHCPClientEventFunc func, | 
|  | gpointer data) | 
|  | { | 
|  | switch (event) { | 
|  | case G_DHCP_CLIENT_EVENT_LEASE_AVAILABLE: | 
|  | 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; | 
|  | return; | 
|  | case G_DHCP_CLIENT_EVENT_LEASE_LOST: | 
|  | 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; | 
|  | case G_DHCP_CLIENT_EVENT_WAKE_EVENT: | 
|  | dhcp_client->wake_event_cb = func; | 
|  | dhcp_client->wake_event_data= data; | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | 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) | 
|  | { | 
|  | if (!dhcp_client) | 
|  | return NULL; | 
|  |  | 
|  | 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 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 int option_code) | 
|  | { | 
|  | 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))); | 
|  |  | 
|  | return G_DHCP_CLIENT_ERROR_NONE; | 
|  | } | 
|  |  | 
|  | void g_dhcp_client_clear_requests(GDHCPClient *dhcp_client) | 
|  | { | 
|  | g_list_free(dhcp_client->request_list); | 
|  | dhcp_client->request_list = NULL; | 
|  | } | 
|  |  | 
|  | void g_dhcp_client_clear_values(GDHCPClient *dhcp_client) | 
|  | { | 
|  | g_hash_table_remove_all(dhcp_client->send_value_hash); | 
|  | } | 
|  |  | 
|  | static uint8_t *alloc_dhcp_option(int code, const uint8_t *data, unsigned size) | 
|  | { | 
|  | uint8_t *storage; | 
|  |  | 
|  | storage = g_try_malloc(size + OPT_DATA); | 
|  | if (!storage) | 
|  | return NULL; | 
|  |  | 
|  | storage[OPT_CODE] = code; | 
|  | storage[OPT_LEN] = size; | 
|  | memcpy(&storage[OPT_DATA], data, size); | 
|  |  | 
|  | return storage; | 
|  | } | 
|  |  | 
|  | 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 */ | 
|  | 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) { | 
|  | 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); | 
|  | } | 
|  |  | 
|  | return G_DHCP_CLIENT_ERROR_NONE; | 
|  | } | 
|  |  | 
|  | 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) | 
|  | { | 
|  | 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 (!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_free(dhcp_client); | 
|  | } | 
|  |  | 
|  | void g_dhcp_client_set_debug(GDHCPClient *dhcp_client, | 
|  | GDHCPDebugFunc func, gpointer user_data) | 
|  | { | 
|  | 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; | 
|  | } |