3 * DHCP Server library with GLib integration
5 * Copyright (C) 2009-2012 Intel Corporation. All rights reserved.
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
31 #include <sys/ioctl.h>
32 #include <arpa/inet.h>
34 #include <netpacket/packet.h>
35 #include <net/ethernet.h>
36 #include <net/if_arp.h>
39 #include <linux/filter.h>
46 #define DEFAULT_DHCP_LEASE_SEC (8*60*60)
49 #define OFFER_TIME (5*60)
60 uint32_t lease_seconds;
63 GIOChannel *listener_channel;
65 GHashTable *nip_lease_hash;
66 GHashTable *option_hash; /* Options send to client */
67 GDHCPSaveLeaseFunc save_lease_func;
68 GDHCPDebugFunc debug_func;
75 uint8_t lease_mac[ETH_ALEN];
78 static inline void debug(GDHCPServer *server, const char *format, ...)
83 if (server->debug_func == NULL)
88 if (vsnprintf(str, sizeof(str), format, ap) > 0)
89 server->debug_func(str, server->debug_data);
94 static struct dhcp_lease *find_lease_by_mac(GDHCPServer *dhcp_server,
99 for (list = dhcp_server->lease_list; list; list = list->next) {
100 struct dhcp_lease *lease = list->data;
102 if (memcmp(lease->lease_mac, mac, ETH_ALEN) == 0)
109 static void remove_lease(GDHCPServer *dhcp_server, struct dhcp_lease *lease)
111 dhcp_server->lease_list =
112 g_list_remove(dhcp_server->lease_list, lease);
114 g_hash_table_remove(dhcp_server->nip_lease_hash,
115 GINT_TO_POINTER((int) lease->lease_nip));
119 /* Clear the old lease and create the new one */
120 static int get_lease(GDHCPServer *dhcp_server, uint32_t yiaddr,
121 const uint8_t *mac, struct dhcp_lease **lease)
123 struct dhcp_lease *lease_nip, *lease_mac;
128 if (ntohl(yiaddr) < dhcp_server->start_ip)
131 if (ntohl(yiaddr) > dhcp_server->end_ip)
134 if (memcmp(mac, MAC_BCAST_ADDR, ETH_ALEN) == 0)
137 if (memcmp(mac, MAC_ANY_ADDR, ETH_ALEN) == 0)
140 lease_mac = find_lease_by_mac(dhcp_server, mac);
142 lease_nip = g_hash_table_lookup(dhcp_server->nip_lease_hash,
143 GINT_TO_POINTER((int) ntohl(yiaddr)));
144 debug(dhcp_server, "lease_mac %p lease_nip %p", lease_mac, lease_nip);
146 if (lease_nip != NULL) {
147 dhcp_server->lease_list =
148 g_list_remove(dhcp_server->lease_list,
150 g_hash_table_remove(dhcp_server->nip_lease_hash,
151 GINT_TO_POINTER((int) ntohl(yiaddr)));
153 if (lease_mac == NULL)
155 else if (lease_nip != lease_mac) {
156 remove_lease(dhcp_server, lease_mac);
164 if (lease_mac != NULL) {
165 dhcp_server->lease_list =
166 g_list_remove(dhcp_server->lease_list,
168 g_hash_table_remove(dhcp_server->nip_lease_hash,
169 GINT_TO_POINTER((int) lease_mac->lease_nip));
175 *lease = g_try_new0(struct dhcp_lease, 1);
182 static gint compare_expire(gconstpointer a, gconstpointer b)
184 const struct dhcp_lease *lease1 = a;
185 const struct dhcp_lease *lease2 = b;
187 return lease2->expire - lease1->expire;
190 static struct dhcp_lease *add_lease(GDHCPServer *dhcp_server, uint32_t expire,
191 const uint8_t *chaddr, uint32_t yiaddr)
193 struct dhcp_lease *lease = NULL;
196 ret = get_lease(dhcp_server, yiaddr, chaddr, &lease);
200 memset(lease, 0, sizeof(*lease));
202 memcpy(lease->lease_mac, chaddr, ETH_ALEN);
203 lease->lease_nip = ntohl(yiaddr);
206 lease->expire = time(NULL) + dhcp_server->lease_seconds;
208 lease->expire = expire;
210 dhcp_server->lease_list = g_list_insert_sorted(dhcp_server->lease_list,
211 lease, compare_expire);
213 g_hash_table_insert(dhcp_server->nip_lease_hash,
214 GINT_TO_POINTER((int) lease->lease_nip), lease);
219 static struct dhcp_lease *find_lease_by_nip(GDHCPServer *dhcp_server,
222 return g_hash_table_lookup(dhcp_server->nip_lease_hash,
223 GINT_TO_POINTER((int) nip));
226 /* Check if the IP is taken; if it is, add it to the lease table */
227 static gboolean arp_check(uint32_t nip, const uint8_t *safe_mac)
229 /* TODO: Add ARP checking */
233 static gboolean is_expired_lease(struct dhcp_lease *lease)
235 if (lease->expire < time(NULL))
241 static uint32_t find_free_or_expired_nip(GDHCPServer *dhcp_server,
242 const uint8_t *safe_mac)
245 struct dhcp_lease *lease;
247 ip_addr = dhcp_server->start_ip;
248 for (; ip_addr <= dhcp_server->end_ip; ip_addr++) {
249 /* e.g. 192.168.55.0 */
250 if ((ip_addr & 0xff) == 0)
253 /* e.g. 192.168.55.255 */
254 if ((ip_addr & 0xff) == 0xff)
257 lease = find_lease_by_nip(dhcp_server, ip_addr);
261 if (arp_check(htonl(ip_addr), safe_mac) == TRUE)
265 /* The last lease is the oldest one */
266 list = g_list_last(dhcp_server->lease_list);
274 if (is_expired_lease(lease) == FALSE)
277 if (arp_check(lease->lease_nip, safe_mac) == FALSE)
280 return lease->lease_nip;
283 static void lease_set_expire(GDHCPServer *dhcp_server,
284 struct dhcp_lease *lease, uint32_t expire)
286 dhcp_server->lease_list = g_list_remove(dhcp_server->lease_list, lease);
288 lease->expire = expire;
290 dhcp_server->lease_list = g_list_insert_sorted(dhcp_server->lease_list,
291 lease, compare_expire);
294 static void destroy_lease_table(GDHCPServer *dhcp_server)
298 g_hash_table_destroy(dhcp_server->nip_lease_hash);
300 dhcp_server->nip_lease_hash = NULL;
302 for (list = dhcp_server->lease_list; list; list = list->next) {
303 struct dhcp_lease *lease = list->data;
308 g_list_free(dhcp_server->lease_list);
310 dhcp_server->lease_list = NULL;
312 static uint32_t get_interface_address(int index)
316 struct sockaddr_in *server_ip;
319 sk = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
321 perror("Open socket error");
325 memset(&ifr, 0, sizeof(ifr));
326 ifr.ifr_ifindex = index;
328 err = ioctl(sk, SIOCGIFNAME, &ifr);
330 perror("Get interface name error");
334 err = ioctl(sk, SIOCGIFADDR, &ifr);
336 perror("Get ip address error");
340 server_ip = (struct sockaddr_in *) &ifr.ifr_addr;
341 ret = server_ip->sin_addr.s_addr;
349 GDHCPServer *g_dhcp_server_new(GDHCPType type,
350 int ifindex, GDHCPServerError *error)
352 GDHCPServer *dhcp_server = NULL;
355 *error = G_DHCP_SERVER_ERROR_INVALID_INDEX;
359 dhcp_server = g_try_new0(GDHCPServer, 1);
360 if (dhcp_server == NULL) {
361 *error = G_DHCP_SERVER_ERROR_NOMEM;
365 dhcp_server->interface = get_interface_name(ifindex);
366 if (dhcp_server->interface == NULL) {
367 *error = G_DHCP_SERVER_ERROR_INTERFACE_UNAVAILABLE;
371 if (interface_is_up(ifindex) == FALSE) {
372 *error = G_DHCP_SERVER_ERROR_INTERFACE_DOWN;
376 dhcp_server->server_nip = get_interface_address(ifindex);
377 if (dhcp_server->server_nip == 0) {
378 *error = G_DHCP_SERVER_ERROR_IP_ADDRESS_INVALID;
382 dhcp_server->nip_lease_hash = g_hash_table_new_full(g_direct_hash,
383 g_direct_equal, NULL, NULL);
384 dhcp_server->option_hash = g_hash_table_new_full(g_direct_hash,
385 g_direct_equal, NULL, NULL);
387 dhcp_server->started = FALSE;
389 /* All the leases have the same fixed lease time,
390 * do not support DHCP_LEASE_TIME option from client.
392 dhcp_server->lease_seconds = DEFAULT_DHCP_LEASE_SEC;
394 dhcp_server->type = type;
395 dhcp_server->ref_count = 1;
396 dhcp_server->ifindex = ifindex;
397 dhcp_server->listener_sockfd = -1;
398 dhcp_server->listener_watch = -1;
399 dhcp_server->listener_channel = NULL;
400 dhcp_server->save_lease_func = NULL;
401 dhcp_server->debug_func = NULL;
402 dhcp_server->debug_data = NULL;
404 *error = G_DHCP_SERVER_ERROR_NONE;
409 g_free(dhcp_server->interface);
415 static uint8_t check_packet_type(struct dhcp_packet *packet)
419 if (packet->hlen != ETH_ALEN)
422 if (packet->op != BOOTREQUEST)
425 type = dhcp_get_option(packet, DHCP_MESSAGE_TYPE);
430 if (*type < DHCP_MINTYPE)
433 if (*type > DHCP_MAXTYPE)
439 static void init_packet(GDHCPServer *dhcp_server, struct dhcp_packet *packet,
440 struct dhcp_packet *client_packet, char type)
442 /* Sets op, htype, hlen, cookie fields
443 * and adds DHCP_MESSAGE_TYPE option */
444 dhcp_init_header(packet, type);
446 packet->xid = client_packet->xid;
447 memcpy(packet->chaddr, client_packet->chaddr,
448 sizeof(client_packet->chaddr));
449 packet->flags = client_packet->flags;
450 packet->gateway_nip = client_packet->gateway_nip;
451 packet->ciaddr = client_packet->ciaddr;
452 dhcp_add_option_uint32(packet, DHCP_SERVER_ID,
453 dhcp_server->server_nip);
456 static void add_option(gpointer key, gpointer value, gpointer user_data)
458 const char *option_value = value;
459 uint8_t option_code = GPOINTER_TO_INT(key);
461 struct dhcp_packet *packet = user_data;
463 if (option_value == NULL)
466 switch (option_code) {
469 case G_DHCP_DNS_SERVER:
470 if (inet_aton(option_value, &nip) == 0)
473 dhcp_add_option_uint32(packet, (uint8_t) option_code,
481 static void add_server_options(GDHCPServer *dhcp_server,
482 struct dhcp_packet *packet)
484 g_hash_table_foreach(dhcp_server->option_hash,
488 static gboolean check_requested_nip(GDHCPServer *dhcp_server,
489 uint32_t requested_nip)
491 struct dhcp_lease *lease;
493 if (requested_nip == 0)
496 if (requested_nip < dhcp_server->start_ip)
499 if (requested_nip > dhcp_server->end_ip)
502 lease = find_lease_by_nip(dhcp_server, requested_nip);
506 if (is_expired_lease(lease) == FALSE)
512 static void send_packet_to_client(GDHCPServer *dhcp_server,
513 struct dhcp_packet *dhcp_pkt)
515 const uint8_t *chaddr;
518 if ((dhcp_pkt->flags & htons(BROADCAST_FLAG))
519 || dhcp_pkt->ciaddr == 0) {
520 debug(dhcp_server, "Broadcasting packet to client");
521 ciaddr = INADDR_BROADCAST;
522 chaddr = MAC_BCAST_ADDR;
524 debug(dhcp_server, "Unicasting packet to client ciaddr");
525 ciaddr = dhcp_pkt->ciaddr;
526 chaddr = dhcp_pkt->chaddr;
529 dhcp_send_raw_packet(dhcp_pkt,
530 dhcp_server->server_nip, SERVER_PORT,
531 ciaddr, CLIENT_PORT, chaddr,
532 dhcp_server->ifindex);
535 static void send_offer(GDHCPServer *dhcp_server,
536 struct dhcp_packet *client_packet,
537 struct dhcp_lease *lease,
538 uint32_t requested_nip)
540 struct dhcp_packet packet;
543 init_packet(dhcp_server, &packet, client_packet, DHCPOFFER);
546 packet.yiaddr = htonl(lease->lease_nip);
547 else if (check_requested_nip(dhcp_server, requested_nip) == TRUE)
548 packet.yiaddr = htonl(requested_nip);
550 packet.yiaddr = htonl(find_free_or_expired_nip(
551 dhcp_server, client_packet->chaddr));
553 debug(dhcp_server, "find yiaddr %u", packet.yiaddr);
555 if (!packet.yiaddr) {
556 debug(dhcp_server, "Err: Can not found lease and send offer");
560 lease = add_lease(dhcp_server, OFFER_TIME,
561 packet.chaddr, packet.yiaddr);
564 "Err: No free IP addresses. OFFER abandoned");
568 dhcp_add_option_uint32(&packet, DHCP_LEASE_TIME,
569 dhcp_server->lease_seconds);
570 add_server_options(dhcp_server, &packet);
572 addr.s_addr = packet.yiaddr;
574 debug(dhcp_server, "Sending OFFER of %s", inet_ntoa(addr));
575 send_packet_to_client(dhcp_server, &packet);
578 static void save_lease(GDHCPServer *dhcp_server)
582 if (dhcp_server->save_lease_func == NULL)
585 for (list = dhcp_server->lease_list; list; list = list->next) {
586 struct dhcp_lease *lease = list->data;
587 dhcp_server->save_lease_func(lease->lease_mac,
588 lease->lease_nip, lease->expire);
592 static void send_ACK(GDHCPServer *dhcp_server,
593 struct dhcp_packet *client_packet, uint32_t dest)
595 struct dhcp_packet packet;
596 uint32_t lease_time_sec;
599 init_packet(dhcp_server, &packet, client_packet, DHCPACK);
600 packet.yiaddr = htonl(dest);
602 lease_time_sec = dhcp_server->lease_seconds;
604 dhcp_add_option_uint32(&packet, DHCP_LEASE_TIME, lease_time_sec);
606 add_server_options(dhcp_server, &packet);
608 addr.s_addr = htonl(dest);
610 debug(dhcp_server, "Sending ACK to %s", inet_ntoa(addr));
612 send_packet_to_client(dhcp_server, &packet);
614 add_lease(dhcp_server, 0, packet.chaddr, packet.yiaddr);
617 static void send_NAK(GDHCPServer *dhcp_server,
618 struct dhcp_packet *client_packet)
620 struct dhcp_packet packet;
622 init_packet(dhcp_server, &packet, client_packet, DHCPNAK);
624 debug(dhcp_server, "Sending NAK");
626 dhcp_send_raw_packet(&packet,
627 dhcp_server->server_nip, SERVER_PORT,
628 INADDR_BROADCAST, CLIENT_PORT, MAC_BCAST_ADDR,
629 dhcp_server->ifindex);
632 static void send_inform(GDHCPServer *dhcp_server,
633 struct dhcp_packet *client_packet)
635 struct dhcp_packet packet;
637 init_packet(dhcp_server, &packet, client_packet, DHCPACK);
638 add_server_options(dhcp_server, &packet);
639 send_packet_to_client(dhcp_server, &packet);
642 static gboolean listener_event(GIOChannel *channel, GIOCondition condition,
645 GDHCPServer *dhcp_server = user_data;
646 struct dhcp_packet packet;
647 struct dhcp_lease *lease;
648 uint32_t requested_nip = 0;
649 uint8_t type, *server_id_option, *request_ip_option;
652 if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
653 dhcp_server->listener_watch = 0;
657 re = dhcp_recv_l3_packet(&packet, dhcp_server->listener_sockfd);
661 type = check_packet_type(&packet);
665 server_id_option = dhcp_get_option(&packet, DHCP_SERVER_ID);
666 if (server_id_option) {
667 uint32_t server_nid = get_be32(server_id_option);
669 if (server_nid != dhcp_server->server_nip)
673 request_ip_option = dhcp_get_option(&packet, DHCP_REQUESTED_IP);
674 if (request_ip_option)
675 requested_nip = get_be32(request_ip_option);
677 lease = find_lease_by_mac(dhcp_server, packet.chaddr);
681 debug(dhcp_server, "Received DISCOVER");
683 send_offer(dhcp_server, &packet, lease, requested_nip);
686 debug(dhcp_server, "Received REQUEST NIP %d",
688 if (requested_nip == 0) {
689 requested_nip = packet.ciaddr;
690 if (requested_nip == 0)
694 if (lease && requested_nip == lease->lease_nip) {
695 debug(dhcp_server, "Sending ACK");
696 send_ACK(dhcp_server, &packet,
701 if (server_id_option || lease == NULL) {
702 debug(dhcp_server, "Sending NAK");
703 send_NAK(dhcp_server, &packet);
708 debug(dhcp_server, "Received DECLINE");
710 if (server_id_option == NULL)
713 if (request_ip_option == NULL)
719 if (requested_nip == lease->lease_nip)
720 remove_lease(dhcp_server, lease);
724 debug(dhcp_server, "Received RELEASE");
726 if (server_id_option == NULL)
732 if (packet.ciaddr == lease->lease_nip)
733 lease_set_expire(dhcp_server, lease,
737 debug(dhcp_server, "Received INFORM");
738 send_inform(dhcp_server, &packet);
745 /* Caller need to load leases before call it */
746 int g_dhcp_server_start(GDHCPServer *dhcp_server)
748 GIOChannel *listener_channel;
751 if (dhcp_server->started == TRUE)
754 listener_sockfd = dhcp_l3_socket(SERVER_PORT,
755 dhcp_server->interface, AF_INET);
756 if (listener_sockfd < 0)
759 listener_channel = g_io_channel_unix_new(listener_sockfd);
760 if (listener_channel == NULL) {
761 close(listener_sockfd);
765 dhcp_server->listener_sockfd = listener_sockfd;
766 dhcp_server->listener_channel = listener_channel;
768 g_io_channel_set_close_on_unref(listener_channel, TRUE);
769 dhcp_server->listener_watch =
770 g_io_add_watch_full(listener_channel, G_PRIORITY_HIGH,
771 G_IO_IN | G_IO_NVAL | G_IO_ERR | G_IO_HUP,
772 listener_event, dhcp_server,
774 g_io_channel_unref(dhcp_server->listener_channel);
776 dhcp_server->started = TRUE;
781 int g_dhcp_server_set_option(GDHCPServer *dhcp_server,
782 unsigned char option_code, const char *option_value)
786 if (option_value == NULL)
789 debug(dhcp_server, "option_code %d option_value %s",
790 option_code, option_value);
791 switch (option_code) {
794 case G_DHCP_DNS_SERVER:
795 if (inet_aton(option_value, &nip) == 0)
802 g_hash_table_replace(dhcp_server->option_hash,
803 GINT_TO_POINTER((int) option_code),
804 (gpointer) option_value);
808 void g_dhcp_server_set_save_lease(GDHCPServer *dhcp_server,
809 GDHCPSaveLeaseFunc func, gpointer user_data)
811 if (dhcp_server == NULL)
814 dhcp_server->save_lease_func = func;
817 GDHCPServer *g_dhcp_server_ref(GDHCPServer *dhcp_server)
819 if (dhcp_server == NULL)
822 __sync_fetch_and_add(&dhcp_server->ref_count, 1);
827 void g_dhcp_server_stop(GDHCPServer *dhcp_server)
829 /* Save leases, before stop; load them before start */
830 save_lease(dhcp_server);
832 if (dhcp_server->listener_watch > 0) {
833 g_source_remove(dhcp_server->listener_watch);
834 dhcp_server->listener_watch = 0;
837 dhcp_server->listener_channel = NULL;
839 dhcp_server->started = FALSE;
842 void g_dhcp_server_unref(GDHCPServer *dhcp_server)
844 if (dhcp_server == NULL)
847 if (__sync_fetch_and_sub(&dhcp_server->ref_count, 1) != 1)
850 g_dhcp_server_stop(dhcp_server);
852 g_hash_table_destroy(dhcp_server->option_hash);
854 destroy_lease_table(dhcp_server);
856 g_free(dhcp_server->interface);
861 int g_dhcp_server_set_ip_range(GDHCPServer *dhcp_server,
862 const char *start_ip, const char *end_ip)
864 struct in_addr _host_addr;
866 if (inet_aton(start_ip, &_host_addr) == 0)
869 dhcp_server->start_ip = ntohl(_host_addr.s_addr);
871 if (inet_aton(end_ip, &_host_addr) == 0)
874 dhcp_server->end_ip = ntohl(_host_addr.s_addr);
879 void g_dhcp_server_set_lease_time(GDHCPServer *dhcp_server, unsigned int lease_time)
881 if (dhcp_server == NULL)
884 dhcp_server->lease_seconds = lease_time;
887 void g_dhcp_server_set_debug(GDHCPServer *dhcp_server,
888 GDHCPDebugFunc func, gpointer user_data)
890 if (dhcp_server == NULL)
893 dhcp_server->debug_func = func;
894 dhcp_server->debug_data = user_data;