Add initial support for DHCP client library
authorMartin Xu <martin.xu@intel.com>
Thu, 22 Jul 2010 06:07:35 +0000 (23:07 -0700)
committerMarcel Holtmann <marcel@holtmann.org>
Thu, 22 Jul 2010 06:07:35 +0000 (23:07 -0700)
gdhcp/client.c [new file with mode: 0644]
gdhcp/common.c [new file with mode: 0644]
gdhcp/common.h [new file with mode: 0644]
gdhcp/gdhcp.h [new file with mode: 0644]

diff --git a/gdhcp/client.c b/gdhcp/client.c
new file mode 100644 (file)
index 0000000..a6f0e8a
--- /dev/null
@@ -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;
+}
diff --git a/gdhcp/common.c b/gdhcp/common.c
new file mode 100644 (file)
index 0000000..784844b
--- /dev/null
@@ -0,0 +1,417 @@
+/*
+ *  DHCP library with GLib integration
+ *
+ *  Copyright (C) 2007-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 <errno.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <string.h>
+#include <endian.h>
+#include <netpacket/packet.h>
+#include <net/ethernet.h>
+
+#include "gdhcp.h"
+#include "common.h"
+
+static const DHCPOption client_options[] = {
+       { OPTION_IP,                    0x01 }, /* subnet-mask */
+       { OPTION_IP | OPTION_LIST,      0x03 }, /* routers */
+       { OPTION_IP | OPTION_LIST,      0x06 }, /* domain-name-servers */
+       { OPTION_STRING,                0x0f }, /* domain-name */
+       { OPTION_IP | OPTION_LIST,      0x2a }, /* ntp-servers */
+       { OPTION_U32,                   0x33 }, /* dhcp-lease-time */
+       /* Options below will not be exposed to user */
+       { OPTION_IP,                    0x32 }, /* requested-ip */
+       { OPTION_U8,                    0x35 }, /* message-type */
+       { OPTION_U16,                   0x39 }, /* max-size */
+       { OPTION_STRING,                0x3c }, /* vendor */
+       { OPTION_STRING,                0x3d }, /* client-id */
+       { OPTION_UNKNOWN,               0x00 },
+};
+
+GDHCPOptionType dhcp_get_code_type(uint8_t code)
+{
+       int i;
+
+       for (i = 0; client_options[i].code; i++) {
+               if (client_options[i].code == code)
+                       return client_options[i].type;
+       }
+
+       return OPTION_UNKNOWN;
+}
+
+uint8_t *dhcp_get_option(struct dhcp_packet *packet, int code)
+{
+       int len, rem;
+       uint8_t *optionptr;
+       uint8_t overload = 0;
+
+       /* option bytes: [code][len][data1][data2]..[dataLEN] */
+       optionptr = packet->options;
+       rem = sizeof(packet->options);
+
+       while (1) {
+               if (rem <= 0)
+                       /* Bad packet, malformed option field */
+                       return NULL;
+
+               if (optionptr[OPT_CODE] == DHCP_PADDING) {
+                       rem--;
+                       optionptr++;
+
+                       continue;
+               }
+
+               if (optionptr[OPT_CODE] == DHCP_END) {
+                       if (overload & FILE_FIELD) {
+                               overload &= ~FILE_FIELD;
+
+                               optionptr = packet->file;
+                               rem = sizeof(packet->file);
+
+                               continue;
+                       } else if (overload & SNAME_FIELD) {
+                               overload &= ~SNAME_FIELD;
+
+                               optionptr = packet->sname;
+                               rem = sizeof(packet->sname);
+
+                               continue;
+                       }
+
+                       break;
+               }
+
+               len = 2 + optionptr[OPT_LEN];
+
+               rem -= len;
+               if (rem < 0)
+                       continue; /* complain and return NULL */
+
+               if (optionptr[OPT_CODE] == code)
+                       return optionptr + OPT_DATA;
+
+               if (optionptr[OPT_CODE] == DHCP_OPTION_OVERLOAD)
+                       overload |= optionptr[OPT_DATA];
+
+               optionptr += len;
+       }
+
+       return NULL;
+}
+
+int dhcp_end_option(uint8_t *optionptr)
+{
+       int i = 0;
+
+       while (optionptr[i] != DHCP_END) {
+               if (optionptr[i] != DHCP_PADDING)
+                       i += optionptr[i + OPT_LEN] + OPT_DATA - 1;
+
+               i++;
+       }
+
+       return i;
+}
+
+/*
+ * Add an option (supplied in binary form) to the options.
+ * Option format: [code][len][data1][data2]..[dataLEN]
+ */
+void dhcp_add_binary_option(struct dhcp_packet *packet, uint8_t *addopt)
+{
+       unsigned len;
+       uint8_t *optionptr = packet->options;
+       unsigned end = dhcp_end_option(optionptr);
+
+       len = OPT_DATA + addopt[OPT_LEN];
+
+       /* end position + (option code/length + addopt length) + end option */
+       if (end + len + 1 >= DHCP_OPTIONS_BUFSIZE)
+               /* option did not fit into the packet */
+               return;
+
+       memcpy(optionptr + end, addopt, len);
+
+       optionptr[end + len] = DHCP_END;
+}
+
+void dhcp_add_simple_option(struct dhcp_packet *packet, uint8_t code,
+                                                       uint32_t data)
+{
+       uint8_t option[6], len;
+       GDHCPOptionType type = dhcp_get_code_type(code);
+
+       if (type == OPTION_UNKNOWN)
+               return;
+
+       option[OPT_CODE] = code;
+
+       len = dhcp_option_lengths[type & OPTION_TYPE_MASK];
+       option[OPT_LEN] = len;
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+       data <<= 8 * (4 - len);
+#endif
+
+       dhcp_put_unaligned(data, (uint32_t *) &option[OPT_DATA]);
+       dhcp_add_binary_option(packet, option);
+
+       return;
+}
+
+void dhcp_init_header(struct dhcp_packet *packet, char type)
+{
+       memset(packet, 0, sizeof(*packet));
+
+       packet->op = BOOTREQUEST;
+
+       switch (type) {
+       case DHCPOFFER:
+       case DHCPACK:
+       case DHCPNAK:
+               packet->op = BOOTREPLY;
+       }
+
+       packet->htype = 1;
+       packet->hlen = 6;
+       packet->cookie = htonl(DHCP_MAGIC);
+       packet->options[0] = DHCP_END;
+
+       dhcp_add_simple_option(packet, DHCP_MESSAGE_TYPE, type);
+}
+
+static gboolean check_vendor(uint8_t  *option_vendor, const char *vendor)
+{
+       uint8_t vendor_length = sizeof(vendor) - 1;
+
+       if (option_vendor[OPT_LEN - OPT_DATA] != vendor_length)
+               return FALSE;
+
+       if (memcmp(option_vendor, vendor, vendor_length) != 0)
+               return FALSE;
+
+       return TRUE;
+}
+
+static void check_broken_vendor(struct dhcp_packet *packet)
+{
+       uint8_t *vendor;
+
+       if (packet->op != BOOTREQUEST)
+               return;
+
+       vendor = dhcp_get_option(packet, DHCP_VENDOR);
+       if (vendor == NULL)
+               return;
+
+       if (check_vendor(vendor, "MSFT 98") == TRUE)
+               packet->flags |= htons(BROADCAST_FLAG);
+}
+
+int dhcp_recv_l3_packet(struct dhcp_packet *packet, int fd)
+{
+       int n;
+
+       memset(packet, 0, sizeof(*packet));
+
+       n = read(fd, packet, sizeof(*packet));
+       if (n < 0)
+               return -errno;
+
+       if (packet->cookie != htonl(DHCP_MAGIC))
+               return -EPROTO;
+
+       check_broken_vendor(packet);
+
+       return n;
+}
+
+/* TODO: Use glib checksum */
+uint16_t dhcp_checksum(void *addr, int count)
+{
+       /*
+        * Compute Internet Checksum for "count" bytes
+        * beginning at location "addr".
+        */
+       int32_t sum = 0;
+       uint16_t *source = (uint16_t *) addr;
+
+       while (count > 1)  {
+               /*  This is the inner loop */
+               sum += *source++;
+               count -= 2;
+       }
+
+       /*  Add left-over byte, if any */
+       if (count > 0) {
+               /* Make sure that the left-over byte is added correctly both
+                * with little and big endian hosts */
+               uint16_t tmp = 0;
+               *(uint8_t *) &tmp = *(uint8_t *) source;
+               sum += tmp;
+       }
+       /*  Fold 32-bit sum to 16 bits */
+       while (sum >> 16)
+               sum = (sum & 0xffff) + (sum >> 16);
+
+       return ~sum;
+}
+
+int dhcp_send_raw_packet(struct dhcp_packet *dhcp_pkt,
+               uint32_t source_ip, int source_port, uint32_t dest_ip,
+                       int dest_port, const uint8_t *dest_arp, int ifindex)
+{
+       struct sockaddr_ll dest;
+       struct ip_udp_dhcp_packet packet;
+       int fd, n;
+
+       enum {
+               IP_UPD_DHCP_SIZE = sizeof(struct ip_udp_dhcp_packet) -
+                                               EXTEND_FOR_BUGGY_SERVERS,
+               UPD_DHCP_SIZE = IP_UPD_DHCP_SIZE -
+                               offsetof(struct ip_udp_dhcp_packet, udp),
+       };
+
+       fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP));
+       if (fd < 0)
+               return -errno;
+
+       memset(&dest, 0, sizeof(dest));
+       memset(&packet, 0, sizeof(packet));
+       packet.data = *dhcp_pkt;
+
+       dest.sll_family = AF_PACKET;
+       dest.sll_protocol = htons(ETH_P_IP);
+       dest.sll_ifindex = ifindex;
+       dest.sll_halen = 6;
+       memcpy(dest.sll_addr, dest_arp, 6);
+       if (bind(fd, (struct sockaddr *)&dest, sizeof(dest)) < 0) {
+               close(fd);
+               return -errno;
+       }
+
+       packet.ip.protocol = IPPROTO_UDP;
+       packet.ip.saddr = source_ip;
+       packet.ip.daddr = dest_ip;
+       packet.udp.source = htons(source_port);
+       packet.udp.dest = htons(dest_port);
+       /* size, excluding IP header: */
+       packet.udp.len = htons(UPD_DHCP_SIZE);
+       /* for UDP checksumming, ip.len is set to UDP packet len */
+       packet.ip.tot_len = packet.udp.len;
+       packet.udp.check = dhcp_checksum(&packet, IP_UPD_DHCP_SIZE);
+       /* but for sending, it is set to IP packet len */
+       packet.ip.tot_len = htons(IP_UPD_DHCP_SIZE);
+       packet.ip.ihl = sizeof(packet.ip) >> 2;
+       packet.ip.version = IPVERSION;
+       packet.ip.ttl = IPDEFTTL;
+       packet.ip.check = dhcp_checksum(&packet.ip, sizeof(packet.ip));
+
+       /*
+        * Currently we send full-sized DHCP packets (zero padded).
+        * If you need to change this: last byte of the packet is
+        * packet.data.options[dhcp_end_option(packet.data.options)]
+        */
+       n = sendto(fd, &packet, IP_UPD_DHCP_SIZE, 0,
+                       (struct sockaddr *) &dest, sizeof(dest));
+       if (n < 0)
+               return -errno;
+
+       close(fd);
+
+       return n;
+}
+
+int dhcp_send_kernel_packet(struct dhcp_packet *dhcp_pkt,
+                               uint32_t source_ip, int source_port,
+                               uint32_t dest_ip, int dest_port)
+{
+       struct sockaddr_in client;
+       int fd, n, opt = 1;
+
+       enum {
+               DHCP_SIZE = sizeof(struct dhcp_packet) -
+                                       EXTEND_FOR_BUGGY_SERVERS,
+       };
+
+       fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
+       if (fd < 0)
+               return -errno;
+
+       setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
+
+       memset(&client, 0, sizeof(client));
+       client.sin_family = AF_INET;
+       client.sin_port = htons(source_port);
+       client.sin_addr.s_addr = source_ip;
+       if (bind(fd, (struct sockaddr *) &client, sizeof(client)) < 0) {
+               close(fd);
+               return -errno;
+       }
+
+       memset(&client, 0, sizeof(client));
+       client.sin_family = AF_INET;
+       client.sin_port = htons(dest_port);
+       client.sin_addr.s_addr = dest_ip;
+       if (connect(fd, (struct sockaddr *) &client, sizeof(client)) < 0) {
+               close(fd);
+               return -errno;
+       }
+
+       n = write(fd, dhcp_pkt, DHCP_SIZE);
+
+       close(fd);
+
+       if (n < 0)
+               return -errno;
+
+       return n;
+}
+
+int dhcp_l3_socket(int port, const char *interface)
+{
+       int fd, opt = 1;
+       struct sockaddr_in addr;
+
+       fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
+
+       setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
+
+       if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE,
+                               interface, strlen(interface) + 1) < 0) {
+               close(fd);
+               return -1;
+       }
+
+       memset(&addr, 0, sizeof(addr));
+       addr.sin_family = AF_INET;
+       addr.sin_port = htons(port);
+       if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
+               close(fd);
+               return -1;
+       }
+
+       return fd;
+}
diff --git a/gdhcp/common.h b/gdhcp/common.h
new file mode 100644 (file)
index 0000000..ade49cd
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+ *
+ *  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
+ *
+ */
+
+#include <netinet/udp.h>
+#include <netinet/ip.h>
+
+#include <glib.h>
+
+#include "gdhcp.h"
+
+#define dhcp_get_unaligned(ptr)                        \
+({                                             \
+       struct __attribute__((packed)) {        \
+               typeof(*(ptr)) __v;             \
+       } *__p = (void *) (ptr);                \
+       __p->__v;                               \
+})
+
+#define dhcp_put_unaligned(val, ptr)           \
+do {                                           \
+       struct __attribute__((packed)) {        \
+               typeof(*(ptr)) __v;             \
+       } *__p = (void *) (ptr);                \
+       __p->__v = (val);                       \
+} while (0)
+
+#define CLIENT_PORT 68
+#define SERVER_PORT 67
+
+#define EXTEND_FOR_BUGGY_SERVERS 80
+
+static const uint8_t MAC_BCAST_ADDR[6] __attribute__((aligned(2))) = {
+       0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+};
+
+/* DHCP packet */
+#define DHCP_MAGIC              0x63825363
+#define DHCP_OPTIONS_BUFSIZE    308
+#define BOOTREQUEST             1
+#define BOOTREPLY               2
+
+#define BROADCAST_FLAG         0x8000
+
+/* See RFC 2131 */
+struct dhcp_packet {
+       uint8_t op;
+       uint8_t htype;
+       uint8_t hlen;
+       uint8_t hops;
+       uint32_t xid;
+       uint16_t secs;
+       uint16_t flags;
+       uint32_t ciaddr;
+       uint32_t yiaddr;
+       uint32_t siaddr_nip;
+       uint32_t gateway_nip;
+       uint8_t chaddr[16];
+       uint8_t sname[64];
+       uint8_t file[128];
+       uint32_t cookie;
+       uint8_t options[DHCP_OPTIONS_BUFSIZE + EXTEND_FOR_BUGGY_SERVERS];
+} __attribute__((packed));
+
+struct ip_udp_dhcp_packet {
+       struct iphdr ip;
+       struct udphdr udp;
+       struct dhcp_packet data;
+} __attribute__((packed));
+
+/* See RFC 2132 */
+#define DHCP_PADDING           0x00
+#define DHCP_SUBNET            0x01
+#define DHCP_ROUTER            0x03
+#define DHCP_TIME_SERVER       0x04
+#define DHCP_NAME_SERVER       0x05
+#define DHCP_DNS_SERVER                0x06
+#define DHCP_HOST_NAME         0x0c
+#define DHCP_DOMAIN_NAME       0x0f
+#define DHCP_NTP_SERVER                0x2a
+#define DHCP_REQUESTED_IP      0x32
+#define DHCP_LEASE_TIME                0x33
+#define DHCP_OPTION_OVERLOAD   0x34
+#define DHCP_MESSAGE_TYPE      0x35
+#define DHCP_SERVER_ID         0x36
+#define DHCP_PARAM_REQ         0x37
+#define DHCP_ERR_MESSAGE       0x38
+#define DHCP_MAX_SIZE          0x39
+#define DHCP_VENDOR            0x3c
+#define DHCP_CLIENT_ID         0x3d
+#define DHCP_END               0xff
+
+#define OPT_CODE               0
+#define OPT_LEN                        1
+#define OPT_DATA               2
+#define OPTION_FIELD           0
+#define FILE_FIELD             1
+#define SNAME_FIELD            2
+
+/* DHCP_MESSAGE_TYPE values */
+#define DHCPDISCOVER           1
+#define DHCPOFFER              2
+#define DHCPREQUEST            3
+#define DHCPDECLINE            4
+#define DHCPACK                        5
+#define DHCPNAK                        6
+#define DHCPRELEASE            7
+#define DHCPINFORM             8
+#define DHCP_MINTYPE DHCPDISCOVER
+#define DHCP_MAXTYPE DHCPINFORM
+
+typedef enum {
+       OPTION_UNKNOWN,
+       OPTION_IP,
+       OPTION_STRING,
+       OPTION_U8,
+       OPTION_U16,
+       OPTION_U32,
+       OPTION_TYPE_MASK = 0x0f,
+       OPTION_LIST = 0x10,
+} GDHCPOptionType;
+
+typedef struct dhcp_option {
+       GDHCPOptionType type;
+       uint8_t code;
+} DHCPOption;
+
+/* Length of the option types in binary form */
+static const uint8_t dhcp_option_lengths[] = {
+       [OPTION_IP]     = 4,
+       [OPTION_STRING] = 1,
+       [OPTION_U8]     = 1,
+       [OPTION_U16]    = 2,
+       [OPTION_U32]    = 4,
+};
+
+uint8_t *dhcp_get_option(struct dhcp_packet *packet, int code);
+int dhcp_end_option(uint8_t *optionptr);
+void dhcp_add_binary_option(struct dhcp_packet *packet, uint8_t *addopt);
+void dhcp_add_simple_option(struct dhcp_packet *packet,
+                               uint8_t code, uint32_t data);
+GDHCPOptionType dhcp_get_code_type(uint8_t code);
+
+uint16_t dhcp_checksum(void *addr, int count);
+
+void dhcp_init_header(struct dhcp_packet *packet, char type);
+
+int dhcp_send_raw_packet(struct dhcp_packet *dhcp_pkt,
+                       uint32_t source_ip, int source_port,
+                       uint32_t dest_ip, int dest_port,
+                       const uint8_t *dest_arp, int ifindex) ;
+int dhcp_send_kernel_packet(struct dhcp_packet *dhcp_pkt,
+                       uint32_t source_ip, int source_port,
+                       uint32_t dest_ip, int dest_port) ;
+int dhcp_l3_socket(int port, const char *interface);
+int dhcp_recv_l3_packet(struct dhcp_packet *packet, int fd);
diff --git a/gdhcp/gdhcp.h b/gdhcp/gdhcp.h
new file mode 100644 (file)
index 0000000..f335f0b
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ *
+ *  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
+ *
+ */
+
+#ifndef __G_DHCP_H
+#define __G_DHCP_H
+
+#include <glib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct _GDHCPClient;
+
+typedef struct _GDHCPClient GDHCPClient;
+
+typedef enum {
+       G_DHCP_CLIENT_ERROR_NONE,
+       G_DHCP_CLIENT_ERROR_INERFACE_UNAVAILABLE,
+       G_DHCP_CLIENT_ERROR_INTERFACE_IN_USE,
+       G_DHCP_CLIENT_ERROR_INTERFACE_DOWN,
+       G_DHCP_CLIENT_ERROR_NOMEM,
+       G_DHCP_CLIENT_ERROR_INVALID_INDEX,
+       G_DHCP_CLIENT_ERROR_INVALID_OPTION
+} GDHCPClientError;
+
+typedef enum {
+       G_DHCP_CLIENT_EVENT_LEASE_AVAILABLE,
+       G_DHCP_CLIENT_EVENT_NO_LEASE,
+       G_DHCP_CLIENT_EVENT_LEASE_LOST,
+       G_DHCP_CLIENT_EVENT_ADDRESS_CONFLICT,
+} GDHCPClientEvent;
+
+typedef enum {
+       G_DHCP_IPV4,
+       G_DHCP_IPV6,
+} GDHCPType;
+
+#define G_DHCP_SUBNET          0x01
+#define G_DHCP_ROUTER          0x03
+#define G_DHCP_TIME_SERVER     0x04
+#define G_DHCP_DNS_SERVER      0x06
+#define G_DHCP_HOST_NAME       0x0c
+#define G_DHCP_NTP_SERVER      0x2a
+
+typedef void (*GDHCPClientEventFunc) (GDHCPClient *client, gpointer user_data);
+
+typedef void (*GDHCPDebugFunc)(const char *str, gpointer user_data);
+
+GDHCPClient *g_dhcp_client_new(GDHCPType type, int index,
+                                               GDHCPClientError *error);
+
+int g_dhcp_client_start(GDHCPClient *client);
+void g_dhcp_client_stop(GDHCPClient *client);
+
+void g_dhcp_client_ref(GDHCPClient *client);
+void g_dhcp_client_unref(GDHCPClient *client);
+
+void g_dhcp_client_register_event(GDHCPClient *client,
+                                       GDHCPClientEvent event,
+                                       GDHCPClientEventFunc func,
+                                       gpointer user_data);
+
+GDHCPClientError g_dhcp_client_set_request(GDHCPClient *client,
+                                               unsigned char option_code);
+GDHCPClientError g_dhcp_client_set_send(GDHCPClient *client,
+                                               unsigned char option_code,
+                                               const char *option_value);
+
+char *g_dhcp_client_get_address(GDHCPClient *client);
+GList *g_dhcp_client_get_option(GDHCPClient *client,
+                                               unsigned char option_code);
+int g_dhcp_client_get_index(GDHCPClient *client);
+
+void g_dhcp_client_set_debug(GDHCPClient *client,
+                                       GDHCPDebugFunc func, gpointer data);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __G_DHCP_H */