xref: /btstack/3rd-party/lwip/dhcp-server/dhserver.c (revision e3ba22907f903f11cd12321c31e7936b8dd1157e)
1 /*
2  * The MIT License (MIT)
3  *
4  * Copyright (c) 2015 by Sergey Fetisov <[email protected]>
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in all
14  * copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22  * SOFTWARE.
23  */
24 
25 #include "dhserver.h"
26 
27 /* DHCP message type */
28 #define DHCP_DISCOVER       1
29 #define DHCP_OFFER          2
30 #define DHCP_REQUEST        3
31 #define DHCP_DECLINE        4
32 #define DHCP_ACK            5
33 #define DHCP_NAK            6
34 #define DHCP_RELEASE        7
35 #define DHCP_INFORM         8
36 
37 /* DHCP options */
38 enum DHCP_OPTIONS
39 {
40 	DHCP_PAD                    = 0,
41 	DHCP_SUBNETMASK             = 1,
42 	DHCP_ROUTER                 = 3,
43 	DHCP_DNSSERVER              = 6,
44 	DHCP_HOSTNAME               = 12,
45 	DHCP_DNSDOMAIN              = 15,
46 	DHCP_MTU                    = 26,
47 	DHCP_BROADCAST              = 28,
48 	DHCP_PERFORMROUTERDISC      = 31,
49 	DHCP_STATICROUTE            = 33,
50 	DHCP_NISDOMAIN              = 40,
51 	DHCP_NISSERVER              = 41,
52 	DHCP_NTPSERVER              = 42,
53 	DHCP_VENDOR                 = 43,
54 	DHCP_IPADDRESS              = 50,
55 	DHCP_LEASETIME              = 51,
56 	DHCP_OPTIONSOVERLOADED      = 52,
57 	DHCP_MESSAGETYPE            = 53,
58 	DHCP_SERVERID               = 54,
59 	DHCP_PARAMETERREQUESTLIST   = 55,
60 	DHCP_MESSAGE                = 56,
61 	DHCP_MAXMESSAGESIZE         = 57,
62 	DHCP_RENEWALTIME            = 58,
63 	DHCP_REBINDTIME             = 59,
64 	DHCP_CLASSID                = 60,
65 	DHCP_CLIENTID               = 61,
66 	DHCP_USERCLASS              = 77,  /* RFC 3004 */
67 	DHCP_FQDN                   = 81,
68 	DHCP_DNSSEARCH              = 119, /* RFC 3397 */
69 	DHCP_CSR                    = 121, /* RFC 3442 */
70 	DHCP_MSCSR                  = 249, /* MS code for RFC 3442 */
71 	DHCP_END                    = 255
72 };
73 
74 typedef struct
75 {
76     uint8_t  dp_op;           /* packet opcode type */
77     uint8_t  dp_htype;        /* hardware addr type */
78     uint8_t  dp_hlen;         /* hardware addr length */
79     uint8_t  dp_hops;         /* gateway hops */
80     uint32_t dp_xid;          /* transaction ID */
81     uint16_t dp_secs;         /* seconds since boot began */
82     uint16_t dp_flags;
83     uint8_t  dp_ciaddr[4];    /* client IP address */
84     uint8_t  dp_yiaddr[4];    /* 'your' IP address */
85     uint8_t  dp_siaddr[4];    /* server IP address */
86     uint8_t  dp_giaddr[4];    /* gateway IP address */
87     uint8_t  dp_chaddr[16];   /* client hardware address */
88     uint8_t  dp_legacy[192];
89     uint8_t  dp_magic[4];
90     uint8_t  dp_options[275]; /* options area */
91 } DHCP_TYPE;
92 
93 DHCP_TYPE dhcp_data;
94 static struct udp_pcb *pcb = NULL;
95 static dhcp_config_t *config = NULL;
96 
97 char magic_cookie[] = {0x63,0x82,0x53,0x63};
98 
99 static uint32_t get_addr32(const uint8_t addr[4]) {
100 	return PP_HTONL(LWIP_MAKEU32(addr[0],addr[1],addr[2],addr[3]));
101 }
102 
103 static void set_addr32(uint8_t *dst, uint32_t src) {
104 	memcpy(dst, &src, 4);
105 }
106 
107 static dhcp_entry_t *entry_by_ip(uint32_t ip)
108 {
109 	int i;
110 	for (i = 0; i < config->num_entry; i++)
111 		if (get_addr32(config->entries[i].addr) == ip)
112 			return &config->entries[i];
113 	return NULL;
114 }
115 
116 static dhcp_entry_t *entry_by_mac(uint8_t *mac)
117 {
118 	int i;
119 	for (i = 0; i < config->num_entry; i++)
120 		if (memcmp(config->entries[i].mac, mac, 6) == 0)
121 			return &config->entries[i];
122 	return NULL;
123 }
124 
125 static __inline bool is_vacant(dhcp_entry_t *entry)
126 {
127 	return memcmp("\0\0\0\0\0", entry->mac, 6) == 0;
128 }
129 
130 static dhcp_entry_t *vacant_address(void)
131 {
132 	int i;
133 	for (i = 0; i < config->num_entry; i++)
134 		if (is_vacant(config->entries + i))
135 			return config->entries + i;
136 	return NULL;
137 }
138 
139 static __inline void free_entry(dhcp_entry_t *entry)
140 {
141 	memset(entry->mac, 0, 6);
142 }
143 
144 static uint8_t *find_dhcp_option(uint8_t *attrs, int size, uint8_t attr)
145 {
146 	int i = 0;
147 	while ((i + 1) < size)
148 	{
149 		int next = i + attrs[i + 1] + 2;
150 		if (next > size) return NULL;
151 		if (attrs[i] == attr)
152 			return attrs + i;
153 		i = next;
154 	}
155 	return NULL;
156 }
157 
158 static int fill_options(void *dest,
159 	uint8_t msg_type,
160 	const char *domain,
161 	uint32_t dns,
162 	int lease_time,
163 	uint32_t serverid,
164 	uint32_t router,
165 	uint32_t subnet)
166 {
167 	uint8_t *ptr = (uint8_t *)dest;
168 	/* ACK message type */
169 	*ptr++ = 53;
170 	*ptr++ = 1;
171 	*ptr++ = msg_type;
172 
173 	/* dhcp server identifier */
174 	*ptr++ = DHCP_SERVERID;
175 	*ptr++ = 4;
176 	set_addr32(ptr, serverid);
177 	ptr += 4;
178 
179 	/* lease time */
180 	*ptr++ = DHCP_LEASETIME;
181 	*ptr++ = 4;
182 	*ptr++ = (lease_time >> 24) & 0xFF;
183 	*ptr++ = (lease_time >> 16) & 0xFF;
184 	*ptr++ = (lease_time >> 8) & 0xFF;
185 	*ptr++ = (lease_time >> 0) & 0xFF;
186 
187 	/* subnet mask */
188 	*ptr++ = DHCP_SUBNETMASK;
189 	*ptr++ = 4;
190 	set_addr32(ptr, subnet);
191 	ptr += 4;
192 
193 	/* router */
194 	if (router != 0)
195 	{
196 		*ptr++ = DHCP_ROUTER;
197 		*ptr++ = 4;
198 		set_addr32(ptr, router);
199 		ptr += 4;
200 	}
201 
202 	/* domain name */
203 	if (domain != NULL)
204 	{
205 		int len = strlen(domain);
206 		*ptr++ = DHCP_DNSDOMAIN;
207 		*ptr++ = len;
208 		memcpy(ptr, domain, len);
209 		ptr += len;
210 	}
211 
212 	/* domain name server (DNS) */
213 	if (dns != 0)
214 	{
215 		*ptr++ = DHCP_DNSSERVER;
216 		*ptr++ = 4;
217 		set_addr32(ptr, dns);
218 		ptr += 4;
219 	}
220 
221 	/* end */
222 	*ptr++ = DHCP_END;
223 	return ptr - (uint8_t *)dest;
224 }
225 
226 static void udp_recv_proc(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr, u16_t port)
227 {
228 	(void) arg;
229 	(void) addr;
230 
231 	uint8_t *ptr;
232 	dhcp_entry_t *entry;
233 	struct pbuf *pp;
234 
235 	unsigned int n = p->len;
236 	if (n > sizeof(dhcp_data)) n = sizeof(dhcp_data);
237 	memcpy(&dhcp_data, p->payload, n);
238 	switch (dhcp_data.dp_options[2])
239 	{
240 		case DHCP_DISCOVER:
241 			entry = entry_by_mac(dhcp_data.dp_chaddr);
242 			if (entry == NULL) entry = vacant_address();
243 			if (entry == NULL) break;
244 
245 			dhcp_data.dp_op = 2; /* reply */
246 			dhcp_data.dp_secs = 0;
247 			dhcp_data.dp_flags = 0;
248 			set_addr32(dhcp_data.dp_yiaddr, get_addr32(entry->addr));
249 			memcpy(dhcp_data.dp_magic, magic_cookie, 4);
250 
251 			memset(dhcp_data.dp_options, 0, sizeof(dhcp_data.dp_options));
252 
253 			fill_options(dhcp_data.dp_options,
254 				DHCP_OFFER,
255 				config->domain,
256 				get_addr32(config->dns),
257 				entry->lease,
258 				get_addr32(config->addr),
259 				get_addr32(config->addr),
260 				get_addr32(entry->subnet));
261 
262 			pp = pbuf_alloc(PBUF_TRANSPORT, sizeof(dhcp_data), PBUF_POOL);
263 			if (pp == NULL) break;
264 			memcpy(pp->payload, &dhcp_data, sizeof(dhcp_data));
265 			udp_sendto(upcb, pp, IP_ADDR_BROADCAST, port);
266 			pbuf_free(pp);
267 			break;
268 
269 		case DHCP_REQUEST:
270 			/* 1. find requested ipaddr in option list */
271 			ptr = find_dhcp_option(dhcp_data.dp_options, sizeof(dhcp_data.dp_options), DHCP_IPADDRESS);
272 			if (ptr == NULL) break;
273 			if (ptr[1] != 4) break;
274 			ptr += 2;
275 
276 			/* 2. does hw-address registered? */
277 			entry = entry_by_mac(dhcp_data.dp_chaddr);
278 			if (entry != NULL) free_entry(entry);
279 
280 			/* 3. find requested ipaddr */
281 			entry = entry_by_ip(get_addr32(ptr));
282 			if (entry == NULL) break;
283 			if (!is_vacant(entry)) break;
284 
285 			/* 4. fill struct fields */
286 			memcpy(dhcp_data.dp_yiaddr, ptr, 4);
287 			dhcp_data.dp_op = 2; /* reply */
288 			dhcp_data.dp_secs = 0;
289 			dhcp_data.dp_flags = 0;
290 			memcpy(dhcp_data.dp_magic, magic_cookie, 4);
291 
292 			/* 5. fill options */
293 			memset(dhcp_data.dp_options, 0, sizeof(dhcp_data.dp_options));
294 
295 			fill_options(dhcp_data.dp_options,
296 				DHCP_ACK,
297 				config->domain,
298 				get_addr32(config->dns),
299 				entry->lease,
300 				get_addr32(config->addr),
301 				get_addr32(config->addr),
302 				get_addr32(entry->subnet));
303 
304 			/* 6. send ACK */
305 			pp = pbuf_alloc(PBUF_TRANSPORT, sizeof(dhcp_data), PBUF_POOL);
306 			if (pp == NULL) break;
307 			memcpy(entry->mac, dhcp_data.dp_chaddr, 6);
308 			memcpy(pp->payload, &dhcp_data, sizeof(dhcp_data));
309 			udp_sendto(upcb, pp, IP_ADDR_BROADCAST, port);
310 			pbuf_free(pp);
311 			break;
312 
313 		default:
314 				break;
315 	}
316 	pbuf_free(p);
317 }
318 
319 err_t dhserv_init(dhcp_config_t *c)
320 {
321 	err_t err;
322 	// udp_init(); already called from lwip_init
323 	dhserv_free();
324 	pcb = udp_new();
325 	if (pcb == NULL)
326 		return ERR_MEM;
327 	err = udp_bind(pcb, IP_ADDR_ANY, c->port);
328 	if (err != ERR_OK)
329 	{
330 		dhserv_free();
331 		return err;
332 	}
333 	udp_recv(pcb, udp_recv_proc, NULL);
334 	config = c;
335 	return ERR_OK;
336 }
337 
338 void dhserv_free(void)
339 {
340 	if (pcb == NULL) return;
341 	udp_remove(pcb);
342 	pcb = NULL;
343 }
344