source: mainline/uspace/srv/net/dhcp/dhcp.c@ af259da

ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since af259da was af259da, checked in by Jiri Svoboda <jiri@…>, 4 years ago

DHCP request must not have ciaddr filled in when Selecting

ciaddr is to be filled in with the current client IP address
if and only if we are renewing the address lease, not if
we are requesting a new address. This is mandated by the standard.

  • Property mode set to 100644
File size: 15.2 KB
Line 
1/*
2 * Copyright (c) 2021 Jiri Svoboda
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup dhcp
30 * @{
31 */
32/**
33 * @file
34 * @brief DHCP client
35 */
36
37#include <adt/list.h>
38#include <bitops.h>
39#include <byteorder.h>
40#include <errno.h>
41#include <str_error.h>
42#include <fibril_synch.h>
43#include <inet/addr.h>
44#include <inet/eth_addr.h>
45#include <inet/dnsr.h>
46#include <inet/inetcfg.h>
47#include <io/log.h>
48#include <loc.h>
49#include <stdio.h>
50#include <stdlib.h>
51#include <str.h>
52
53#include "dhcp.h"
54#include "dhcp_std.h"
55#include "transport.h"
56
57enum {
58 /** In microseconds */
59 dhcp_discover_timeout_val = 5 * 1000 * 1000,
60 /** In microseconds */
61 dhcp_request_timeout_val = 1 * 1000 * 1000,
62 dhcp_discover_retries = 5,
63 dhcp_request_retries = 3
64};
65
66#define MAX_MSG_SIZE 1024
67static uint8_t msgbuf[MAX_MSG_SIZE];
68
69/** List of registered links (of dhcp_link_t) */
70static list_t dhcp_links;
71
72static void dhcpsrv_discover_timeout(void *);
73static void dhcpsrv_request_timeout(void *);
74
75typedef enum {
76 ds_bound,
77 ds_fail,
78 ds_init,
79 ds_init_reboot,
80 ds_rebinding,
81 ds_renewing,
82 ds_requesting,
83 ds_selecting
84} dhcp_state_t;
85
86typedef struct {
87 /** Message type */
88 enum dhcp_msg_type msg_type;
89 /** Offered address */
90 inet_naddr_t oaddr;
91 /** Server address */
92 inet_addr_t srv_addr;
93 /** Router address */
94 inet_addr_t router;
95 /** DNS server */
96 inet_addr_t dns_server;
97} dhcp_offer_t;
98
99typedef struct {
100 /** Link to dhcp_links list */
101 link_t links;
102 /** Link service ID */
103 service_id_t link_id;
104 /** Link info */
105 inet_link_info_t link_info;
106 /** Transport */
107 dhcp_transport_t dt;
108 /** Transport timeout */
109 fibril_timer_t *timeout;
110 /** Number of retries */
111 int retries_left;
112 /** Link state */
113 dhcp_state_t state;
114 /** Last received offer */
115 dhcp_offer_t offer;
116} dhcp_link_t;
117
118static void dhcpsrv_recv(void *, void *, size_t);
119
120/** Decode subnet mask into subnet prefix length. */
121static errno_t subnet_mask_decode(uint32_t mask, int *bits)
122{
123 int zbits;
124 uint32_t nmask;
125
126 if (mask == 0xffffffff) {
127 *bits = 32;
128 return EOK;
129 }
130
131 zbits = 1 + fnzb32(mask ^ 0xffffffff);
132 nmask = BIT_RRANGE(uint32_t, zbits);
133
134 if ((mask ^ nmask) != 0xffffffff) {
135 /* The mask is not in the form 1**n,0**m */
136 return EINVAL;
137 }
138
139 *bits = 32 - zbits;
140 return EOK;
141}
142
143static uint32_t dhcp_uint32_decode(uint8_t *data)
144{
145 return
146 ((uint32_t)data[0] << 24) |
147 ((uint32_t)data[1] << 16) |
148 ((uint32_t)data[2] << 8) |
149 ((uint32_t)data[3]);
150}
151
152static errno_t dhcp_send_discover(dhcp_link_t *dlink)
153{
154 dhcp_hdr_t *hdr = (dhcp_hdr_t *)msgbuf;
155 uint8_t *opt = msgbuf + sizeof(dhcp_hdr_t);
156
157 memset(msgbuf, 0, MAX_MSG_SIZE);
158 hdr->op = op_bootrequest;
159 hdr->htype = 1; /* AHRD_ETHERNET */
160 hdr->hlen = ETH_ADDR_SIZE;
161 hdr->xid = host2uint32_t_be(42);
162 hdr->flags = flag_broadcast;
163
164 eth_addr_encode(&dlink->link_info.mac_addr, hdr->chaddr);
165 hdr->opt_magic = host2uint32_t_be(dhcp_opt_magic);
166
167 opt[0] = opt_msg_type;
168 opt[1] = 1;
169 opt[2] = msg_dhcpdiscover;
170 opt[3] = opt_end;
171
172 return dhcp_send(&dlink->dt, msgbuf, sizeof(dhcp_hdr_t) + 4);
173}
174
175static errno_t dhcp_send_request(dhcp_link_t *dlink, dhcp_offer_t *offer)
176{
177 dhcp_hdr_t *hdr = (dhcp_hdr_t *)msgbuf;
178 uint8_t *opt = msgbuf + sizeof(dhcp_hdr_t);
179 size_t i;
180
181 memset(msgbuf, 0, MAX_MSG_SIZE);
182 hdr->op = op_bootrequest;
183 hdr->htype = 1; /* AHRD_ETHERNET */
184 hdr->hlen = 6;
185 hdr->xid = host2uint32_t_be(42);
186 hdr->flags = flag_broadcast;
187 eth_addr_encode(&dlink->link_info.mac_addr, hdr->chaddr);
188 hdr->opt_magic = host2uint32_t_be(dhcp_opt_magic);
189
190 i = 0;
191
192 opt[i++] = opt_msg_type;
193 opt[i++] = 1;
194 opt[i++] = msg_dhcprequest;
195
196 opt[i++] = opt_req_ip_addr;
197 opt[i++] = 4;
198 opt[i++] = offer->oaddr.addr >> 24;
199 opt[i++] = (offer->oaddr.addr >> 16) & 0xff;
200 opt[i++] = (offer->oaddr.addr >> 8) & 0xff;
201 opt[i++] = offer->oaddr.addr & 0xff;
202
203 opt[i++] = opt_server_id;
204 opt[i++] = 4;
205 opt[i++] = offer->srv_addr.addr >> 24;
206 opt[i++] = (offer->srv_addr.addr >> 16) & 0xff;
207 opt[i++] = (offer->srv_addr.addr >> 8) & 0xff;
208 opt[i++] = offer->srv_addr.addr & 0xff;
209
210 opt[i++] = opt_end;
211
212 return dhcp_send(&dlink->dt, msgbuf, sizeof(dhcp_hdr_t) + i);
213}
214
215static errno_t dhcp_parse_reply(void *msg, size_t size, dhcp_offer_t *offer)
216{
217 dhcp_hdr_t *hdr = (dhcp_hdr_t *)msg;
218 inet_addr_t yiaddr;
219 inet_addr_t siaddr;
220 inet_addr_t giaddr;
221 uint32_t subnet_mask;
222 bool have_subnet_mask = false;
223 bool have_server_id = false;
224 int subnet_bits;
225 char *saddr;
226 uint8_t opt_type, opt_len;
227 uint8_t *msgb;
228 errno_t rc;
229 size_t i;
230
231 log_msg(LOG_DEFAULT, LVL_DEBUG, "Receive reply");
232 memset(offer, 0, sizeof(*offer));
233
234 inet_addr_set(uint32_t_be2host(hdr->yiaddr), &yiaddr);
235 rc = inet_addr_format(&yiaddr, &saddr);
236 if (rc != EOK)
237 return rc;
238
239 log_msg(LOG_DEFAULT, LVL_DEBUG, "Your IP address: %s", saddr);
240 free(saddr);
241
242 inet_addr_set(uint32_t_be2host(hdr->siaddr), &siaddr);
243 rc = inet_addr_format(&siaddr, &saddr);
244 if (rc != EOK)
245 return rc;
246
247 log_msg(LOG_DEFAULT, LVL_DEBUG, "Next server IP address: %s", saddr);
248 free(saddr);
249
250 inet_addr_set(uint32_t_be2host(hdr->giaddr), &giaddr);
251 rc = inet_addr_format(&giaddr, &saddr);
252 if (rc != EOK)
253 return rc;
254
255 log_msg(LOG_DEFAULT, LVL_DEBUG, "Relay agent IP address: %s", saddr);
256 free(saddr);
257
258 inet_naddr_set(yiaddr.addr, 0, &offer->oaddr);
259
260 msgb = (uint8_t *)msg;
261
262 i = sizeof(dhcp_hdr_t);
263 while (i < size) {
264 opt_type = msgb[i++];
265
266 if (opt_type == opt_pad)
267 continue;
268 if (opt_type == opt_end)
269 break;
270
271 if (i >= size)
272 return EINVAL;
273
274 opt_len = msgb[i++];
275
276 if (i + opt_len > size)
277 return EINVAL;
278
279 switch (opt_type) {
280 case opt_subnet_mask:
281 if (opt_len != 4)
282 return EINVAL;
283 subnet_mask = dhcp_uint32_decode(&msgb[i]);
284 rc = subnet_mask_decode(subnet_mask, &subnet_bits);
285 if (rc != EOK)
286 return EINVAL;
287 offer->oaddr.prefix = subnet_bits;
288 have_subnet_mask = true;
289 break;
290 case opt_msg_type:
291 if (opt_len != 1)
292 return EINVAL;
293 offer->msg_type = msgb[i];
294 break;
295 case opt_server_id:
296 if (opt_len != 4)
297 return EINVAL;
298 inet_addr_set(dhcp_uint32_decode(&msgb[i]),
299 &offer->srv_addr);
300 have_server_id = true;
301 break;
302 case opt_router:
303 if (opt_len != 4)
304 return EINVAL;
305 inet_addr_set(dhcp_uint32_decode(&msgb[i]),
306 &offer->router);
307 break;
308 case opt_dns_server:
309 if (opt_len < 4 || opt_len % 4 != 0)
310 return EINVAL;
311 /* XXX Handle multiple DNS servers properly */
312 inet_addr_set(dhcp_uint32_decode(&msgb[i]),
313 &offer->dns_server);
314 break;
315 case opt_end:
316 break;
317 default:
318 break;
319 }
320
321 /* Advance to the next option */
322 i = i + opt_len;
323 }
324
325 if (!have_server_id) {
326 log_msg(LOG_DEFAULT, LVL_ERROR, "Missing server ID option.");
327 return rc;
328 }
329
330 if (!have_subnet_mask) {
331 log_msg(LOG_DEFAULT, LVL_ERROR, "Missing subnet mask option.");
332 return rc;
333 }
334
335 rc = inet_naddr_format(&offer->oaddr, &saddr);
336 if (rc != EOK)
337 return rc;
338
339 log_msg(LOG_DEFAULT, LVL_DEBUG, "Offered network address: %s", saddr);
340 free(saddr);
341
342 if (offer->router.addr != 0) {
343 rc = inet_addr_format(&offer->router, &saddr);
344 if (rc != EOK)
345 return rc;
346
347 log_msg(LOG_DEFAULT, LVL_DEBUG, "Router address: %s", saddr);
348 free(saddr);
349 }
350
351 if (offer->dns_server.addr != 0) {
352 rc = inet_addr_format(&offer->dns_server, &saddr);
353 if (rc != EOK)
354 return rc;
355
356 log_msg(LOG_DEFAULT, LVL_DEBUG, "DNS server: %s", saddr);
357 free(saddr);
358 }
359
360 return EOK;
361}
362
363static errno_t dhcp_cfg_create(service_id_t iplink, dhcp_offer_t *offer)
364{
365 errno_t rc;
366 service_id_t addr_id;
367 service_id_t sroute_id;
368 inet_naddr_t defr;
369
370 rc = inetcfg_addr_create_static("dhcp4a", &offer->oaddr, iplink,
371 &addr_id);
372 if (rc != EOK) {
373 log_msg(LOG_DEFAULT, LVL_ERROR,
374 "Error creating IP address %s: %s", "dhcp4a", str_error(rc));
375 return rc;
376 }
377
378 if (offer->router.addr != 0) {
379 inet_naddr_set(0, 0, &defr);
380
381 rc = inetcfg_sroute_create("dhcpdef", &defr, &offer->router, &sroute_id);
382 if (rc != EOK) {
383 log_msg(LOG_DEFAULT, LVL_ERROR, "Error creating "
384 "default route %s: %s.", "dhcpdef", str_error(rc));
385 return rc;
386 }
387 }
388
389 if (offer->dns_server.addr != 0) {
390 rc = dnsr_set_srvaddr(&offer->dns_server);
391 if (rc != EOK) {
392 log_msg(LOG_DEFAULT, LVL_ERROR, "Error setting "
393 "nameserver address: %s)", str_error(rc));
394 return rc;
395 }
396 }
397
398 return EOK;
399}
400
401void dhcpsrv_links_init(void)
402{
403 list_initialize(&dhcp_links);
404}
405
406static dhcp_link_t *dhcpsrv_link_find(service_id_t link_id)
407{
408 list_foreach(dhcp_links, links, dhcp_link_t, dlink) {
409 if (dlink->link_id == link_id)
410 return dlink;
411 }
412
413 return NULL;
414}
415
416static void dhcp_link_set_failed(dhcp_link_t *dlink)
417{
418 log_msg(LOG_DEFAULT, LVL_NOTE, "Giving up on link %s",
419 dlink->link_info.name);
420 dlink->state = ds_fail;
421}
422
423static errno_t dhcp_discover_proc(dhcp_link_t *dlink)
424{
425 dlink->state = ds_selecting;
426
427 errno_t rc = dhcp_send_discover(dlink);
428 if (rc != EOK)
429 return EIO;
430
431 dlink->retries_left = dhcp_discover_retries;
432
433 if ((dlink->timeout->state == fts_not_set) ||
434 (dlink->timeout->state == fts_fired))
435 fibril_timer_set(dlink->timeout, dhcp_discover_timeout_val,
436 dhcpsrv_discover_timeout, dlink);
437
438 return rc;
439}
440
441errno_t dhcpsrv_link_add(service_id_t link_id)
442{
443 dhcp_link_t *dlink;
444 errno_t rc;
445
446 log_msg(LOG_DEFAULT, LVL_DEBUG, "dhcpsrv_link_add(%zu)", link_id);
447
448 if (dhcpsrv_link_find(link_id) != NULL) {
449 log_msg(LOG_DEFAULT, LVL_NOTE, "Link %zu already added",
450 link_id);
451 return EEXIST;
452 }
453
454 dlink = calloc(1, sizeof(dhcp_link_t));
455 if (dlink == NULL)
456 return ENOMEM;
457
458 dlink->link_id = link_id;
459 dlink->timeout = fibril_timer_create(NULL);
460 if (dlink->timeout == NULL) {
461 rc = ENOMEM;
462 goto error;
463 }
464
465 /* Get link hardware address */
466 rc = inetcfg_link_get(link_id, &dlink->link_info);
467 if (rc != EOK) {
468 log_msg(LOG_DEFAULT, LVL_ERROR, "Error getting properties "
469 "for link %zu.", link_id);
470 rc = EIO;
471 goto error;
472 }
473
474 rc = dhcp_transport_init(&dlink->dt, link_id, dhcpsrv_recv, dlink);
475 if (rc != EOK) {
476 log_msg(LOG_DEFAULT, LVL_ERROR, "Error initializing DHCP "
477 "transport for link %s.", dlink->link_info.name);
478 rc = EIO;
479 goto error;
480 }
481
482 log_msg(LOG_DEFAULT, LVL_DEBUG, "Send DHCPDISCOVER");
483 rc = dhcp_discover_proc(dlink);
484 if (rc != EOK) {
485 log_msg(LOG_DEFAULT, LVL_ERROR, "Error sending DHCPDISCOVER.");
486 dhcp_link_set_failed(dlink);
487 rc = EIO;
488 goto error;
489 }
490
491 list_append(&dlink->links, &dhcp_links);
492
493 return EOK;
494error:
495 if (dlink != NULL && dlink->timeout != NULL)
496 fibril_timer_destroy(dlink->timeout);
497 free(dlink);
498 return rc;
499}
500
501errno_t dhcpsrv_link_remove(service_id_t link_id)
502{
503 return ENOTSUP;
504}
505
506errno_t dhcpsrv_discover(service_id_t link_id)
507{
508 log_msg(LOG_DEFAULT, LVL_DEBUG, "dhcpsrv_link_add(%zu)", link_id);
509
510 dhcp_link_t *dlink = dhcpsrv_link_find(link_id);
511
512 if (dlink == NULL) {
513 log_msg(LOG_DEFAULT, LVL_NOTE, "Link %zu doesn't exist",
514 link_id);
515 return EINVAL;
516 }
517
518 return dhcp_discover_proc(dlink);
519}
520
521static void dhcpsrv_recv_offer(dhcp_link_t *dlink, dhcp_offer_t *offer)
522{
523 errno_t rc;
524
525 if (dlink->state != ds_selecting) {
526 log_msg(LOG_DEFAULT, LVL_DEBUG, "Received offer in state "
527 " %d, ignoring.", (int)dlink->state);
528 return;
529 }
530
531 fibril_timer_clear(dlink->timeout);
532 dlink->offer = *offer;
533 dlink->state = ds_requesting;
534
535 log_msg(LOG_DEFAULT, LVL_DEBUG, "Send DHCPREQUEST");
536 rc = dhcp_send_request(dlink, offer);
537 if (rc != EOK) {
538 log_msg(LOG_DEFAULT, LVL_DEBUG, "Error sending request.");
539 return;
540 }
541
542 dlink->retries_left = dhcp_request_retries;
543 fibril_timer_set(dlink->timeout, dhcp_request_timeout_val,
544 dhcpsrv_request_timeout, dlink);
545}
546
547static void dhcpsrv_recv_ack(dhcp_link_t *dlink, dhcp_offer_t *offer)
548{
549 errno_t rc;
550
551 if (dlink->state != ds_requesting) {
552 log_msg(LOG_DEFAULT, LVL_DEBUG, "Received ack in state "
553 " %d, ignoring.", (int)dlink->state);
554 return;
555 }
556
557 fibril_timer_clear(dlink->timeout);
558 dlink->offer = *offer;
559 dlink->state = ds_bound;
560
561 rc = dhcp_cfg_create(dlink->link_id, offer);
562 if (rc != EOK) {
563 log_msg(LOG_DEFAULT, LVL_DEBUG, "Error creating configuration.");
564 return;
565 }
566
567 log_msg(LOG_DEFAULT, LVL_NOTE, "%s: Successfully configured.",
568 dlink->link_info.name);
569}
570
571static void dhcpsrv_recv(void *arg, void *msg, size_t size)
572{
573 dhcp_link_t *dlink = (dhcp_link_t *)arg;
574 dhcp_offer_t offer;
575 errno_t rc;
576
577 log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: dhcpsrv_recv() %zu bytes",
578 dlink->link_info.name, size);
579
580 rc = dhcp_parse_reply(msg, size, &offer);
581 if (rc != EOK) {
582 log_msg(LOG_DEFAULT, LVL_DEBUG, "Error parsing reply");
583 return;
584 }
585
586 switch (offer.msg_type) {
587 case msg_dhcpoffer:
588 dhcpsrv_recv_offer(dlink, &offer);
589 break;
590 case msg_dhcpack:
591 dhcpsrv_recv_ack(dlink, &offer);
592 break;
593 default:
594 log_msg(LOG_DEFAULT, LVL_DEBUG, "Received unexpected "
595 "message type. %d", (int)offer.msg_type);
596 break;
597 }
598}
599
600static void dhcpsrv_discover_timeout(void *arg)
601{
602 dhcp_link_t *dlink = (dhcp_link_t *)arg;
603 errno_t rc;
604
605 assert(dlink->state == ds_selecting);
606 log_msg(LOG_DEFAULT, LVL_NOTE, "%s: dhcpsrv_discover_timeout",
607 dlink->link_info.name);
608
609 if (dlink->retries_left == 0) {
610 log_msg(LOG_DEFAULT, LVL_NOTE, "Retries exhausted");
611 dhcp_link_set_failed(dlink);
612 return;
613 }
614 --dlink->retries_left;
615
616 log_msg(LOG_DEFAULT, LVL_DEBUG, "Send DHCPDISCOVER");
617 rc = dhcp_send_discover(dlink);
618 if (rc != EOK) {
619 log_msg(LOG_DEFAULT, LVL_ERROR, "Error sending DHCPDISCOVER");
620 dhcp_link_set_failed(dlink);
621 return;
622 }
623
624 fibril_timer_set(dlink->timeout, dhcp_discover_timeout_val,
625 dhcpsrv_discover_timeout, dlink);
626}
627
628static void dhcpsrv_request_timeout(void *arg)
629{
630 dhcp_link_t *dlink = (dhcp_link_t *)arg;
631 errno_t rc;
632
633 assert(dlink->state == ds_requesting);
634 log_msg(LOG_DEFAULT, LVL_NOTE, "%s: dhcpsrv_request_timeout",
635 dlink->link_info.name);
636
637 if (dlink->retries_left == 0) {
638 log_msg(LOG_DEFAULT, LVL_NOTE, "Retries exhausted");
639 dhcp_link_set_failed(dlink);
640 return;
641 }
642 --dlink->retries_left;
643
644 log_msg(LOG_DEFAULT, LVL_DEBUG, "Send DHCPREQUEST");
645 rc = dhcp_send_request(dlink, &dlink->offer);
646 if (rc != EOK) {
647 log_msg(LOG_DEFAULT, LVL_DEBUG, "Error sending request.");
648 dhcp_link_set_failed(dlink);
649 return;
650 }
651
652 fibril_timer_set(dlink->timeout, dhcp_request_timeout_val,
653 dhcpsrv_request_timeout, dlink);
654}
655
656/** @}
657 */
Note: See TracBrowser for help on using the repository browser.