348 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
		
		
			
		
	
	
			348 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
|   | /*
 | ||
|  |  * The MIT License (MIT) | ||
|  |  * | ||
|  |  * Copyright (c) 2015 by Sergey Fetisov <fsenok@gmail.com> | ||
|  |  *  | ||
|  |  * Permission is hereby granted, free of charge, to any person obtaining a copy | ||
|  |  * of this software and associated documentation files (the "Software"), to deal | ||
|  |  * in the Software without restriction, including without limitation the rights | ||
|  |  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
|  |  * copies of the Software, and to permit persons to whom the Software is | ||
|  |  * furnished to do so, subject to the following conditions: | ||
|  |  *  | ||
|  |  * The above copyright notice and this permission notice shall be included in all | ||
|  |  * copies or substantial portions of the Software. | ||
|  |  *  | ||
|  |  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
|  |  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
|  |  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
|  |  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
|  |  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
|  |  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
|  |  * SOFTWARE. | ||
|  |  */ | ||
|  | 
 | ||
|  | #include "dhserver.h"
 | ||
|  | 
 | ||
|  | /* DHCP message type */ | ||
|  | #define DHCP_DISCOVER       1
 | ||
|  | #define DHCP_OFFER          2
 | ||
|  | #define DHCP_REQUEST        3
 | ||
|  | #define DHCP_DECLINE        4
 | ||
|  | #define DHCP_ACK            5
 | ||
|  | #define DHCP_NAK            6
 | ||
|  | #define DHCP_RELEASE        7
 | ||
|  | #define DHCP_INFORM         8
 | ||
|  | 
 | ||
|  | /* DHCP options */ | ||
|  | enum DHCP_OPTIONS | ||
|  | { | ||
|  | 	DHCP_PAD                    = 0, | ||
|  | 	DHCP_SUBNETMASK             = 1, | ||
|  | 	DHCP_ROUTER                 = 3, | ||
|  | 	DHCP_DNSSERVER              = 6, | ||
|  | 	DHCP_HOSTNAME               = 12, | ||
|  | 	DHCP_DNSDOMAIN              = 15, | ||
|  | 	DHCP_MTU                    = 26, | ||
|  | 	DHCP_BROADCAST              = 28, | ||
|  | 	DHCP_PERFORMROUTERDISC      = 31, | ||
|  | 	DHCP_STATICROUTE            = 33, | ||
|  | 	DHCP_NISDOMAIN              = 40, | ||
|  | 	DHCP_NISSERVER              = 41, | ||
|  | 	DHCP_NTPSERVER              = 42, | ||
|  | 	DHCP_VENDOR                 = 43, | ||
|  | 	DHCP_IPADDRESS              = 50, | ||
|  | 	DHCP_LEASETIME              = 51, | ||
|  | 	DHCP_OPTIONSOVERLOADED      = 52, | ||
|  | 	DHCP_MESSAGETYPE            = 53, | ||
|  | 	DHCP_SERVERID               = 54, | ||
|  | 	DHCP_PARAMETERREQUESTLIST   = 55, | ||
|  | 	DHCP_MESSAGE                = 56, | ||
|  | 	DHCP_MAXMESSAGESIZE         = 57, | ||
|  | 	DHCP_RENEWALTIME            = 58, | ||
|  | 	DHCP_REBINDTIME             = 59, | ||
|  | 	DHCP_CLASSID                = 60, | ||
|  | 	DHCP_CLIENTID               = 61, | ||
|  | 	DHCP_USERCLASS              = 77,  /* RFC 3004 */ | ||
|  | 	DHCP_FQDN                   = 81, | ||
|  | 	DHCP_DNSSEARCH              = 119, /* RFC 3397 */ | ||
|  | 	DHCP_CSR                    = 121, /* RFC 3442 */ | ||
|  | 	DHCP_MSCSR                  = 249, /* MS code for RFC 3442 */ | ||
|  | 	DHCP_END                    = 255 | ||
|  | }; | ||
|  | 
 | ||
