3
* DHCP Server library with GLib integration
5
* Copyright (C) 2009-2010 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) 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) 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 = 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,
258
(uint32_t) htonl(ip_addr));
262
if (arp_check(htonl(ip_addr), safe_mac) == TRUE)
263
return htonl(ip_addr);
266
/* The last lease is the oldest one */
267
list = g_list_last(dhcp_server->lease_list);
275
if (is_expired_lease(lease) == FALSE)
278
if (arp_check(lease->lease_nip, safe_mac) == FALSE)
281
return lease->lease_nip;
284
static void lease_set_expire(GDHCPServer *dhcp_server,
285
struct dhcp_lease *lease, uint32_t expire)
287
dhcp_server->lease_list = g_list_remove(dhcp_server->lease_list, lease);
289
lease->expire = expire;
291
dhcp_server->lease_list = g_list_insert_sorted(dhcp_server->lease_list,
292
lease, compare_expire);
295
static void destroy_lease_table(GDHCPServer *dhcp_server)
299
g_hash_table_destroy(dhcp_server->nip_lease_hash);
301
dhcp_server->nip_lease_hash = NULL;
303
for (list = dhcp_server->lease_list; list; list = list->next) {
304
struct dhcp_lease *lease = list->data;
309
g_list_free(dhcp_server->lease_list);
311
dhcp_server->lease_list = NULL;
313
static uint32_t get_interface_address(int index)
317
struct sockaddr_in *server_ip;
320
sk = socket(PF_INET, SOCK_DGRAM, 0);
322
perror("Open socket error");
326
memset(&ifr, 0, sizeof(ifr));
327
ifr.ifr_ifindex = index;
329
err = ioctl(sk, SIOCGIFNAME, &ifr);
331
perror("Get interface name error");
335
err = ioctl(sk, SIOCGIFADDR, &ifr);
337
perror("Get ip address error");
341
server_ip = (struct sockaddr_in *) &ifr.ifr_addr;
342
ret = server_ip->sin_addr.s_addr;
350
GDHCPServer *g_dhcp_server_new(GDHCPType type,
351
int ifindex, GDHCPServerError *error)
353
GDHCPServer *dhcp_server = NULL;
356
*error = G_DHCP_SERVER_ERROR_INVALID_INDEX;
360
dhcp_server = g_try_new0(GDHCPServer, 1);
361
if (dhcp_server == NULL) {
362
*error = G_DHCP_SERVER_ERROR_NOMEM;
366
dhcp_server->interface = get_interface_name(ifindex);
367
if (dhcp_server->interface == NULL) {
368
*error = G_DHCP_SERVER_ERROR_INTERFACE_UNAVAILABLE;
372
if (interface_is_up(ifindex) == FALSE) {
373
*error = G_DHCP_SERVER_ERROR_INTERFACE_DOWN;
377
dhcp_server->server_nip = get_interface_address(ifindex);
378
if (dhcp_server->server_nip == 0) {
379
*error = G_DHCP_SERVER_ERROR_IP_ADDRESS_INVALID;
383
dhcp_server->nip_lease_hash = g_hash_table_new_full(g_direct_hash,
384
g_direct_equal, NULL, NULL);
385
dhcp_server->option_hash = g_hash_table_new_full(g_direct_hash,
386
g_direct_equal, NULL, NULL);
388
dhcp_server->started = FALSE;
390
/* All the leases have the same fixed lease time,
391
* do not support DHCP_LEASE_TIME option from client.
393
dhcp_server->lease_seconds = DEFAULT_DHCP_LEASE_SEC;
395
dhcp_server->type = type;
396
dhcp_server->ref_count = 1;
397
dhcp_server->ifindex = ifindex;
398
dhcp_server->listener_sockfd = -1;
399
dhcp_server->listener_watch = -1;
400
dhcp_server->listener_channel = NULL;
401
dhcp_server->save_lease_func = NULL;
402
dhcp_server->debug_func = NULL;
403
dhcp_server->debug_data = NULL;
405
*error = G_DHCP_SERVER_ERROR_NONE;
410
g_free(dhcp_server->interface);
416
static uint8_t check_packet_type(struct dhcp_packet *packet)
420
if (packet->hlen != ETH_ALEN)
423
if (packet->op != BOOTREQUEST)
426
type = dhcp_get_option(packet, DHCP_MESSAGE_TYPE);
431
if (*type < DHCP_MINTYPE)
434
if (*type > DHCP_MAXTYPE)
440
static void init_packet(GDHCPServer *dhcp_server, struct dhcp_packet *packet,
441
struct dhcp_packet *client_packet, char type)
443
/* Sets op, htype, hlen, cookie fields
444
* and adds DHCP_MESSAGE_TYPE option */
445
dhcp_init_header(packet, type);
447
packet->xid = client_packet->xid;
448
memcpy(packet->chaddr, client_packet->chaddr,
449
sizeof(client_packet->chaddr));
450
packet->flags = client_packet->flags;
451
packet->gateway_nip = client_packet->gateway_nip;
452
packet->ciaddr = client_packet->ciaddr;
453
dhcp_add_simple_option(packet, DHCP_SERVER_ID, 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_simple_option(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 (ntohl(requested_nip) < dhcp_server->start_ip)
499
if (ntohl(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 = lease->lease_nip;
547
else if (check_requested_nip(dhcp_server, requested_nip) == TRUE)
548
packet.yiaddr = requested_nip;
550
packet.yiaddr = 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_simple_option(&packet, DHCP_LEASE_TIME,
569
htonl(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 yiaddr)
595
struct dhcp_packet packet;
596
uint32_t lease_time_sec;
599
init_packet(dhcp_server, &packet, client_packet, DHCPACK);
600
packet.yiaddr = yiaddr;
602
lease_time_sec = dhcp_server->lease_seconds;
604
dhcp_add_simple_option(&packet, DHCP_LEASE_TIME, htonl(lease_time_sec));
606
add_server_options(dhcp_server, &packet);
608
addr.s_addr = yiaddr;
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 = dhcp_get_unaligned(
668
(uint32_t *) server_id_option);
670
if (server_nid != dhcp_server->server_nip)
674
request_ip_option = dhcp_get_option(&packet, DHCP_REQUESTED_IP);
675
if (request_ip_option)
676
requested_nip = dhcp_get_unaligned(
677
(uint32_t *) request_ip_option);
679
lease = find_lease_by_mac(dhcp_server, packet.chaddr);
683
debug(dhcp_server, "Received DISCOVER");
685
send_offer(dhcp_server, &packet, lease, requested_nip);
688
debug(dhcp_server, "Received REQUEST NIP %d",
690
if (requested_nip == 0) {
691
requested_nip = packet.ciaddr;
692
if (requested_nip == 0)
696
if (lease && requested_nip == lease->lease_nip) {
697
debug(dhcp_server, "Sending ACK");
698
send_ACK(dhcp_server, &packet,
703
if (server_id_option || lease == NULL) {
704
debug(dhcp_server, "Sending NAK");
705
send_NAK(dhcp_server, &packet);
710
debug(dhcp_server, "Received DECLINE");
712
if (server_id_option == NULL)
715
if (request_ip_option == NULL)
721
if (requested_nip == lease->lease_nip)
722
remove_lease(dhcp_server, lease);
726
debug(dhcp_server, "Received RELEASE");
728
if (server_id_option == NULL)
734
if (packet.ciaddr == lease->lease_nip)
735
lease_set_expire(dhcp_server, lease,
739
debug(dhcp_server, "Received INFORM");
740
send_inform(dhcp_server, &packet);
747
/* Caller need to load leases before call it */
748
int g_dhcp_server_start(GDHCPServer *dhcp_server)
750
GIOChannel *listener_channel;
753
if (dhcp_server->started == TRUE)
756
listener_sockfd = dhcp_l3_socket(SERVER_PORT,
757
dhcp_server->interface);
758
if (listener_sockfd < 0)
761
listener_channel = g_io_channel_unix_new(listener_sockfd);
762
if (listener_channel == NULL) {
763
close(listener_sockfd);
767
dhcp_server->listener_sockfd = listener_sockfd;
768
dhcp_server->listener_channel = listener_channel;
770
g_io_channel_set_close_on_unref(listener_channel, TRUE);
771
dhcp_server->listener_watch =
772
g_io_add_watch_full(listener_channel,
773
G_PRIORITY_HIGH, G_IO_IN,
774
listener_event, dhcp_server,
776
g_io_channel_unref(dhcp_server->listener_channel);
778
dhcp_server->started = TRUE;
783
int g_dhcp_server_set_option(GDHCPServer *dhcp_server,
784
unsigned char option_code, const char *option_value)
788
if (option_value == NULL)
791
debug(dhcp_server, "option_code %d option_value %s",
792
option_code, option_value);
793
switch (option_code) {
796
case G_DHCP_DNS_SERVER:
797
if (inet_aton(option_value, &nip) == 0)
804
g_hash_table_replace(dhcp_server->option_hash,
805
GINT_TO_POINTER((int) option_code),
806
(gpointer) option_value);
810
void g_dhcp_server_set_save_lease(GDHCPServer *dhcp_server,
811
GDHCPSaveLeaseFunc func, gpointer user_data)
813
if (dhcp_server == NULL)
816
dhcp_server->save_lease_func = func;
819
GDHCPServer *g_dhcp_server_ref(GDHCPServer *dhcp_server)
821
if (dhcp_server == NULL)
824
g_atomic_int_inc(&dhcp_server->ref_count);
829
void g_dhcp_server_stop(GDHCPServer *dhcp_server)
831
/* Save leases, before stop; load them before start */
832
save_lease(dhcp_server);
834
if (dhcp_server->listener_watch > 0) {
835
g_source_remove(dhcp_server->listener_watch);
836
dhcp_server->listener_watch = 0;
839
dhcp_server->listener_channel = NULL;
841
dhcp_server->started = FALSE;
844
void g_dhcp_server_unref(GDHCPServer *dhcp_server)
846
if (dhcp_server == NULL)
849
if (g_atomic_int_dec_and_test(&dhcp_server->ref_count) == FALSE)
852
g_dhcp_server_stop(dhcp_server);
854
g_hash_table_destroy(dhcp_server->option_hash);
856
destroy_lease_table(dhcp_server);
858
g_free(dhcp_server->interface);
863
void g_dhcp_server_load_lease(GDHCPServer *dhcp_server, unsigned int expire,
864
unsigned char *mac, unsigned int lease_ip)
866
add_lease(dhcp_server, expire, mac, lease_ip);
869
int g_dhcp_server_set_ip_range(GDHCPServer *dhcp_server,
870
const char *start_ip, const char *end_ip)
872
struct in_addr _host_addr;
874
if (inet_aton(start_ip, &_host_addr) == 0)
877
dhcp_server->start_ip = ntohl(_host_addr.s_addr);
879
if (inet_aton(end_ip, &_host_addr) == 0)
882
dhcp_server->end_ip = ntohl(_host_addr.s_addr);
887
void g_dhcp_server_set_lease_time(GDHCPServer *dhcp_server, unsigned int lease_time)
889
if (dhcp_server == NULL)
892
dhcp_server->lease_seconds = lease_time;
895
void g_dhcp_server_set_debug(GDHCPServer *dhcp_server,
896
GDHCPDebugFunc func, gpointer user_data)
898
if (dhcp_server == NULL)
901
dhcp_server->debug_func = func;
902
dhcp_server->debug_data = user_data;