summaryrefslogtreecommitdiff
path: root/gdhcp
diff options
context:
space:
mode:
authorMartin Xu <martin.xu@intel.com>2010-11-12 17:45:46 +0100
committerSamuel Ortiz <sameo@linux.intel.com>2010-11-13 23:35:38 +0100
commitb3fb03c5c032d40667a767a301c07ebf4c051d05 (patch)
treec8b6f69c71d73bcbf99629fba4c6c2a6d7874210 /gdhcp
parent5dcabcf4a70d58a862990ba3235882c4e47ebcc4 (diff)
downloadconnman-b3fb03c5c032d40667a767a301c07ebf4c051d05.tar.gz
connman-b3fb03c5c032d40667a767a301c07ebf4c051d05.tar.bz2
connman-b3fb03c5c032d40667a767a301c07ebf4c051d05.zip
gdhcp: Initial DHCP server implementation
Diffstat (limited to 'gdhcp')
-rw-r--r--gdhcp/gdhcp.h41
-rw-r--r--gdhcp/server.c899
2 files changed, 939 insertions, 1 deletions
diff --git a/gdhcp/gdhcp.h b/gdhcp/gdhcp.h
index 59b07ead..052ab90c 100644
--- a/gdhcp/gdhcp.h
+++ b/gdhcp/gdhcp.h
@@ -1,6 +1,6 @@
/*
*
- * DHCP client library with GLib integration
+ * DHCP library with GLib integration
*
* Copyright (C) 2009-2010 Intel Corporation. All rights reserved.
*
@@ -28,6 +28,7 @@
extern "C" {
#endif
+/* DHCP Client part*/
struct _GDHCPClient;
typedef struct _GDHCPClient GDHCPClient;
@@ -94,6 +95,44 @@ int g_dhcp_client_get_index(GDHCPClient *client);
void g_dhcp_client_set_debug(GDHCPClient *client,
GDHCPDebugFunc func, gpointer user_data);
+/* DHCP Server */
+typedef enum {
+ G_DHCP_SERVER_ERROR_NONE,
+ G_DHCP_SERVER_ERROR_INTERFACE_UNAVAILABLE,
+ G_DHCP_SERVER_ERROR_INTERFACE_IN_USE,
+ G_DHCP_SERVER_ERROR_INTERFACE_DOWN,
+ G_DHCP_SERVER_ERROR_NOMEM,
+ G_DHCP_SERVER_ERROR_INVALID_INDEX,
+ G_DHCP_SERVER_ERROR_INVALID_OPTION,
+ G_DHCP_SERVER_ERROR_IP_ADDRESS_INVALID
+} GDHCPServerError;
+
+typedef void (*GDHCPSaveLeaseFunc) (unsigned char *mac,
+ unsigned int nip, unsigned int expire);
+struct _GDHCPServer;
+
+typedef struct _GDHCPServer GDHCPServer;
+
+GDHCPServer *g_dhcp_server_new(GDHCPType type,
+ int ifindex, GDHCPServerError *error);
+int g_dhcp_server_start(GDHCPServer *server);
+void g_dhcp_server_stop(GDHCPServer *server);
+
+GDHCPServer *g_dhcp_server_ref(GDHCPServer *server);
+void g_dhcp_server_unref(GDHCPServer *server);
+
+int g_dhcp_server_set_option(GDHCPServer *server,
+ unsigned char option_code, const char *option_value);
+int g_dhcp_server_set_ip_range(GDHCPServer *server,
+ const char *start_ip, const char *end_ip);
+void g_dhcp_server_load_lease(GDHCPServer *dhcp_server, unsigned int expire,
+ unsigned char *mac, unsigned int lease_ip);
+void g_dhcp_server_set_debug(GDHCPServer *server,
+ GDHCPDebugFunc func, gpointer user_data);
+void g_dhcp_server_set_lease_time(GDHCPServer *dhcp_server,
+ unsigned int lease_time);
+void g_dhcp_server_set_save_lease(GDHCPServer *dhcp_server,
+ GDHCPSaveLeaseFunc func, gpointer user_data);
#ifdef __cplusplus
}
#endif
diff --git a/gdhcp/server.c b/gdhcp/server.c
new file mode 100644
index 00000000..7322e6e2
--- /dev/null
+++ b/gdhcp/server.c
@@ -0,0 +1,899 @@
+/*
+ *
+ * DHCP Server 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 "common.h"
+
+/* 8 hours */
+#define DEFAULT_DHCP_LEASE_SEC (8*60*60)
+
+/* 5 minutes */
+#define OFFER_TIME (5*60)
+
+struct _GDHCPServer {
+ gint ref_count;
+ GDHCPType type;
+ gboolean started;
+ int ifindex;
+ char *interface;
+ uint32_t start_ip;
+ uint32_t end_ip;
+ uint32_t server_nip;
+ uint32_t lease_seconds;
+ int listener_sockfd;
+ guint listener_watch;
+ GIOChannel *listener_channel;
+ GList *lease_list;
+ GHashTable *nip_lease_hash;
+ GHashTable *option_hash; /* Options send to client */
+ GDHCPSaveLeaseFunc save_lease_func;
+ GDHCPDebugFunc debug_func;
+ gpointer debug_data;
+};
+
+struct dhcp_lease {
+ time_t expire;
+ uint32_t lease_nip;
+ uint8_t lease_mac[ETH_ALEN];
+};
+
+static inline void debug(GDHCPServer *server, const char *format, ...)
+{
+ char str[256];
+ va_list ap;
+
+ if (server->debug_func == NULL)
+ return;
+
+ va_start(ap, format);
+
+ if (vsnprintf(str, sizeof(str), format, ap) > 0)
+ server->debug_func(str, server->debug_data);
+
+ va_end(ap);
+}
+
+static struct dhcp_lease *find_lease_by_mac(GDHCPServer *dhcp_server,
+ const uint8_t *mac)
+{
+ GList *list;
+
+ for (list = dhcp_server->lease_list; list; list = list->next) {
+ struct dhcp_lease *lease = list->data;
+
+ if (memcmp(lease->lease_mac, mac, ETH_ALEN) == 0)
+ return lease;
+ }
+
+ return NULL;
+}
+
+static void remove_lease(GDHCPServer *dhcp_server, struct dhcp_lease *lease)
+{
+ dhcp_server->lease_list =
+ g_list_remove(dhcp_server->lease_list, lease);
+
+ g_hash_table_remove(dhcp_server->nip_lease_hash,
+ GINT_TO_POINTER((int) lease->lease_nip));
+ g_free(lease);
+}
+
+/* Clear the old lease and create the new one */
+static int get_lease(GDHCPServer *dhcp_server, uint32_t yiaddr,
+ const uint8_t *mac, struct dhcp_lease **lease)
+{
+ struct dhcp_lease *lease_nip, *lease_mac;
+
+ if (yiaddr == 0)
+ return -ENXIO;
+
+ if (ntohl(yiaddr) < dhcp_server->start_ip)
+ return -ENXIO;
+
+ if (ntohl(yiaddr) > dhcp_server->end_ip)
+ return -ENXIO;
+
+ if (memcmp(mac, MAC_BCAST_ADDR, ETH_ALEN) == 0)
+ return -ENXIO;
+
+ if (memcmp(mac, MAC_ANY_ADDR, ETH_ALEN) == 0)
+ return -ENXIO;
+
+ lease_mac = find_lease_by_mac(dhcp_server, mac);
+
+ lease_nip = g_hash_table_lookup(dhcp_server->nip_lease_hash,
+ GINT_TO_POINTER((int) yiaddr));
+ debug(dhcp_server, "lease_mac %p lease_nip %p", lease_mac, lease_nip);
+
+ if (lease_nip != NULL) {
+ dhcp_server->lease_list =
+ g_list_remove(dhcp_server->lease_list,
+ lease_nip);
+ g_hash_table_remove(dhcp_server->nip_lease_hash,
+ GINT_TO_POINTER((int) yiaddr));
+
+ if (lease_mac == NULL)
+ *lease = lease_nip;
+ else if (lease_nip != lease_mac) {
+ remove_lease(dhcp_server, lease_mac);
+ *lease = lease_nip;
+ } else
+ *lease = lease_nip;
+
+ return 0;
+ }
+
+ if (lease_mac != NULL) {
+ dhcp_server->lease_list =
+ g_list_remove(dhcp_server->lease_list,
+ lease_mac);
+ g_hash_table_remove(dhcp_server->nip_lease_hash,
+ GINT_TO_POINTER((int) lease_mac->lease_nip));
+ *lease = lease_mac;
+
+ return 0;
+ }
+
+ *lease = g_try_new0(struct dhcp_lease, 1);
+ if (*lease == NULL)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static gint compare_expire(gconstpointer a, gconstpointer b)
+{
+ const struct dhcp_lease *lease1 = a;
+ const struct dhcp_lease *lease2 = b;
+
+ return lease2->expire - lease1->expire;
+}
+
+static struct dhcp_lease *add_lease(GDHCPServer *dhcp_server, uint32_t expire,
+ const uint8_t *chaddr, uint32_t yiaddr)
+{
+ struct dhcp_lease *lease = NULL;
+ int ret;
+
+ ret = get_lease(dhcp_server, yiaddr, chaddr, &lease);
+ if (ret != 0)
+ return NULL;
+
+ memset(lease, 0, sizeof(*lease));
+
+ memcpy(lease->lease_mac, chaddr, ETH_ALEN);
+ lease->lease_nip = yiaddr;
+
+ if (expire == 0)
+ lease->expire = time(NULL) + dhcp_server->lease_seconds;
+ else
+ lease->expire = expire;
+
+ dhcp_server->lease_list = g_list_insert_sorted(dhcp_server->lease_list,
+ lease, compare_expire);
+
+ g_hash_table_insert(dhcp_server->nip_lease_hash,
+ GINT_TO_POINTER((int) lease->lease_nip), lease);
+
+ return lease;
+}
+
+static struct dhcp_lease *find_lease_by_nip(GDHCPServer *dhcp_server,
+ uint32_t nip)
+{
+ return g_hash_table_lookup(dhcp_server->nip_lease_hash,
+ GINT_TO_POINTER((int) nip));
+}
+
+/* Check if the IP is taken; if it is, add it to the lease table */
+static gboolean arp_check(uint32_t nip, const uint8_t *safe_mac)
+{
+ /* TODO: Add ARP checking */
+ return TRUE;
+}
+
+static gboolean is_expired_lease(struct dhcp_lease *lease)
+{
+ if (lease->expire < time(NULL))
+ return TRUE;
+
+ return FALSE;
+}
+
+static uint32_t find_free_or_expired_nip(GDHCPServer *dhcp_server,
+ const uint8_t *safe_mac)
+{
+ uint32_t ip_addr;
+ struct dhcp_lease *lease;
+ GList *list;
+ ip_addr = dhcp_server->start_ip;
+ for (; ip_addr <= dhcp_server->end_ip; ip_addr++) {
+ /* e.g. 192.168.55.0 */
+ if ((ip_addr & 0xff) == 0)
+ continue;
+
+ /* e.g. 192.168.55.255 */
+ if ((ip_addr & 0xff) == 0xff)
+ continue;
+
+ lease = find_lease_by_nip(dhcp_server,
+ (uint32_t) htonl(ip_addr));
+ if (lease != NULL)
+ continue;
+
+ if (arp_check(htonl(ip_addr), safe_mac) == TRUE)
+ return htonl(ip_addr);
+ }
+
+ /* The last lease is the oldest one */
+ list = g_list_last(dhcp_server->lease_list);
+ if (list == NULL)
+ return 0;
+
+ lease = list->data;
+ if (lease == NULL)
+ return 0;
+
+ if (is_expired_lease(lease) == FALSE)
+ return 0;
+
+ if (arp_check(lease->lease_nip, safe_mac) == FALSE)
+ return 0;
+
+ return lease->lease_nip;
+}
+
+static void lease_set_expire(GDHCPServer *dhcp_server,
+ struct dhcp_lease *lease, uint32_t expire)
+{
+ dhcp_server->lease_list = g_list_remove(dhcp_server->lease_list, lease);
+
+ lease->expire = expire;
+
+ dhcp_server->lease_list = g_list_insert_sorted(dhcp_server->lease_list,
+ lease, compare_expire);
+}
+
+static void distroy_lease_table(GDHCPServer *dhcp_server)
+{
+ GList *list;
+
+ g_hash_table_destroy(dhcp_server->nip_lease_hash);
+
+ dhcp_server->nip_lease_hash = NULL;
+
+ for (list = dhcp_server->lease_list; list; list = list->next) {
+ struct dhcp_lease *lease = list->data;
+
+ g_free(lease);
+ }
+
+ g_list_free(dhcp_server->lease_list);
+
+ dhcp_server->lease_list = NULL;
+}
+static uint32_t get_interface_address(int index)
+{
+ struct ifreq ifr;
+ int sk, err;
+ struct sockaddr_in *server_ip;
+ uint32_t ret = 0;
+
+ sk = socket(PF_INET, SOCK_DGRAM, 0);
+ if (sk < 0) {
+ perror("Open socket error");
+ return 0;
+ }
+
+ 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, SIOCGIFADDR, &ifr);
+ if (err < 0) {
+ perror("Get ip address error");
+ goto done;
+ }
+
+ server_ip = (struct sockaddr_in *) &ifr.ifr_addr;
+ ret = server_ip->sin_addr.s_addr;
+
+done:
+ close(sk);
+
+ return ret;
+}
+
+GDHCPServer *g_dhcp_server_new(GDHCPType type,
+ int ifindex, GDHCPServerError *error)
+{
+ GDHCPServer *dhcp_server = NULL;
+
+ if (ifindex < 0) {
+ *error = G_DHCP_SERVER_ERROR_INVALID_INDEX;
+ return NULL;
+ }
+
+ dhcp_server = g_try_new0(GDHCPServer, 1);
+ if (dhcp_server == NULL) {
+ *error = G_DHCP_SERVER_ERROR_NOMEM;
+ return NULL;
+ }
+
+ dhcp_server->interface = get_interface_name(ifindex);
+ if (dhcp_server->interface == NULL) {
+ *error = G_DHCP_SERVER_ERROR_INTERFACE_UNAVAILABLE;
+ goto error;
+ }
+
+ if (interface_is_up(ifindex) == FALSE) {
+ *error = G_DHCP_SERVER_ERROR_INTERFACE_DOWN;
+ goto error;
+ }
+
+ dhcp_server->server_nip = get_interface_address(ifindex);
+ if (dhcp_server->server_nip == 0) {
+ *error = G_DHCP_SERVER_ERROR_IP_ADDRESS_INVALID;
+ goto error;
+ }
+
+ dhcp_server->nip_lease_hash = g_hash_table_new_full(g_direct_hash,
+ g_direct_equal, NULL, NULL);
+ dhcp_server->option_hash = g_hash_table_new_full(g_direct_hash,
+ g_direct_equal, NULL, NULL);
+
+ dhcp_server->started = FALSE;
+
+ /* All the leases have the same fixed lease time,
+ * do not support DHCP_LEASE_TIME option from client.
+ */
+ dhcp_server->lease_seconds = DEFAULT_DHCP_LEASE_SEC;
+
+ dhcp_server->type = type;
+ dhcp_server->ref_count = 1;
+ dhcp_server->ifindex = ifindex;
+ dhcp_server->listener_sockfd = -1;
+ dhcp_server->listener_watch = -1;
+ dhcp_server->listener_channel = NULL;
+ dhcp_server->save_lease_func = NULL;
+ dhcp_server->debug_func = NULL;
+ dhcp_server->debug_data = NULL;
+
+ *error = G_DHCP_SERVER_ERROR_NONE;
+
+ return dhcp_server;
+
+error:
+ g_free(dhcp_server->interface);
+ g_free(dhcp_server);
+ return NULL;
+}
+
+
+static uint8_t check_packet_type(struct dhcp_packet *packet)
+{
+ uint8_t *type;
+
+ if (packet->hlen != ETH_ALEN)
+ return 0;
+
+ if (packet->op != BOOTREQUEST)
+ return 0;
+
+ type = dhcp_get_option(packet, DHCP_MESSAGE_TYPE);
+
+ if (type == NULL)
+ return 0;
+
+ if (*type < DHCP_MINTYPE)
+ return 0;
+
+ if (*type > DHCP_MAXTYPE)
+ return 0;
+
+ return *type;
+}
+
+static void init_packet(GDHCPServer *dhcp_server, struct dhcp_packet *packet,
+ struct dhcp_packet *client_packet, char type)
+{
+ /* Sets op, htype, hlen, cookie fields
+ * and adds DHCP_MESSAGE_TYPE option */
+ dhcp_init_header(packet, type);
+
+ packet->xid = client_packet->xid;
+ memcpy(packet->chaddr, client_packet->chaddr,
+ sizeof(client_packet->chaddr));
+ packet->flags = client_packet->flags;
+ packet->gateway_nip = client_packet->gateway_nip;
+ packet->ciaddr = client_packet->ciaddr;
+ dhcp_add_simple_option(packet, DHCP_SERVER_ID, dhcp_server->server_nip);
+}
+
+static void add_option(gpointer key, gpointer value, gpointer user_data)
+{
+ const char *option_value = value;
+ uint8_t option_code = GPOINTER_TO_INT(key);
+ struct in_addr nip;
+ struct dhcp_packet *packet = user_data;
+
+ if (option_value == NULL)
+ return;
+
+ switch (option_code) {
+ case G_DHCP_SUBNET:
+ case G_DHCP_ROUTER:
+ case G_DHCP_DNS_SERVER:
+ if (inet_aton(option_value, &nip) == 0)
+ return;
+
+ dhcp_add_simple_option(packet, (uint8_t) option_code,
+ nip.s_addr);
+ break;
+ default:
+ return;
+ }
+}
+
+static void add_server_options(GDHCPServer *dhcp_server,
+ struct dhcp_packet *packet)
+{
+ g_hash_table_foreach(dhcp_server->option_hash,
+ add_option, packet);
+}
+
+static gboolean check_requested_nip(GDHCPServer *dhcp_server,
+ uint32_t requested_nip)
+{
+ struct dhcp_lease *lease;
+
+ if (requested_nip == 0)
+ return FALSE;
+
+ if (ntohl(requested_nip) < dhcp_server->start_ip)
+ return FALSE;
+
+ if (ntohl(requested_nip) > dhcp_server->end_ip)
+ return FALSE;
+
+ lease = find_lease_by_nip(dhcp_server, requested_nip);
+ if (lease == NULL)
+ return TRUE;
+
+ if (is_expired_lease(lease) == FALSE)
+ return FALSE;
+
+ return TRUE;
+}
+
+static void send_packet_to_client(GDHCPServer *dhcp_server,
+ struct dhcp_packet *dhcp_pkt)
+{
+ const uint8_t *chaddr;
+ uint32_t ciaddr;
+
+ if ((dhcp_pkt->flags & htons(BROADCAST_FLAG))
+ || dhcp_pkt->ciaddr == 0) {
+ debug(dhcp_server, "Broadcasting packet to client");
+ ciaddr = INADDR_BROADCAST;
+ chaddr = MAC_BCAST_ADDR;
+ } else {
+ debug(dhcp_server, "Unicasting packet to client ciaddr");
+ ciaddr = dhcp_pkt->ciaddr;
+ chaddr = dhcp_pkt->chaddr;
+ }
+
+ dhcp_send_raw_packet(dhcp_pkt,
+ dhcp_server->server_nip, SERVER_PORT,
+ ciaddr, CLIENT_PORT, chaddr,
+ dhcp_server->ifindex);
+}
+
+static void send_offer(GDHCPServer *dhcp_server,
+ struct dhcp_packet *client_packet,
+ struct dhcp_lease *lease,
+ uint32_t requested_nip)
+{
+ struct dhcp_packet packet;
+ struct in_addr addr;
+
+ init_packet(dhcp_server, &packet, client_packet, DHCPOFFER);
+
+ if (lease)
+ packet.yiaddr = lease->lease_nip;
+ else if (check_requested_nip(dhcp_server, requested_nip) == TRUE)
+ packet.yiaddr = requested_nip;
+ else
+ packet.yiaddr = find_free_or_expired_nip(
+ dhcp_server, client_packet->chaddr);
+
+ debug(dhcp_server, "find yiaddr %u", packet.yiaddr);
+
+ if (!packet.yiaddr) {
+ debug(dhcp_server, "Err: Can not found lease and send offer");
+ return;
+ }
+
+ lease = add_lease(dhcp_server, OFFER_TIME,
+ packet.chaddr, packet.yiaddr);
+ if (lease == NULL) {
+ debug(dhcp_server,
+ "Err: No free IP addresses. OFFER abandoned");
+ return;
+ }
+
+ dhcp_add_simple_option(&packet, DHCP_LEASE_TIME,
+ htonl(dhcp_server->lease_seconds));
+ add_server_options(dhcp_server, &packet);
+
+ addr.s_addr = packet.yiaddr;
+
+ debug(dhcp_server, "Sending OFFER of %s", inet_ntoa(addr));
+ send_packet_to_client(dhcp_server, &packet);
+}
+
+static void save_lease(GDHCPServer *dhcp_server)
+{
+ GList *list;
+
+ if (dhcp_server->save_lease_func == NULL)
+ return;
+
+ for (list = dhcp_server->lease_list; list; list = list->next) {
+ struct dhcp_lease *lease = list->data;
+ dhcp_server->save_lease_func(lease->lease_mac,
+ lease->lease_nip, lease->expire);
+ }
+}
+
+static void send_ACK(GDHCPServer *dhcp_server,
+ struct dhcp_packet *client_packet, uint32_t yiaddr)
+{
+ struct dhcp_packet packet;
+ uint32_t lease_time_sec;
+ struct in_addr addr;
+
+ init_packet(dhcp_server, &packet, client_packet, DHCPACK);
+ packet.yiaddr = yiaddr;
+
+ lease_time_sec = dhcp_server->lease_seconds;
+
+ dhcp_add_simple_option(&packet, DHCP_LEASE_TIME, htonl(lease_time_sec));
+
+ add_server_options(dhcp_server, &packet);
+
+ addr.s_addr = yiaddr;
+
+ debug(dhcp_server, "Sending ACK to %s", inet_ntoa(addr));
+
+ send_packet_to_client(dhcp_server, &packet);
+
+ add_lease(dhcp_server, 0, packet.chaddr, packet.yiaddr);
+}
+
+static void send_NAK(GDHCPServer *dhcp_server,
+ struct dhcp_packet *client_packet)
+{
+ struct dhcp_packet packet;
+
+ init_packet(dhcp_server, &packet, client_packet, DHCPNAK);
+
+ debug(dhcp_server, "Sending NAK");
+
+ dhcp_send_raw_packet(&packet,
+ dhcp_server->server_nip, SERVER_PORT,
+ INADDR_BROADCAST, CLIENT_PORT, MAC_BCAST_ADDR,
+ dhcp_server->ifindex);
+}
+
+static void send_inform(GDHCPServer *dhcp_server,
+ struct dhcp_packet *client_packet)
+{
+ struct dhcp_packet packet;
+
+ init_packet(dhcp_server, &packet, client_packet, DHCPACK);
+ add_server_options(dhcp_server, &packet);
+ send_packet_to_client(dhcp_server, &packet);
+}
+
+static gboolean listener_event(GIOChannel *channel, GIOCondition condition,
+ gpointer user_data)
+{
+ GDHCPServer *dhcp_server = user_data;
+ struct dhcp_packet packet;
+ struct dhcp_lease *lease;
+ uint32_t requested_nip = 0;
+ uint8_t type, *server_id_option, *request_ip_option;
+ int re;
+
+ if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
+ dhcp_server->listener_watch = 0;
+ return FALSE;
+ }
+
+ re = dhcp_recv_l3_packet(&packet, dhcp_server->listener_sockfd);
+ if (re < 0)
+ return TRUE;
+
+ type = check_packet_type(&packet);
+ if (type == 0)
+ return TRUE;
+
+ server_id_option = dhcp_get_option(&packet, DHCP_SERVER_ID);
+ if (server_id_option) {
+ uint32_t server_nid = dhcp_get_unaligned(
+ (uint32_t *) server_id_option);
+
+ if (server_nid != dhcp_server->server_nip)
+ return TRUE;
+ }
+
+ request_ip_option = dhcp_get_option(&packet, DHCP_REQUESTED_IP);
+ if (request_ip_option)
+ requested_nip = dhcp_get_unaligned(
+ (uint32_t *) request_ip_option);
+
+ lease = find_lease_by_mac(dhcp_server, packet.chaddr);
+
+ switch (type) {
+ case DHCPDISCOVER:
+ debug(dhcp_server, "Received DISCOVER");
+
+ send_offer(dhcp_server, &packet, lease, requested_nip);
+ break;
+ case DHCPREQUEST:
+ debug(dhcp_server, "Received REQUEST");
+ if (requested_nip == 0) {
+ requested_nip = packet.ciaddr;
+ if (requested_nip == 0)
+ break;
+ }
+
+ if (lease && requested_nip == lease->lease_nip) {
+ send_ACK(dhcp_server, &packet,
+ lease->lease_nip);
+ break;
+ }
+
+ if (server_id_option)
+ send_NAK(dhcp_server, &packet);
+
+ break;
+ case DHCPDECLINE:
+ debug(dhcp_server, "Received DECLINE");
+
+ if (server_id_option == NULL)
+ break;
+
+ if (request_ip_option == NULL)
+ break;
+
+ if (lease == NULL)
+ break;
+
+ if (requested_nip == lease->lease_nip)
+ remove_lease(dhcp_server, lease);
+
+ break;
+ case DHCPRELEASE:
+ debug(dhcp_server, "Received RELEASE");
+
+ if (server_id_option == NULL)
+ break;
+
+ if (lease == NULL)
+ break;
+
+ if (packet.ciaddr == lease->lease_nip)
+ lease_set_expire(dhcp_server, lease,
+ time(NULL));
+ break;
+ case DHCPINFORM:
+ debug(dhcp_server, "Received INFORM");
+ send_inform(dhcp_server, &packet);
+ break;
+ }
+
+ return TRUE;
+}
+
+/* Caller need to load leases before call it */
+int g_dhcp_server_start(GDHCPServer *dhcp_server)
+{
+ GIOChannel *listener_channel;
+ int listener_sockfd;
+
+ if (dhcp_server->started == TRUE)
+ return 0;
+
+ listener_sockfd = dhcp_l3_socket(SERVER_PORT,
+ dhcp_server->interface);
+ if (listener_sockfd < 0)
+ return -EIO;
+
+ listener_channel = g_io_channel_unix_new(listener_sockfd);
+ if (listener_channel == NULL) {
+ close(listener_sockfd);
+ return -EIO;
+ }
+
+ dhcp_server->listener_sockfd = listener_sockfd;
+ dhcp_server->listener_channel = listener_channel;
+
+ g_io_channel_set_close_on_unref(listener_channel, TRUE);
+ dhcp_server->listener_watch =
+ g_io_add_watch_full(listener_channel,
+ G_PRIORITY_HIGH, G_IO_IN,
+ listener_event, dhcp_server,
+ NULL);
+ g_io_channel_unref(dhcp_server->listener_channel);
+
+ dhcp_server->started = TRUE;
+
+ return 0;
+}
+
+int g_dhcp_server_set_option(GDHCPServer *dhcp_server,
+ unsigned char option_code, const char *option_value)
+{
+ struct in_addr nip;
+
+ if (option_value == NULL)
+ return -EINVAL;
+
+ debug(dhcp_server, "option_code %d option_value %s",
+ option_code, option_value);
+ switch (option_code) {
+ case G_DHCP_SUBNET:
+ case G_DHCP_ROUTER:
+ case G_DHCP_DNS_SERVER:
+ if (inet_aton(option_value, &nip) == 0)
+ return -ENXIO;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ g_hash_table_replace(dhcp_server->option_hash,
+ GINT_TO_POINTER((int) option_code),
+ (gpointer) option_value);
+ return 0;
+}
+
+void g_dhcp_server_set_save_lease(GDHCPServer *dhcp_server,
+ GDHCPSaveLeaseFunc func, gpointer user_data)
+{
+ if (dhcp_server == NULL)
+ return;
+
+ dhcp_server->save_lease_func = func;
+}
+
+GDHCPServer *g_dhcp_server_ref(GDHCPServer *dhcp_server)
+{
+ if (dhcp_server == NULL)
+ return NULL;
+
+ g_atomic_int_inc(&dhcp_server->ref_count);
+
+ return dhcp_server;
+}
+
+void g_dhcp_server_stop(GDHCPServer *dhcp_server)
+{
+ /* Save leases, before stop; load them before start */
+ save_lease(dhcp_server);
+
+ if (dhcp_server->listener_watch > 0) {
+ g_source_remove(dhcp_server->listener_watch);
+ dhcp_server->listener_watch = 0;
+ }
+
+ dhcp_server->listener_channel = NULL;
+
+ dhcp_server->started = FALSE;
+}
+
+void g_dhcp_server_unref(GDHCPServer *dhcp_server)
+{
+ if (dhcp_server == NULL)
+ return;
+
+ if (g_atomic_int_dec_and_test(&dhcp_server->ref_count) == FALSE)
+ return;
+
+ g_dhcp_server_stop(dhcp_server);
+
+ g_hash_table_destroy(dhcp_server->option_hash);
+
+ distroy_lease_table(dhcp_server);
+
+ g_free(dhcp_server->interface);
+
+ g_free(dhcp_server);
+}
+
+void g_dhcp_server_load_lease(GDHCPServer *dhcp_server, unsigned int expire,
+ unsigned char *mac, unsigned int lease_ip)
+{
+ add_lease(dhcp_server, expire, mac, lease_ip);
+}
+
+int g_dhcp_server_set_ip_range(GDHCPServer *dhcp_server,
+ const char *start_ip, const char *end_ip)
+{
+ struct in_addr _host_addr;
+
+ if (inet_aton(start_ip, &_host_addr) == 0)
+ return -ENXIO;
+
+ dhcp_server->start_ip = ntohl(_host_addr.s_addr);
+
+ if (inet_aton(end_ip, &_host_addr) == 0)
+ return -ENXIO;
+
+ dhcp_server->end_ip = ntohl(_host_addr.s_addr);
+
+ return 0;
+}
+
+void g_dhcp_server_set_lease_time(GDHCPServer *dhcp_server, unsigned int lease_time)
+{
+ if (dhcp_server == NULL)
+ return;
+
+ dhcp_server->lease_seconds = lease_time;
+}
+
+void g_dhcp_server_set_debug(GDHCPServer *dhcp_server,
+ GDHCPDebugFunc func, gpointer user_data)
+{
+ if (dhcp_server == NULL)
+ return;
+
+ dhcp_server->debug_func = func;
+ dhcp_server->debug_data = user_data;
+}