|  | typedef struct | ||
|  | { | ||
|  |     uint8_t  dp_op;           /* packet opcode type */ | ||
|  |     uint8_t  dp_htype;        /* hardware addr type */ | ||
|  |     uint8_t  dp_hlen;         /* hardware addr length */ | ||
|  |     uint8_t  dp_hops;         /* gateway hops */ | ||
|  |     uint32_t dp_xid;          /* transaction ID */ | ||
|  |     uint16_t dp_secs;         /* seconds since boot began */ | ||
|  |     uint16_t dp_flags; | ||
|  |     uint8_t  dp_ciaddr[4];    /* client IP address */ | ||
|  |     uint8_t  dp_yiaddr[4];    /* 'your' IP address */ | ||
|  |     uint8_t  dp_siaddr[4];    /* server IP address */ | ||
|  |     uint8_t  dp_giaddr[4];    /* gateway IP address */ | ||
|  |     uint8_t  dp_chaddr[16];   /* client hardware address */ | ||
|  |     uint8_t  dp_legacy[192]; | ||
|  |     uint8_t  dp_magic[4];      | ||
|  |     uint8_t  dp_options[275]; /* options area */ | ||
|  | } DHCP_TYPE; | ||
|  | 
 | ||
|  | DHCP_TYPE dhcp_data; | ||
|  | static struct udp_pcb *pcb = NULL; | ||
|  | static const dhcp_config_t *config = NULL; | ||
|  | 
 | ||
|  | char magic_cookie[] = {0x63,0x82,0x53,0x63}; | ||
|  | 
 | ||
|  | static uint32_t get_ip(const uint8_t *pnt) | ||
|  | { | ||
|  |   uint32_t result; | ||
|  |   memcpy(&result, pnt, sizeof(result)); | ||
|  |   return result; | ||
|  | } | ||
|  | 
 | ||
|  | static void set_ip(uint8_t *pnt, uint32_t value) | ||
|  | { | ||
|  |   memcpy(pnt, &value, sizeof(value)); | ||
|  | } | ||
|  | 
 | ||
|  | static dhcp_entry_t *entry_by_ip(uint32_t ip) | ||
|  | { | ||
|  | 	int i; | ||
|  | 	for (i = 0; i < config->num_entry; i++) | ||
|  | 		if (get_ip(config->entries[i].addr) == ip) | ||
|  | 			return &config->entries[i]; | ||
|  | 	return NULL; | ||
|  | } | ||
|  | 
 | ||
|  | static dhcp_entry_t *entry_by_mac(uint8_t *mac) | ||
|  | { | ||
|  | 	int i; | ||
|  | 	for (i = 0; i < config->num_entry; i++) | ||
|  | 		if (memcmp(config->entries[i].mac, mac, 6) == 0) | ||
|  | 			return &config->entries[i]; | ||
|  | 	return NULL; | ||
|  | } | ||
|  | 
 | ||
|  | static __inline bool is_vacant(dhcp_entry_t *entry) | ||
|  | { | ||
|  | 	return memcmp("\0\0\0\0\0", entry->mac, 6) == 0; | ||
|  | } | ||
|  | 
 | ||
|  | static dhcp_entry_t *vacant_address(void) | ||
|  | { | ||
|  | 	int i; | ||
|  | 	for (i = 0; i < config->num_entry; i++) | ||
|  | 		if (is_vacant(config->entries + i)) | ||
|  | 			return config->entries + i; | ||
|  | 	return NULL; | ||
|  | } | ||
|  | 
 | ||
|  | static __inline void free_entry(dhcp_entry_t *entry) | ||
|  | { | ||
|  | 	memset(entry->mac, 0, 6); | ||
|  | } | ||
|  | 
 | ||
|  | uint8_t *find_dhcp_option(uint8_t *attrs, int size, uint8_t attr) | ||
|  | { | ||
|  | 	int i = 0; | ||
|  | 	while ((i + 1) < size) | ||
|  | 	{ | ||
|  | 		int next = i + attrs[i + 1] + 2; | ||
|  | 		if (next > size) return NULL; | ||
|  | 		if (attrs[i] == attr) | ||
|  | 			return attrs + i; | ||
|  | 		i = next; | ||
|  | 	} | ||
|  | 	return NULL; | ||
|  | } | ||
|  | 
 | ||
