summaryrefslogtreecommitdiff
path: root/gdhcp/client.c
diff options
context:
space:
mode:
authorMartin Xu <martin.xu@intel.com>2010-07-21 23:07:35 -0700
committerMarcel Holtmann <marcel@holtmann.org>2010-07-21 23:07:35 -0700
commit0c5c862749c05193cf4c513628328c6db02b5222 (patch)
tree16fd1cac0e9d8532059d4deb1ad809c3f6792b98 /gdhcp/client.c
parent8a5126061c80fb8c6309bce69c34738ede2f7895 (diff)
downloadconnman-0c5c862749c05193cf4c513628328c6db02b5222.tar.gz
connman-0c5c862749c05193cf4c513628328c6db02b5222.tar.bz2
connman-0c5c862749c05193cf4c513628328c6db02b5222.zip
Add initial support for DHCP client library
Diffstat (limited to 'gdhcp/client.c')
-rw-r--r--gdhcp/client.c1229
1 files changed, 1229 insertions, 0 deletions
diff --git a/gdhcp/client.c b/gdhcp/client.c
new file mode 100644
index 00000000..a6f0e8a5
--- /dev/null
+++ b/gdhcp/client.c
@@ -0,0 +1,1229 @@
+/*
+ *
+ * DHCP client library with GLib integration
+ *
+ * Copyright (C) 2009-2010 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
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <arpa/inet.h>
+
+#include <netpacket/packet.h>
+#include <net/ethernet.h>
+#include <net/if_arp.h>
+
+#include <linux/if.h>
+#include <linux/filter.h>
+
+#include <glib.h>
+
+#include "gdhcp.h"
+#include "common.h"
+
+#define DISCOVER_TIMEOUT 3
+#define DISCOVER_RETRIES 3
+
+#define REQUEST_TIMEOUT 3
+#define REQUEST_RETRIES 3
+
+typedef enum _listen_mode {
+ L_NONE,
+ L2,
+ L3,
+} ListenMode;
+
+typedef enum _dhcp_client_state {
+ INIT_SELECTING,
+ REQUESTING,
+ BOUND,
+ RENEWING,
+ REBINDING,
+ RELEASED,
+} ClientState;
+
+struct _GDHCPClient {
+ gint 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;
+ uint32_t lease_seconds;
+ ListenMode listen_mode;
+ int listener_sockfd;
+ uint8_t retry_times;
+ uint8_t ack_retry_times;
+ guint timeout;
+ guint listener_watch;
+ GIOChannel *listener_channel;
+ GList *require_list;
+ GList *request_list;
+ GHashTable *code_value_hash;
+ GHashTable *send_value_hash;
+ GDHCPClientEventFunc lease_available_cb;
+ gpointer lease_available_data;
+ GDHCPClientEventFunc no_lease_cb;
+ gpointer no_lease_data;
+ GDHCPClientEventFunc lease_lost_cb;
+ gpointer lease_lost_data;
+ GDHCPClientEventFunc address_conflict_cb;
+ gpointer address_conflict_data;
+ GDHCPDebugFunc debug_func;
+ gpointer debug_data;
+};
+
+static GTimer *timer = NULL;
+
+/* Initialize the packet with the proper defaults */
+static void init_packet(GDHCPClient *dhcp_client,
+ struct dhcp_packet *packet, char type)
+{
+ 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;
+ }
+}
+
+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 int send_discover(GDHCPClient *dhcp_client, uint32_t requested)
+{
+ struct dhcp_packet packet;
+
+ init_packet(dhcp_client, &packet, DHCPDISCOVER);
+
+ packet.xid = dhcp_client->xid;
+
+ if (requested)
+ dhcp_add_simple_option(&packet, DHCP_REQUESTED_IP, requested);
+
+ /* Explicitly saying that we want RFC-compliant packets helps
+ * some buggy DHCP servers to NOT send bigger packets */
+ dhcp_add_simple_option(&packet, DHCP_MAX_SIZE, htons(576));
+
+ add_request_options(dhcp_client, &packet);
+
+ add_send_options(dhcp_client, &packet);
+
+ return dhcp_send_raw_packet(&packet, INADDR_ANY, CLIENT_PORT,
+ INADDR_BROADCAST, SERVER_PORT,
+ MAC_BCAST_ADDR, dhcp_client->ifindex);
+}
+
+static int send_select(GDHCPClient *dhcp_client)
+{
+ struct dhcp_packet packet;
+ struct in_addr addr;
+
+ init_packet(dhcp_client, &packet, DHCPREQUEST);
+
+ packet.xid = dhcp_client->xid;
+
+ dhcp_add_simple_option(&packet, DHCP_REQUESTED_IP,
+ dhcp_client->requested_ip);
+ dhcp_add_simple_option(&packet, DHCP_SERVER_ID, dhcp_client->server_ip);
+
+ add_request_options(dhcp_client, &packet);
+
+ add_send_options(dhcp_client, &packet);
+
+ addr.s_addr = dhcp_client->requested_ip;
+
+ return dhcp_send_raw_packet(&packet, INADDR_ANY, CLIENT_PORT,
+ INADDR_BROADCAST, SERVER_PORT,
+ MAC_BCAST_ADDR, dhcp_client->ifindex);
+}
+
+static int send_renew(GDHCPClient *dhcp_client)
+{
+ struct dhcp_packet packet;
+
+ init_packet(dhcp_client , &packet, DHCPREQUEST);
+ packet.xid = dhcp_client->xid;
+ packet.ciaddr = dhcp_client->requested_ip;
+
+ add_request_options(dhcp_client, &packet);
+
+ add_send_options(dhcp_client, &packet);
+
+ return dhcp_send_kernel_packet(&packet,
+ dhcp_client->requested_ip, CLIENT_PORT,
+ dhcp_client->server_ip, SERVER_PORT);
+}
+
+static int send_rebound(GDHCPClient *dhcp_client)
+{
+ struct dhcp_packet packet;
+
+ init_packet(dhcp_client , &packet, DHCPREQUEST);
+ packet.xid = dhcp_client->xid;
+ packet.ciaddr = dhcp_client->requested_ip;
+
+ add_request_options(dhcp_client, &packet);
+
+ add_send_options(dhcp_client, &packet);
+
+ return dhcp_send_raw_packet(&packet, INADDR_ANY, CLIENT_PORT,
+ INADDR_BROADCAST, SERVER_PORT,
+ MAC_BCAST_ADDR, dhcp_client->ifindex);
+}
+
+static int send_release(GDHCPClient *dhcp_client,
+ uint32_t server, uint32_t ciaddr)
+{
+ struct dhcp_packet packet;
+
+ init_packet(dhcp_client, &packet, DHCPRELEASE);
+ packet.xid = rand();
+ packet.ciaddr = ciaddr;
+
+ dhcp_add_simple_option(&packet, DHCP_SERVER_ID, server);
+
+ return dhcp_send_kernel_packet(&packet, ciaddr, CLIENT_PORT,
+ server, SERVER_PORT);
+}
+
+static gboolean interface_is_up(int index)
+{
+ int sk, err;
+ struct ifreq ifr;
+ gboolean ret = FALSE;
+
+ sk = socket(PF_INET, SOCK_DGRAM, 0);
+ if (sk < 0) {
+ perror("Open socket error");
+ return FALSE;
+ }
+
+ 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, SIOCGIFFLAGS, &ifr);
+ if (err < 0) {
+ perror("Get interface flags error");
+ goto done;
+ }
+
+ if (ifr.ifr_flags & IFF_UP)
+ ret = TRUE;
+
+done:
+ close(sk);
+
+ return ret;
+}
+
+static char *get_interface_name(int index)
+{
+ struct ifreq ifr;
+ int sk, err;
+
+ if (index < 0)
+ return NULL;
+
+ sk = socket(PF_INET, SOCK_DGRAM, 0);
+ if (sk < 0) {
+ perror("Open socket error");
+ return NULL;
+ }
+
+ memset(&ifr, 0, sizeof(ifr));
+ ifr.ifr_ifindex = index;
+
+ err = ioctl(sk, SIOCGIFNAME, &ifr);
+ if (err < 0) {
+ perror("Get interface name error");
+ close(sk);
+ return NULL;
+ }
+
+ close(sk);
+
+ return g_strdup(ifr.ifr_name);
+}
+
+static void get_interface_mac_address(int index, uint8_t *mac_address)
+{
+ struct ifreq ifr;
+ int sk, err;
+
+ sk = socket(PF_INET, SOCK_DGRAM, 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);
+}
+
+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);
+}
+
+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 == NULL) {
+ *error = G_DHCP_CLIENT_ERROR_NOMEM;
+ return NULL;
+ }
+
+ dhcp_client->interface = get_interface_name(ifindex);
+ if (dhcp_client->interface == NULL) {
+ *error = G_DHCP_CLIENT_ERROR_INERFACE_UNAVAILABLE;
+ goto error;
+ }
+
+ if (interface_is_up(ifindex) == FALSE) {
+ *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->listener_channel = NULL;
+ dhcp_client->listen_mode = L_NONE;
+ dhcp_client->ref_count = 1;
+ dhcp_client->type = type;
+ dhcp_client->ifindex = ifindex;
+ dhcp_client->lease_available_cb = NULL;
+ dhcp_client->no_lease_cb = NULL;
+ dhcp_client->lease_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;
+
+ *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, htons(ETH_P_IP));
+ if (fd < 0)
+ return fd;
+
+ 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));
+
+ 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) {
+ close(fd);
+ return -errno;
+ }
+
+ return fd;
+}
+
+static gboolean 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)
+{
+ 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) == FALSE)
+ 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;
+
+ return bytes - (sizeof(packet.ip) + sizeof(packet.udp));
+}
+
+static gboolean check_package_owner(GDHCPClient *dhcp_client,
+ struct dhcp_packet *packet)
+{
+ 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;
+
+ 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;
+
+ if (dhcp_client->listen_mode != L_NONE) {
+ g_source_remove(dhcp_client->listener_watch);
+ dhcp_client->listener_channel = NULL;
+ dhcp_client->listen_mode = L_NONE;
+ dhcp_client->listener_sockfd = -1;
+ dhcp_client->listener_watch = 0;
+ }
+
+ if (listen_mode == L_NONE)
+ return 0;
+
+ if (listen_mode == L2)
+ listener_sockfd = dhcp_l2_socket(dhcp_client->ifindex);
+ else if (listen_mode == L3)
+ listener_sockfd = dhcp_l3_socket(CLIENT_PORT,
+ dhcp_client->interface);
+ else
+ return -EIO;
+
+ if (listener_sockfd < 0)
+ return -EIO;
+
+ listener_channel = g_io_channel_unix_new(listener_sockfd);
+ if (listener_channel == NULL) {
+ /* Failed to create listener channel */
+ close(listener_sockfd);
+ return -EIO;
+ }
+
+ dhcp_client->listen_mode = listen_mode;
+ dhcp_client->listener_sockfd = listener_sockfd;
+ dhcp_client->listener_channel = listener_channel;
+
+ g_io_channel_set_close_on_unref(listener_channel, TRUE);
+ dhcp_client->listener_watch =
+ g_io_add_watch_full(listener_channel,
+ G_PRIORITY_HIGH, G_IO_IN,
+ listener_event, dhcp_client,
+ NULL);
+ g_io_channel_unref(dhcp_client->listener_channel);
+
+ return 0;
+}
+
+static void start_request(GDHCPClient *dhcp_client)
+{
+ if (dhcp_client->retry_times == REQUEST_RETRIES) {
+ dhcp_client->state = INIT_SELECTING;
+
+ if (dhcp_client->no_lease_cb != NULL)
+ 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_select(dhcp_client);
+
+ dhcp_client->timeout = g_timeout_add_seconds_full(G_PRIORITY_HIGH,
+ REQUEST_TIMEOUT,
+ request_timeout,
+ dhcp_client,
+ NULL);
+}
+
+static uint32_t get_lease(struct dhcp_packet *packet)
+{
+ uint8_t *option_u8;
+ uint32_t lease_seconds;
+
+ option_u8 = dhcp_get_option(packet, DHCP_LEASE_TIME);
+ if (option_u8 == NULL)
+ return 3600;
+
+ lease_seconds = dhcp_get_unaligned((uint32_t *) option_u8);
+ lease_seconds = ntohl(lease_seconds);
+ /* paranoia: must not be prone to overflows */
+ lease_seconds &= 0x0fffffff;
+ if (lease_seconds < 10)
+ lease_seconds = 10;
+
+ return lease_seconds;
+}
+
+static void restart_dhcp(GDHCPClient *dhcp_client, int retry_times)
+{
+ if (dhcp_client->timeout > 0) {
+ g_source_remove(dhcp_client->timeout);
+ dhcp_client->timeout = 0;
+ }
+
+ dhcp_client->retry_times = retry_times;
+ dhcp_client->requested_ip = 0;
+ switch_listening_mode(dhcp_client, L2);
+
+ g_dhcp_client_start(dhcp_client);
+}
+
+static gboolean start_rebound_timeout(gpointer user_data)
+{
+ GDHCPClient *dhcp_client = user_data;
+
+ switch_listening_mode(dhcp_client, L2);
+
+ dhcp_client->lease_seconds >>= 1;
+
+ /* We need to have enough time to receive ACK package*/
+ if (dhcp_client->lease_seconds <= 6) {
+
+ /* ip need to be cleared */
+ if (dhcp_client->lease_lost_cb != NULL)
+ dhcp_client->lease_lost_cb(dhcp_client,
+ dhcp_client->lease_lost_data);
+
+ restart_dhcp(dhcp_client, 0);
+ } else {
+ send_rebound(dhcp_client);
+
+ dhcp_client->timeout =
+ g_timeout_add_seconds_full(G_PRIORITY_HIGH,
+ dhcp_client->lease_seconds >> 1,
+ start_rebound_timeout,
+ dhcp_client,
+ NULL);
+ }
+
+ return FALSE;
+}
+
+static void start_rebound(GDHCPClient *dhcp_client)
+{
+ dhcp_client->state = REBINDING;
+
+ dhcp_client->timeout = g_timeout_add_seconds_full(G_PRIORITY_HIGH,
+ dhcp_client->lease_seconds >> 1,
+ start_rebound_timeout,
+ dhcp_client,
+ NULL);
+}
+
+static gboolean start_renew_timeout(gpointer user_data)
+{
+ GDHCPClient *dhcp_client = user_data;
+ gdouble elapse;
+ gulong microseconds;
+
+ elapse = g_timer_elapsed(timer, &microseconds);
+
+ g_timer_start(timer);
+
+ dhcp_client->state = RENEWING;
+
+ dhcp_client->lease_seconds >>= 1;
+
+ switch_listening_mode(dhcp_client, L3);
+ if (dhcp_client->lease_seconds <= 60)
+ start_rebound(dhcp_client);
+ else {
+ send_renew(dhcp_client);
+
+ dhcp_client->timeout =
+ g_timeout_add_seconds_full(G_PRIORITY_HIGH,
+ dhcp_client->lease_seconds >> 1,
+ start_renew_timeout,
+ dhcp_client,
+ NULL);
+ }
+
+ return FALSE;
+}
+
+static void start_bound(GDHCPClient *dhcp_client)
+{
+ dhcp_client->state = BOUND;
+
+ if (timer == NULL)
+ timer = g_timer_new();
+
+ dhcp_client->timeout =
+ g_timeout_add_seconds_full(G_PRIORITY_HIGH,
+ dhcp_client->lease_seconds >> 1,
+ start_renew_timeout, dhcp_client,
+ NULL);
+}
+
+static gboolean restart_dhcp_timeout(gpointer user_data)
+{
+ GDHCPClient *dhcp_client = user_data;
+
+ dhcp_client->ack_retry_times++;
+
+ restart_dhcp(dhcp_client, dhcp_client->ack_retry_times);
+
+ 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];
+ upper_length = len_of_option_as_string[type] *
+ ((unsigned)len / (unsigned)optlen);
+ dest = ret = malloc(upper_length + 1);
+
+ while (len >= optlen) {
+ switch (type) {
+ case OPTION_IP:
+ dest += sprint_nip(dest, "", option);
+ break;
+ case OPTION_U16: {
+ uint16_t val_u16 = dhcp_get_unaligned(
+ (uint16_t *) option);
+ dest += sprintf(dest, "%u", ntohs(val_u16));
+ break;
+ }
+ case OPTION_U32: {
+ uint32_t val_u32 = dhcp_get_unaligned(
+ (uint32_t *) option);
+ dest += sprintf(dest, type == OPTION_U32 ? "%lu" :
+ "%ld", (unsigned long) ntohl(val_u32));
+ 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)
+{
+ char *pos = value;
+ GList *list = NULL;
+
+ while ((pos = strchr(pos, ' ')) != NULL) {
+ *pos = '\0';
+
+ list = g_list_append(list, g_strdup(value));
+
+ value = ++pos;
+ }
+
+ list = g_list_append(list, g_strdup(value));
+
+ return 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 == NULL) {
+ 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);
+
+ value_list = get_option_value_list(option_value);
+
+ g_free(option_value);
+
+ if (value_list == NULL)
+ 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 dhcp_packet packet;
+ uint8_t *message_type, *option_u8;
+ int re;
+
+ 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;
+
+ if (dhcp_client->listen_mode == L2)
+ re = dhcp_recv_l2_packet(&packet, dhcp_client->listener_sockfd);
+ else if (dhcp_client->listen_mode == L3)
+ re = dhcp_recv_l3_packet(&packet, dhcp_client->listener_sockfd);
+ else
+ re = -EIO;
+
+ if (re < 0)
+ return TRUE;
+
+ if (check_package_owner(dhcp_client, &packet) == FALSE)
+ return TRUE;
+
+ message_type = dhcp_get_option(&packet, DHCP_MESSAGE_TYPE);
+ if (message_type == NULL)
+ /* No message type option, ignore pakcage */
+ return TRUE;
+
+ switch (dhcp_client->state) {
+ case INIT_SELECTING:
+ if (*message_type != DHCPOFFER)
+ return TRUE;
+
+ g_source_remove(dhcp_client->timeout);
+ dhcp_client->timeout = 0;
+ dhcp_client->retry_times = 0;
+
+ option_u8 = dhcp_get_option(&packet, DHCP_SERVER_ID);
+ dhcp_client->server_ip =
+ dhcp_get_unaligned((uint32_t *) option_u8);
+ dhcp_client->requested_ip = packet.yiaddr;
+
+ dhcp_client->state = REQUESTING;
+
+ start_request(dhcp_client);
+
+ return TRUE;
+ case REQUESTING:
+ case RENEWING:
+ case REBINDING:
+ if (*message_type == DHCPACK) {
+ dhcp_client->retry_times = 0;
+
+ if (dhcp_client->timeout > 0)
+ g_source_remove(dhcp_client->timeout);
+ dhcp_client->timeout = 0;
+
+ dhcp_client->lease_seconds = get_lease(&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);
+
+ /* Address should be set up here */
+ if (dhcp_client->lease_available_cb != NULL)
+ 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;
+
+ if (dhcp_client->timeout > 0)
+ g_source_remove(dhcp_client->timeout);
+
+ dhcp_client->timeout = g_timeout_add_seconds_full(
+ G_PRIORITY_HIGH, 3,
+ restart_dhcp_timeout,
+ dhcp_client,
+ NULL);
+ }
+
+ break;
+ default:
+ break;
+ }
+
+ return TRUE;
+}
+
+static gboolean discover_timeout(gpointer user_data)
+{
+ GDHCPClient *dhcp_client = user_data;
+
+ dhcp_client->retry_times++;
+
+ g_dhcp_client_start(dhcp_client);
+
+ return FALSE;
+}
+
+int g_dhcp_client_start(GDHCPClient *dhcp_client)
+{
+ int re;
+
+ if (dhcp_client->retry_times == DISCOVER_RETRIES) {
+ if (dhcp_client->no_lease_cb != NULL)
+ dhcp_client->no_lease_cb(dhcp_client,
+ dhcp_client->no_lease_data);
+
+ 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_client->xid = rand();
+ }
+
+ send_discover(dhcp_client, 0);
+
+ dhcp_client->timeout = g_timeout_add_seconds_full(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);
+
+ if (dhcp_client->timeout > 0) {
+ g_source_remove(dhcp_client->timeout);
+ dhcp_client->timeout = 0;
+ }
+
+ if (dhcp_client->listener_watch > 0) {
+ g_source_remove(dhcp_client->listener_watch);
+ dhcp_client->listener_watch = 0;
+ }
+
+ dhcp_client->listener_channel = NULL;
+
+ dhcp_client->retry_times = 0;
+ dhcp_client->ack_retry_times = 0;
+
+ dhcp_client->requested_ip = 0;
+ dhcp_client->state = RELEASED;
+ dhcp_client->lease_seconds = 0;
+}
+
+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_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_ADDRESS_CONFLICT:
+ dhcp_client->address_conflict_cb = func;
+ dhcp_client->address_conflict_data = data;
+ return;
+ }
+}
+
+int g_dhcp_client_get_index(GDHCPClient *dhcp_client)
+{
+ return dhcp_client->ifindex;
+}
+
+char *g_dhcp_client_get_address(GDHCPClient *dhcp_client)
+{
+ return g_strdup(dhcp_client->assigned_ip);
+}
+
+GDHCPClientError g_dhcp_client_set_request(GDHCPClient *dhcp_client,
+ unsigned char option_code)
+{
+ if (g_list_find(dhcp_client->request_list,
+ GINT_TO_POINTER((int) option_code)) == NULL)
+ dhcp_client->request_list = g_list_prepend(
+ dhcp_client->request_list,
+ (GINT_TO_POINTER((int) option_code)));
+
+ return G_DHCP_CLIENT_ERROR_NONE;
+}
+
+static uint8_t *alloc_dhcp_option(int code, const char *str, int extra)
+{
+ uint8_t *storage;
+ int len = strnlen(str, 255);
+
+ storage = malloc(len + extra + OPT_DATA);
+ storage[OPT_CODE] = code;
+ storage[OPT_LEN] = len + extra;
+ memcpy(storage + extra + OPT_DATA, str, len);
+
+ return storage;
+}
+
+static const char *get_hostname(const char *host)
+{
+ char local_host_name[HOST_NAME_MAX + 1];
+
+ if (g_strcmp0("<hostname>", host) != 0)
+ return g_strdup(host);
+
+ if (gethostname(local_host_name, HOST_NAME_MAX) != 0)
+ return NULL;
+
+ local_host_name[HOST_NAME_MAX] = 0;
+
+ return g_strdup(local_host_name);
+}
+
+/* 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;
+ const char *hostname;
+
+ if (option_code == DHCP_HOST_NAME) {
+ hostname = get_hostname(option_value);
+
+ binary_option = alloc_dhcp_option(option_code, hostname, 0);
+
+ g_hash_table_insert(dhcp_client->send_value_hash,
+ GINT_TO_POINTER((int) option_code), binary_option);
+ }
+
+ return G_DHCP_CLIENT_ERROR_NONE;
+}
+
+void g_dhcp_client_ref(GDHCPClient *dhcp_client)
+{
+ g_atomic_int_inc(&dhcp_client->ref_count);
+}
+
+void g_dhcp_client_unref(GDHCPClient *dhcp_client)
+{
+ if (g_atomic_int_dec_and_test(&dhcp_client->ref_count) == FALSE)
+ return;
+
+ g_dhcp_client_stop(dhcp_client);
+
+ g_free(dhcp_client->interface);
+ g_free(dhcp_client->assigned_ip);
+
+ 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 data)
+{
+ dhcp_client->debug_func = func;
+ dhcp_client->debug_data = data;
+}