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)
59 uint32_t server_nip; /* our address in network byte order */
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 GDHCPLeaseAddedCb lease_added_cb;
69 GDHCPSaveACKLeaseFunc save_ack_lease_func;
70 GDHCPDebugFunc debug_func;
77 uint8_t lease_mac[ETH_ALEN];
80 static inline void debug(GDHCPServer *server, const char *format, ...)
85 if (!server->debug_func)
90 if (vsnprintf(str, sizeof(str), format, ap) > 0)
91 server->debug_func(str, server->debug_data);
96 static struct dhcp_lease *find_lease_by_mac(GDHCPServer *dhcp_server,
101 for (list = dhcp_server->lease_list; list; list = list->next) {
102 struct dhcp_lease *lease = list->data;
104 if (memcmp(lease->lease_mac, mac, ETH_ALEN) == 0)
111 static void remove_lease(GDHCPServer *dhcp_server, struct dhcp_lease *lease)
113 dhcp_server->lease_list =
114 g_list_remove(dhcp_server->lease_list, lease);
116 g_hash_table_remove(dhcp_server->nip_lease_hash,
117 GINT_TO_POINTER((int) lease->lease_nip));
121 /* Clear the old lease and create the new one */
122 static int get_lease(GDHCPServer *dhcp_server, uint32_t yiaddr,
123 const uint8_t *mac, struct dhcp_lease **lease)
125 struct dhcp_lease *lease_nip, *lease_mac;
130 if (ntohl(yiaddr) < dhcp_server->start_ip)
133 if (ntohl(yiaddr) > dhcp_server->end_ip)
136 if (memcmp(mac, MAC_BCAST_ADDR, ETH_ALEN) == 0)
139 if (memcmp(mac, MAC_ANY_ADDR, ETH_ALEN) == 0)
142 lease_mac = find_lease_by_mac(dhcp_server, mac);
144 lease_nip = g_hash_table_lookup(dhcp_server->nip_lease_hash,
145 GINT_TO_POINTER((int) ntohl(yiaddr)));
146 debug(dhcp_server, "lease_mac %p lease_nip %p", lease_mac, lease_nip);
149 dhcp_server->lease_list =
150 g_list_remove(dhcp_server->lease_list,
152 g_hash_table_remove(dhcp_server->nip_lease_hash,
153 GINT_TO_POINTER((int) ntohl(yiaddr)));
157 else if (lease_nip != lease_mac) {
158 remove_lease(dhcp_server, lease_mac);
167 dhcp_server->lease_list =
168 g_list_remove(dhcp_server->lease_list,
170 g_hash_table_remove(dhcp_server->nip_lease_hash,
171 GINT_TO_POINTER((int) lease_mac->lease_nip));
177 *lease = g_try_new0(struct dhcp_lease, 1);
184 static gint compare_expire(gconstpointer a, gconstpointer b)
186 const struct dhcp_lease *lease1 = a;
187 const struct dhcp_lease *lease2 = b;
189 return lease2->expire - lease1->expire;
192 static struct dhcp_lease *add_lease(GDHCPServer *dhcp_server, uint32_t expire,
193 const uint8_t *chaddr, uint32_t yiaddr)
195 struct dhcp_lease *lease = NULL;
198 ret = get_lease(dhcp_server, yiaddr, chaddr, &lease);
202 memset(lease, 0, sizeof(*lease));
204 memcpy(lease->lease_mac, chaddr, ETH_ALEN);
205 lease->lease_nip = ntohl(yiaddr);
208 lease->expire = time(NULL) + dhcp_server->lease_seconds;
210 lease->expire = expire;
212 dhcp_server->lease_list = g_list_insert_sorted(dhcp_server->lease_list,
213 lease, compare_expire);
215 g_hash_table_insert(dhcp_server->nip_lease_hash,
216 GINT_TO_POINTER((int) lease->lease_nip), lease);
218 if (dhcp_server->lease_added_cb)
219 dhcp_server->lease_added_cb(lease->lease_mac, yiaddr);
224 static struct dhcp_lease *find_lease_by_nip(GDHCPServer *dhcp_server,
227 return g_hash_table_lookup(dhcp_server->nip_lease_hash,
228 GINT_TO_POINTER((int) nip));
231 /* Check if the IP is taken; if it is, add it to the lease table */
232 static bool arp_check(uint32_t nip, const uint8_t *safe_mac)
234 /* TODO: Add ARP checking */
238 static bool is_expired_lease(struct dhcp_lease *lease)
240 if (lease->expire < time(NULL))
246 static uint32_t find_free_or_expired_nip(GDHCPServer *dhcp_server,
247 const uint8_t *safe_mac)
250 struct dhcp_lease *lease;
252 ip_addr = dhcp_server->start_ip;
253 for (; ip_addr <= dhcp_server->end_ip; ip_addr++) {
254 /* e.g. 192.168.55.0 */
255 if ((ip_addr & 0xff) == 0)
258 /* e.g. 192.168.55.255 */
259 if ((ip_addr & 0xff) == 0xff)
262 lease = find_lease_by_nip(dhcp_server, ip_addr);
266 if (arp_check(htonl(ip_addr), safe_mac))
270 /* The last lease is the oldest one */
271 list = g_list_last(dhcp_server->lease_list);
279 if (!is_expired_lease(lease))
282 if (!arp_check(lease->lease_nip, safe_mac))
285 return lease->lease_nip;
288 static void lease_set_expire(GDHCPServer *dhcp_server,
289 struct dhcp_lease *lease, uint32_t expire)
291 dhcp_server->lease_list = g_list_remove(dhcp_server->lease_list, lease);
293 lease->expire = expire;
295 dhcp_server->lease_list = g_list_insert_sorted(dhcp_server->lease_list,
296 lease, compare_expire);
299 static void destroy_lease_table(GDHCPServer *dhcp_server)
303 g_hash_table_destroy(dhcp_server->nip_lease_hash);
305 dhcp_server->nip_lease_hash = NULL;
307 for (list = dhcp_server->lease_list; list; list = list->next) {
308 struct dhcp_lease *lease = list->data;
313 g_list_free(dhcp_server->lease_list);
315 dhcp_server->lease_list = NULL;
317 static uint32_t get_interface_address(int index)
321 struct sockaddr_in *server_ip;
324 sk = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
326 perror("Open socket error");
330 memset(&ifr, 0, sizeof(ifr));
331 ifr.ifr_ifindex = index;
333 err = ioctl(sk, SIOCGIFNAME, &ifr);
335 perror("Get interface name error");
339 err = ioctl(sk, SIOCGIFADDR, &ifr);
341 perror("Get ip address error");
345 server_ip = (struct sockaddr_in *) &ifr.ifr_addr;
346 ret = server_ip->sin_addr.s_addr;
354 GDHCPServer *g_dhcp_server_new(GDHCPType type,
355 int ifindex, GDHCPServerError *error)
357 GDHCPServer *dhcp_server = NULL;
360 *error = G_DHCP_SERVER_ERROR_INVALID_INDEX;
364 dhcp_server = g_try_new0(GDHCPServer, 1);
366 *error = G_DHCP_SERVER_ERROR_NOMEM;
370 dhcp_server->interface = get_interface_name(ifindex);
371 if (!dhcp_server->interface) {
372 *error = G_DHCP_SERVER_ERROR_INTERFACE_UNAVAILABLE;
376 if (!interface_is_up(ifindex)) {
377 *error = G_DHCP_SERVER_ERROR_INTERFACE_DOWN;
381 dhcp_server->server_nip = get_interface_address(ifindex);
382 if (dhcp_server->server_nip == 0) {
383 *error = G_DHCP_SERVER_ERROR_IP_ADDRESS_INVALID;
387 dhcp_server->nip_lease_hash = g_hash_table_new_full(g_direct_hash,
388 g_direct_equal, NULL, NULL);
389 dhcp_server->option_hash = g_hash_table_new_full(g_direct_hash,
390 g_direct_equal, NULL, NULL);
392 dhcp_server->started = FALSE;
394 /* All the leases have the same fixed lease time,
395 * do not support DHCP_LEASE_TIME option from client.
397 dhcp_server->lease_seconds = DEFAULT_DHCP_LEASE_SEC;
399 dhcp_server->type = type;
400 dhcp_server->ref_count = 1;
401 dhcp_server->ifindex = ifindex;
402 dhcp_server->listener_sockfd = -1;
403 dhcp_server->listener_watch = -1;
404 dhcp_server->listener_channel = NULL;
405 dhcp_server->save_lease_func = NULL;
406 dhcp_server->save_ack_lease_func = NULL;
407 dhcp_server->debug_func = NULL;
408 dhcp_server->debug_data = NULL;
410 *error = G_DHCP_SERVER_ERROR_NONE;
415 g_free(dhcp_server->interface);
421 static uint8_t check_packet_type(struct dhcp_packet *packet)
425 if (packet->hlen != ETH_ALEN)
428 if (packet->op != BOOTREQUEST)
431 type = dhcp_get_option(packet, DHCP_MESSAGE_TYPE);
436 if (*type < DHCP_MINTYPE)
439 if (*type > DHCP_MAXTYPE)
445 static void init_packet(GDHCPServer *dhcp_server, struct dhcp_packet *packet,
446 struct dhcp_packet *client_packet, char type)
448 /* Sets op, htype, hlen, cookie fields
449 * and adds DHCP_MESSAGE_TYPE option */
450 dhcp_init_header(packet, type);
452 packet->xid = client_packet->xid;
453 memcpy(packet->chaddr, client_packet->chaddr,
454 sizeof(client_packet->chaddr));
455 packet->flags = client_packet->flags;
456 packet->gateway_nip = client_packet->gateway_nip;
457 packet->ciaddr = client_packet->ciaddr;
458 dhcp_add_option_uint32(packet, DHCP_SERVER_ID,
459 ntohl(dhcp_server->server_nip));
462 static void add_option(gpointer key, gpointer value, gpointer user_data)
464 const char *option_value = value;
465 uint8_t option_code = GPOINTER_TO_INT(key);
467 struct dhcp_packet *packet = user_data;
472 switch (option_code) {
475 case G_DHCP_DNS_SERVER:
476 if (inet_aton(option_value, &nip) == 0)
479 dhcp_add_option_uint32(packet, (uint8_t) option_code,
487 static void add_server_options(GDHCPServer *dhcp_server,
488 struct dhcp_packet *packet)
490 g_hash_table_foreach(dhcp_server->option_hash,
494 static bool check_requested_nip(GDHCPServer *dhcp_server,
495 uint32_t requested_nip)
497 struct dhcp_lease *lease;
499 if (requested_nip == 0)
502 if (requested_nip < dhcp_server->start_ip)
505 if (requested_nip > dhcp_server->end_ip)
508 lease = find_lease_by_nip(dhcp_server, requested_nip);
512 if (!is_expired_lease(lease))
518 static void send_packet_to_client(GDHCPServer *dhcp_server,
519 struct dhcp_packet *dhcp_pkt)
521 const uint8_t *chaddr;
524 if ((dhcp_pkt->flags & htons(BROADCAST_FLAG))
525 || dhcp_pkt->ciaddr == 0) {
526 debug(dhcp_server, "Broadcasting packet to client");
527 ciaddr = INADDR_BROADCAST;
528 chaddr = MAC_BCAST_ADDR;
530 debug(dhcp_server, "Unicasting packet to client ciaddr");
531 ciaddr = dhcp_pkt->ciaddr;
532 chaddr = dhcp_pkt->chaddr;
535 dhcp_send_raw_packet(dhcp_pkt,
536 dhcp_server->server_nip, SERVER_PORT,
537 ciaddr, CLIENT_PORT, chaddr,
538 dhcp_server->ifindex, false);
541 static void send_offer(GDHCPServer *dhcp_server,
542 struct dhcp_packet *client_packet,
543 struct dhcp_lease *lease,
544 uint32_t requested_nip)
546 struct dhcp_packet packet;
549 init_packet(dhcp_server, &packet, client_packet, DHCPOFFER);
552 packet.yiaddr = htonl(lease->lease_nip);
553 else if (check_requested_nip(dhcp_server, requested_nip))
554 packet.yiaddr = htonl(requested_nip);
556 packet.yiaddr = htonl(find_free_or_expired_nip(
557 dhcp_server, client_packet->chaddr));
559 debug(dhcp_server, "find yiaddr %u", packet.yiaddr);
561 if (!packet.yiaddr) {
562 debug(dhcp_server, "Err: Can not found lease and send offer");
566 lease = add_lease(dhcp_server, OFFER_TIME,
567 packet.chaddr, packet.yiaddr);
570 "Err: No free IP addresses. OFFER abandoned");
574 dhcp_add_option_uint32(&packet, DHCP_LEASE_TIME,
575 dhcp_server->lease_seconds);
576 add_server_options(dhcp_server, &packet);
578 addr.s_addr = packet.yiaddr;
580 debug(dhcp_server, "Sending OFFER of %s", inet_ntoa(addr));
581 send_packet_to_client(dhcp_server, &packet);
584 static void save_lease(GDHCPServer *dhcp_server)
588 if (!dhcp_server->save_lease_func)
591 for (list = dhcp_server->lease_list; list; list = list->next) {
592 struct dhcp_lease *lease = list->data;
593 dhcp_server->save_lease_func(lease->lease_mac,
594 lease->lease_nip, lease->expire);
598 static void send_ACK(GDHCPServer *dhcp_server,
599 struct dhcp_packet *client_packet, uint32_t dest)
601 struct dhcp_packet packet;
602 uint32_t lease_time_sec;
605 init_packet(dhcp_server, &packet, client_packet, DHCPACK);
606 packet.yiaddr = htonl(dest);
608 lease_time_sec = dhcp_server->lease_seconds;
610 dhcp_add_option_uint32(&packet, DHCP_LEASE_TIME, lease_time_sec);
612 add_server_options(dhcp_server, &packet);
614 addr.s_addr = htonl(dest);
616 debug(dhcp_server, "Sending ACK to %s", inet_ntoa(addr));
618 send_packet_to_client(dhcp_server, &packet);
620 add_lease(dhcp_server, 0, packet.chaddr, packet.yiaddr);
623 static void send_NAK(GDHCPServer *dhcp_server,
624 struct dhcp_packet *client_packet)
626 struct dhcp_packet packet;
628 init_packet(dhcp_server, &packet, client_packet, DHCPNAK);
630 debug(dhcp_server, "Sending NAK");
632 dhcp_send_raw_packet(&packet,
633 dhcp_server->server_nip, SERVER_PORT,
634 INADDR_BROADCAST, CLIENT_PORT, MAC_BCAST_ADDR,
635 dhcp_server->ifindex, false);
638 static void send_inform(GDHCPServer *dhcp_server,
639 struct dhcp_packet *client_packet)
641 struct dhcp_packet packet;
643 init_packet(dhcp_server, &packet, client_packet, DHCPACK);
644 add_server_options(dhcp_server, &packet);
645 send_packet_to_client(dhcp_server, &packet);
648 static gboolean listener_event(GIOChannel *channel, GIOCondition condition,
651 GDHCPServer *dhcp_server = user_data;
652 struct dhcp_packet packet;
653 struct dhcp_lease *lease;
654 uint32_t requested_nip = 0;
655 uint8_t type, *server_id_option, *request_ip_option, *host_name;
658 GDHCPOptionType option_type;
661 if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
662 dhcp_server->listener_watch = 0;
666 re = dhcp_recv_l3_packet(&packet, dhcp_server->listener_sockfd);
670 type = check_packet_type(&packet);
674 server_id_option = dhcp_get_option(&packet, DHCP_SERVER_ID);
675 if (server_id_option) {
676 uint32_t server_nid =
677 get_unaligned((const uint32_t *) server_id_option);
679 if (server_nid != dhcp_server->server_nip)
683 request_ip_option = dhcp_get_option(&packet, DHCP_REQUESTED_IP);
684 if (request_ip_option)
685 requested_nip = get_be32(request_ip_option);
687 lease = find_lease_by_mac(dhcp_server, packet.chaddr);
691 debug(dhcp_server, "Received DISCOVER");
693 send_offer(dhcp_server, &packet, lease, requested_nip);
696 debug(dhcp_server, "Received REQUEST NIP %d",
698 if (requested_nip == 0) {
699 requested_nip = packet.ciaddr;
700 if (requested_nip == 0)
704 if (lease && requested_nip == lease->lease_nip) {
705 debug(dhcp_server, "Sending ACK");
707 host_name = dhcp_get_option(&packet, DHCP_HOST_NAME);
708 option_type = dhcp_get_code_type(DHCP_HOST_NAME);
709 option_value = malloc_option_value_string(host_name,
711 send_ACK(dhcp_server, &packet,
714 if (dhcp_server->save_ack_lease_func)
715 dhcp_server->save_ack_lease_func(
719 g_free(option_value);
724 if (server_id_option || !lease) {
725 debug(dhcp_server, "Sending NAK");
726 send_NAK(dhcp_server, &packet);
731 debug(dhcp_server, "Received DECLINE");
733 if (!server_id_option)
736 if (!request_ip_option)
742 if (requested_nip == lease->lease_nip)
743 remove_lease(dhcp_server, lease);
747 debug(dhcp_server, "Received RELEASE");
749 if (!server_id_option)
755 if (packet.ciaddr == lease->lease_nip)
756 lease_set_expire(dhcp_server, lease,
760 debug(dhcp_server, "Received INFORM");
761 send_inform(dhcp_server, &packet);
768 /* Caller need to load leases before call it */
769 int g_dhcp_server_start(GDHCPServer *dhcp_server)
771 GIOChannel *listener_channel;
774 if (dhcp_server->started)
777 listener_sockfd = dhcp_l3_socket(SERVER_PORT,
778 dhcp_server->interface, AF_INET);
779 if (listener_sockfd < 0)
782 listener_channel = g_io_channel_unix_new(listener_sockfd);
783 if (!listener_channel) {
784 close(listener_sockfd);
788 dhcp_server->listener_sockfd = listener_sockfd;
789 dhcp_server->listener_channel = listener_channel;
791 g_io_channel_set_close_on_unref(listener_channel, TRUE);
792 dhcp_server->listener_watch =
793 g_io_add_watch_full(listener_channel, G_PRIORITY_HIGH,
794 G_IO_IN | G_IO_NVAL | G_IO_ERR | G_IO_HUP,
795 listener_event, dhcp_server,
797 g_io_channel_unref(dhcp_server->listener_channel);
799 dhcp_server->started = TRUE;
804 int g_dhcp_server_set_option(GDHCPServer *dhcp_server,
805 unsigned char option_code, const char *option_value)
812 debug(dhcp_server, "option_code %d option_value %s",
813 option_code, option_value);
814 switch (option_code) {
817 case G_DHCP_DNS_SERVER:
818 if (inet_aton(option_value, &nip) == 0)
825 g_hash_table_replace(dhcp_server->option_hash,
826 GINT_TO_POINTER((int) option_code),
827 (gpointer) option_value);
831 void g_dhcp_server_set_save_lease(GDHCPServer *dhcp_server,
832 GDHCPSaveLeaseFunc func, gpointer user_data)
837 dhcp_server->save_lease_func = func;
840 void g_dhcp_server_set_lease_added_cb(GDHCPServer *dhcp_server,
841 GDHCPLeaseAddedCb cb)
846 dhcp_server->lease_added_cb = cb;
849 void g_dhcp_server_set_save_ack_lease(GDHCPServer *dhcp_server,
850 GDHCPSaveACKLeaseFunc func, gpointer user_data)
852 if (dhcp_server == NULL)
855 dhcp_server->save_ack_lease_func = func;
858 GDHCPServer *g_dhcp_server_ref(GDHCPServer *dhcp_server)
863 __sync_fetch_and_add(&dhcp_server->ref_count, 1);
868 void g_dhcp_server_stop(GDHCPServer *dhcp_server)
870 /* Save leases, before stop; load them before start */
871 save_lease(dhcp_server);
873 if (dhcp_server->listener_watch > 0) {
874 g_source_remove(dhcp_server->listener_watch);
875 dhcp_server->listener_watch = 0;
878 dhcp_server->listener_channel = NULL;
880 dhcp_server->started = FALSE;
883 void g_dhcp_server_unref(GDHCPServer *dhcp_server)
888 if (__sync_fetch_and_sub(&dhcp_server->ref_count, 1) != 1)
891 g_dhcp_server_stop(dhcp_server);
893 g_hash_table_destroy(dhcp_server->option_hash);
895 destroy_lease_table(dhcp_server);
897 g_free(dhcp_server->interface);
902 int g_dhcp_server_set_ip_range(GDHCPServer *dhcp_server,
903 const char *start_ip, const char *end_ip)
905 struct in_addr _host_addr;
907 if (inet_aton(start_ip, &_host_addr) == 0)
910 dhcp_server->start_ip = ntohl(_host_addr.s_addr);
912 if (inet_aton(end_ip, &_host_addr) == 0)
915 dhcp_server->end_ip = ntohl(_host_addr.s_addr);
920 void g_dhcp_server_set_lease_time(GDHCPServer *dhcp_server,
921 unsigned int lease_time)
926 dhcp_server->lease_seconds = lease_time;
929 void g_dhcp_server_set_debug(GDHCPServer *dhcp_server,
930 GDHCPDebugFunc func, gpointer user_data)
935 dhcp_server->debug_func = func;
936 dhcp_server->debug_data = user_data;