|  | int fill_options(void *dest, | ||
|  | 	uint8_t msg_type, | ||
|  | 	const char *domain, | ||
|  | 	uint32_t dns, | ||
|  | 	int lease_time, | ||
|  | 	uint32_t serverid, | ||
|  | 	uint32_t router, | ||
|  | 	uint32_t subnet) | ||
|  | { | ||
|  | 	uint8_t *ptr = (uint8_t *)dest; | ||
|  | 	/* ACK message type */ | ||
|  | 	*ptr++ = 53; | ||
|  | 	*ptr++ = 1; | ||
|  | 	*ptr++ = msg_type; | ||
|  | 
 | ||
|  | 	/* dhcp server identifier */ | ||
|  | 	*ptr++ = DHCP_SERVERID; | ||
|  | 	*ptr++ = 4; | ||
|  | 	set_ip(ptr, serverid); | ||
|  | 	ptr += 4; | ||
|  | 
 | ||
|  | 	/* lease time */ | ||
|  | 	*ptr++ = DHCP_LEASETIME; | ||
|  | 	*ptr++ = 4; | ||
|  | 	*ptr++ = (lease_time >> 24) & 0xFF; | ||
|  | 	*ptr++ = (lease_time >> 16) & 0xFF; | ||
|  | 	*ptr++ = (lease_time >> 8) & 0xFF; | ||
|  | 	*ptr++ = (lease_time >> 0) & 0xFF; | ||
|  | 
 | ||
|  | 	/* subnet mask */ | ||
|  | 	*ptr++ = DHCP_SUBNETMASK; | ||
|  | 	*ptr++ = 4; | ||
|  | 	set_ip(ptr, subnet); | ||
|  | 	ptr += 4; | ||
|  | 
 | ||
|  | 	/* router */ | ||
|  | 	if (router != 0) | ||
|  | 	{ | ||
|  | 		*ptr++ = DHCP_ROUTER; | ||
|  | 		*ptr++ = 4; | ||
|  | 		set_ip(ptr, router); | ||
|  | 		ptr += 4; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/* domain name */ | ||
|  | 	if (domain != NULL) | ||
|  | 	{ | ||
|  | 		int len = strlen(domain); | ||
|  | 		*ptr++ = DHCP_DNSDOMAIN; | ||
|  | 		*ptr++ = len; | ||
|  | 		memcpy(ptr, domain, len); | ||
|  | 		ptr += len; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/* domain name server (DNS) */ | ||
|  | 	if (dns != 0) | ||
|  | 	{ | ||
|  | 		*ptr++ = DHCP_DNSSERVER; | ||
|  | 		*ptr++ = 4; | ||
|  | 		set_ip(ptr, dns); | ||
|  | 		ptr += 4; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/* end */ | ||
|  | 	*ptr++ = DHCP_END; | ||
|  | 	return ptr - (uint8_t *)dest; | ||
|  | } | ||
|  | 
 | ||
|  | static void udp_recv_proc(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr, u16_t port) | ||
|  | { | ||
|  | 	uint8_t *ptr; | ||
|  | 	dhcp_entry_t *entry; | ||
|  | 	struct pbuf *pp; | ||
|  | 
 | ||
|  | 	(void)arg; | ||
|  | 	(void)addr; | ||
|  | 
 | ||
|  | 	unsigned n = p->len; | ||
|  | 	if (n > sizeof(dhcp_data)) n = sizeof(dhcp_data); | ||
|  | 	memcpy(&dhcp_data, p->payload, n); | ||
|  | 	switch (dhcp_data.dp_options[2]) | ||
|  | 	{ | ||
|  | 		case DHCP_DISCOVER: | ||
|  | 			entry = entry_by_mac(dhcp_data.dp_chaddr); | ||
|  | 			if (entry == NULL) entry = vacant_address(); | ||
|  | 			if (entry == NULL) break; | ||
|  | 
 | ||
|  | 			dhcp_data.dp_op = 2; /* reply */ | ||
|  | 			dhcp_data.dp_secs = 0; | ||
|  | 			dhcp_data.dp_flags = 0; | ||
|  | 			set_ip(dhcp_data.dp_yiaddr, get_ip(entry->addr)); | ||
|  | 			memcpy(dhcp_data.dp_magic, magic_cookie, 4); | ||
|  | 
 | ||
|  | 			memset(dhcp_data.dp_options, 0, sizeof(dhcp_data.dp_options)); | ||
|  | 
 | ||
|  | 			fill_options(dhcp_data.dp_options, | ||
|  | 				DHCP_OFFER, | ||
|  | 				config->domain, | ||
|  | 				get_ip(config->dns), | ||
|  | 				entry->lease,  | ||
|  | 				get_ip(config->addr), | ||
|  | 				get_ip(config->addr),  | ||
|  | 				get_ip(entry->subnet)); | ||
|  | 
 | ||
|  | 			pp = pbuf_alloc(PBUF_TRANSPORT, sizeof(dhcp_data), PBUF_POOL); | ||
|  | 			if (pp == NULL) break; | ||
|  | 			memcpy(pp->payload, &dhcp_data, sizeof(dhcp_data)); | ||
|  | 			udp_sendto(upcb, pp, IP_ADDR_BROADCAST, port); | ||
|  | 			pbuf_free(pp); | ||
|  | 			break; | ||
|  | 
 | ||
|  | 		case DHCP_REQUEST: | ||
|  | 			/* 1. find requested ipaddr in option list */ | ||
|  | 			ptr = find_dhcp_option(dhcp_data.dp_options, sizeof(dhcp_data.dp_options), DHCP_IPADDRESS); | ||
|  | 			if (ptr == NULL) break; | ||
|  | 			if (ptr[1] != 4) break; | ||
|  | 			ptr += 2; | ||
|  | 
 | ||
|  | 			/* 2. does hw-address registered? */ | ||
|  | 			entry = entry_by_mac(dhcp_data.dp_chaddr); | ||
|  | 			if (entry != NULL) free_entry(entry); | ||
|  | 
 | ||
|  | 			/* 3. find requested ipaddr */ | ||
|  | 			entry = entry_by_ip(get_ip(ptr)); | ||
|  | 			if (entry == NULL) break; | ||
|  | 			if (!is_vacant(entry)) break; | ||
|  | 
 | ||
|  | 			/* 4. fill struct fields */ | ||
|  | 			memcpy(dhcp_data.dp_yiaddr, ptr, 4); | ||
|  | 			dhcp_data.dp_op = 2; /* reply */ | ||
|  | 			dhcp_data.dp_secs = 0; | ||
|  | 			dhcp_data.dp_flags = 0; | ||
|  | 			memcpy(dhcp_data.dp_magic, magic_cookie, 4); | ||
|  | 
 | ||
|  | 			/* 5. fill options */ | ||
|  | 			memset(dhcp_data.dp_options, 0, sizeof(dhcp_data.dp_options)); | ||
|  | 
 | ||
|  | 			fill_options(dhcp_data.dp_options, | ||
|  | 				DHCP_ACK, | ||
|  | 				config->domain, | ||
|  | 				get_ip(config->dns), | ||
|  | 				entry->lease,  | ||
|  | 				get_ip(config->addr), | ||
|  | 				get_ip(config->addr),  | ||
|  | 				get_ip(entry->subnet)); | ||
|  | 
 | ||
|  | 			/* 6. send ACK */ | ||
|  | 			pp = pbuf_alloc(PBUF_TRANSPORT, sizeof(dhcp_data), PBUF_POOL); | ||
|  | 			if (pp == NULL) break; | ||
|  | 			memcpy(entry->mac, dhcp_data.dp_chaddr, 6); | ||
|  | 			memcpy(pp->payload, &dhcp_data, sizeof(dhcp_data)); | ||
|  | 			udp_sendto(upcb, pp, IP_ADDR_BROADCAST, port); | ||
|  | 			pbuf_free(pp); | ||
|  | 			break; | ||
|  | 
 | ||
|  | 		default: | ||
|  | 				break; | ||
|  | 	} | ||
|  | 	pbuf_free(p); | ||
|  | } | ||
|  | 
 | ||
|  | err_t dhserv_init(const dhcp_config_t *c) | ||
|  | { | ||
|  | 	err_t err; | ||
|  | 	udp_init(); | ||
|  | 	dhserv_free(); | ||
|  | 	pcb = udp_new(); | ||
|  | 	if (pcb == NULL) | ||
|  | 		return ERR_MEM; | ||
|  | 	err = udp_bind(pcb, IP_ADDR_ANY, c->port); | ||
|  | 	if (err != ERR_OK) | ||
|  | 	{ | ||
|  | 		dhserv_free(); | ||
|  | 		return err; | ||
|  | 	} | ||
|  | 	udp_recv(pcb, udp_recv_proc, NULL); | ||
|  | 	config = c; | ||
|  | 	return ERR_OK; | ||
|  | } | ||
|  | 
 | ||
|  | void dhserv_free(void) | ||
|  | { | ||
|  | 	if (pcb == NULL) return; | ||
|  | 	udp_remove(pcb); | ||
|  | 	pcb = NULL; | ||
|  | } |