|  | /* | 
|  | * | 
|  | *  DHCP Server library with GLib integration | 
|  | * | 
|  | *  Copyright (C) 2009-2012  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 { | 
|  | int ref_count; | 
|  | GDHCPType type; | 
|  | bool started; | 
|  | int ifindex; | 
|  | char *interface; | 
|  | uint32_t start_ip; | 
|  | uint32_t end_ip; | 
|  | uint32_t server_nip;	/* our address in network byte order */ | 
|  | 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; | 
|  | GDHCPLeaseAddedCb lease_added_cb; | 
|  | 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) | 
|  | 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) ntohl(yiaddr))); | 
|  | debug(dhcp_server, "lease_mac %p lease_nip %p", lease_mac, lease_nip); | 
|  |  | 
|  | if (lease_nip) { | 
|  | 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) ntohl(yiaddr))); | 
|  |  | 
|  | if (!lease_mac) | 
|  | *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) { | 
|  | 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) | 
|  | 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 = ntohl(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); | 
|  |  | 
|  | if (dhcp_server->lease_added_cb) | 
|  | dhcp_server->lease_added_cb(lease->lease_mac, yiaddr); | 
|  |  | 
|  | 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 bool arp_check(uint32_t nip, const uint8_t *safe_mac) | 
|  | { | 
|  | /* TODO: Add ARP checking */ | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool 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, ip_addr); | 
|  | if (lease) | 
|  | continue; | 
|  |  | 
|  | if (arp_check(htonl(ip_addr), safe_mac)) | 
|  | return ip_addr; | 
|  | } | 
|  |  | 
|  | /* The last lease is the oldest one */ | 
|  | list = g_list_last(dhcp_server->lease_list); | 
|  | if (!list) | 
|  | return 0; | 
|  |  | 
|  | lease = list->data; | 
|  | if (!lease) | 
|  | return 0; | 
|  |  | 
|  | if (!is_expired_lease(lease)) | 
|  | return 0; | 
|  |  | 
|  | if (!arp_check(lease->lease_nip, safe_mac)) | 
|  | 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 destroy_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 | SOCK_CLOEXEC, 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) { | 
|  | *error = G_DHCP_SERVER_ERROR_NOMEM; | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | dhcp_server->interface = get_interface_name(ifindex); | 
|  | if (!dhcp_server->interface) { | 
|  | *error = G_DHCP_SERVER_ERROR_INTERFACE_UNAVAILABLE; | 
|  | goto error; | 
|  | } | 
|  |  | 
|  | if (!interface_is_up(ifindex)) { | 
|  | *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) | 
|  | 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_option_uint32(packet, DHCP_SERVER_ID, | 
|  | ntohl(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) | 
|  | 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_option_uint32(packet, (uint8_t) option_code, | 
|  | ntohl(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 bool check_requested_nip(GDHCPServer *dhcp_server, | 
|  | uint32_t requested_nip) | 
|  | { | 
|  | struct dhcp_lease *lease; | 
|  |  | 
|  | if (requested_nip == 0) | 
|  | return false; | 
|  |  | 
|  | if (requested_nip < dhcp_server->start_ip) | 
|  | return false; | 
|  |  | 
|  | if (requested_nip > dhcp_server->end_ip) | 
|  | return false; | 
|  |  | 
|  | lease = find_lease_by_nip(dhcp_server, requested_nip); | 
|  | if (!lease) | 
|  | return true; | 
|  |  | 
|  | if (!is_expired_lease(lease)) | 
|  | 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, false); | 
|  | } | 
|  |  | 
|  | 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 = htonl(lease->lease_nip); | 
|  | else if (check_requested_nip(dhcp_server, requested_nip)) | 
|  | packet.yiaddr = htonl(requested_nip); | 
|  | else | 
|  | packet.yiaddr = htonl(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) { | 
|  | debug(dhcp_server, | 
|  | "Err: No free IP addresses. OFFER abandoned"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | dhcp_add_option_uint32(&packet, DHCP_LEASE_TIME, | 
|  | 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) | 
|  | 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 dest) | 
|  | { | 
|  | struct dhcp_packet packet; | 
|  | uint32_t lease_time_sec; | 
|  | struct in_addr addr; | 
|  |  | 
|  | init_packet(dhcp_server, &packet, client_packet, DHCPACK); | 
|  | packet.yiaddr = htonl(dest); | 
|  |  | 
|  | lease_time_sec = dhcp_server->lease_seconds; | 
|  |  | 
|  | dhcp_add_option_uint32(&packet, DHCP_LEASE_TIME, lease_time_sec); | 
|  |  | 
|  | add_server_options(dhcp_server, &packet); | 
|  |  | 
|  | addr.s_addr = htonl(dest); | 
|  |  | 
|  | 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, false); | 
|  | } | 
|  |  | 
|  | 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 = | 
|  | get_unaligned((const 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 = get_be32(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 NIP %d", | 
|  | requested_nip); | 
|  | if (requested_nip == 0) { | 
|  | requested_nip = packet.ciaddr; | 
|  | if (requested_nip == 0) | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (lease && requested_nip == lease->lease_nip) { | 
|  | debug(dhcp_server, "Sending ACK"); | 
|  | send_ACK(dhcp_server, &packet, | 
|  | lease->lease_nip); | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (server_id_option || !lease) { | 
|  | debug(dhcp_server, "Sending NAK"); | 
|  | send_NAK(dhcp_server, &packet); | 
|  | } | 
|  |  | 
|  | break; | 
|  | case DHCPDECLINE: | 
|  | debug(dhcp_server, "Received DECLINE"); | 
|  |  | 
|  | if (!server_id_option) | 
|  | break; | 
|  |  | 
|  | if (!request_ip_option) | 
|  | break; | 
|  |  | 
|  | if (!lease) | 
|  | break; | 
|  |  | 
|  | if (requested_nip == lease->lease_nip) | 
|  | remove_lease(dhcp_server, lease); | 
|  |  | 
|  | break; | 
|  | case DHCPRELEASE: | 
|  | debug(dhcp_server, "Received RELEASE"); | 
|  |  | 
|  | if (!server_id_option) | 
|  | break; | 
|  |  | 
|  | if (!lease) | 
|  | 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) | 
|  | return 0; | 
|  |  | 
|  | listener_sockfd = dhcp_l3_socket(SERVER_PORT, | 
|  | dhcp_server->interface, AF_INET); | 
|  | if (listener_sockfd < 0) | 
|  | return -EIO; | 
|  |  | 
|  | listener_channel = g_io_channel_unix_new(listener_sockfd); | 
|  | if (!listener_channel) { | 
|  | 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 | G_IO_NVAL | G_IO_ERR | G_IO_HUP, | 
|  | 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) | 
|  | 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) | 
|  | return; | 
|  |  | 
|  | dhcp_server->save_lease_func = func; | 
|  | } | 
|  |  | 
|  | void g_dhcp_server_set_lease_added_cb(GDHCPServer *dhcp_server, | 
|  | GDHCPLeaseAddedCb cb) | 
|  | { | 
|  | if (!dhcp_server) | 
|  | return; | 
|  |  | 
|  | dhcp_server->lease_added_cb = cb; | 
|  | } | 
|  |  | 
|  | GDHCPServer *g_dhcp_server_ref(GDHCPServer *dhcp_server) | 
|  | { | 
|  | if (!dhcp_server) | 
|  | return NULL; | 
|  |  | 
|  | __sync_fetch_and_add(&dhcp_server->ref_count, 1); | 
|  |  | 
|  | 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) | 
|  | return; | 
|  |  | 
|  | if (__sync_fetch_and_sub(&dhcp_server->ref_count, 1) != 1) | 
|  | return; | 
|  |  | 
|  | g_dhcp_server_stop(dhcp_server); | 
|  |  | 
|  | g_hash_table_destroy(dhcp_server->option_hash); | 
|  |  | 
|  | destroy_lease_table(dhcp_server); | 
|  |  | 
|  | g_free(dhcp_server->interface); | 
|  |  | 
|  | g_free(dhcp_server); | 
|  | } | 
|  |  | 
|  | 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) | 
|  | return; | 
|  |  | 
|  | dhcp_server->lease_seconds = lease_time; | 
|  | } | 
|  |  | 
|  | void g_dhcp_server_set_debug(GDHCPServer *dhcp_server, | 
|  | GDHCPDebugFunc func, gpointer user_data) | 
|  | { | 
|  | if (!dhcp_server) | 
|  | return; | 
|  |  | 
|  | dhcp_server->debug_func = func; | 
|  | dhcp_server->debug_data = user_data; | 
|  | } |