1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21 /*
22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
24 * Copyright (c) 2016-2017, Chris Fraire <cfraire@me.com>.
25 *
26 * REQUESTING state of the client state machine.
27 */
28
29 #include <stdlib.h>
30 #include <string.h>
31 #include <search.h>
32 #include <sys/types.h>
33 #include <netinet/in.h>
34 #include <netinet/dhcp.h>
35 #include <netinet/udp.h>
36 #include <netinet/ip_var.h>
37 #include <netinet/udp_var.h>
38 #include <arpa/inet.h>
39 #include <dhcp_hostconf.h>
40 #include <dhcpagent_util.h>
41 #include <dhcpmsg.h>
42
43 #include "states.h"
44 #include "util.h"
45 #include "packet.h"
46 #include "interface.h"
47 #include "agent.h"
48
49 static PKT_LIST *select_best(dhcp_smach_t *);
50 static void request_failed(dhcp_smach_t *);
51 static stop_func_t stop_requesting;
52
53 /*
54 * send_v6_request(): sends a DHCPv6 Request message and switches to REQUESTING
55 * state. This is a separate function because a NoBinding
56 * response can also cause us to do this.
57 *
58 * input: dhcp_smach_t *: the state machine
59 * output: none
60 */
61
62 void
send_v6_request(dhcp_smach_t * dsmp)63 send_v6_request(dhcp_smach_t *dsmp)
64 {
65 dhcp_pkt_t *dpkt;
66 dhcpv6_ia_na_t d6in;
67
68 dpkt = init_pkt(dsmp, DHCPV6_MSG_REQUEST);
69 (void) add_pkt_opt(dpkt, DHCPV6_OPT_SERVERID, dsmp->dsm_serverid,
70 dsmp->dsm_serveridlen);
71
72 /* Add an IA_NA option for our controlling LIF */
73 d6in.d6in_iaid = htonl(dsmp->dsm_lif->lif_iaid);
74 d6in.d6in_t1 = htonl(0);
75 d6in.d6in_t2 = htonl(0);
76 (void) add_pkt_opt(dpkt, DHCPV6_OPT_IA_NA,
77 (dhcpv6_option_t *)&d6in + 1,
78 sizeof (d6in) - sizeof (dhcpv6_option_t));
79
80 /* Add required Option Request option */
81 (void) add_pkt_prl(dpkt, dsmp);
82
83 (void) send_pkt_v6(dsmp, dpkt, dsmp->dsm_server, stop_requesting,
84 DHCPV6_REQ_TIMEOUT, DHCPV6_REQ_MAX_RT);
85
86 /* For DHCPv6, state switch cannot fail */
87 (void) set_smach_state(dsmp, REQUESTING);
88 }
89
90 /*
91 * server_unicast_option(): determines the server address to use based on the
92 * DHCPv6 Server Unicast option present in the given
93 * packet.
94 *
95 * input: dhcp_smach_t *: the state machine
96 * PKT_LIST *: received packet (Advertisement or Reply)
97 * output: none
98 */
99
100 void
server_unicast_option(dhcp_smach_t * dsmp,PKT_LIST * plp)101 server_unicast_option(dhcp_smach_t *dsmp, PKT_LIST *plp)
102 {
103 const dhcpv6_option_t *d6o;
104 uint_t olen;
105
106 d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_UNICAST, &olen);
107 olen -= sizeof (*d6o);
108 /* LINTED: no consequent */
109 if (d6o == NULL) {
110 /* No Server Unicast option specified */
111 } else if (olen != sizeof (dsmp->dsm_server)) {
112 dhcpmsg(MSG_WARNING, "server_unicast_option: %s has Server "
113 "Unicast option with bad length",
114 pkt_type_to_string(pkt_recv_type(plp), B_TRUE));
115 } else {
116 in6_addr_t addr;
117
118 (void) memcpy(&addr, d6o + 1, olen);
119 if (IN6_IS_ADDR_UNSPECIFIED(&addr)) {
120 dhcpmsg(MSG_WARNING, "server_unicast_option: unicast "
121 "to unspecified address ignored");
122 } else if (IN6_IS_ADDR_MULTICAST(&addr)) {
123 dhcpmsg(MSG_WARNING, "server_unicast_option: unicast "
124 "to multicast address ignored");
125 } else if (IN6_IS_ADDR_V4COMPAT(&addr) ||
126 IN6_IS_ADDR_V4MAPPED(&addr)) {
127 dhcpmsg(MSG_WARNING, "server_unicast_option: unicast "
128 "to invalid address ignored");
129 } else {
130 dsmp->dsm_server = addr;
131 }
132 }
133 }
134
135 /*
136 * dhcp_requesting(): checks if OFFER packets to come in from DHCP servers.
137 * if so, chooses the best one, sends a REQUEST to the
138 * server and registers an event handler to receive
139 * the ACK/NAK. This may be called by the offer timer or
140 * by any function that wants to check for offers after
141 * canceling that timer.
142 *
143 * input: iu_tq_t *: timer queue; non-NULL if this is a timer callback
144 * void *: the state machine receiving OFFER packets
145 * output: void
146 */
147
148 void
dhcp_requesting(iu_tq_t * tqp,void * arg)149 dhcp_requesting(iu_tq_t *tqp, void *arg)
150 {
151 dhcp_smach_t *dsmp = arg;
152 dhcp_pkt_t *dpkt;
153 PKT_LIST *offer;
154 lease_t lease;
155 boolean_t isv6 = dsmp->dsm_isv6;
156
157 /*
158 * We assume here that if tqp is set, then this means we're being
159 * called back by the offer wait timer. If so, then drop our hold
160 * on the state machine. Otherwise, cancel the timer if it's running.
161 */
162 if (tqp != NULL) {
163 dhcpmsg(MSG_VERBOSE,
164 "dhcp_requesting: offer wait timer on v%d %s",
165 isv6 ? 6 : 4, dsmp->dsm_name);
166 dsmp->dsm_offer_timer = -1;
167 if (!verify_smach(dsmp))
168 return;
169 } else {
170 cancel_offer_timer(dsmp);
171 }
172
173 /*
174 * select the best OFFER; all others pitched.
175 */
176
177 offer = select_best(dsmp);
178 if (offer == NULL) {
179
180 dhcpmsg(MSG_VERBOSE,
181 "no OFFERs/Advertisements on %s, waiting...",
182 dsmp->dsm_name);
183
184 /*
185 * no acceptable OFFERs have come in. reschedule
186 * ourself for callback.
187 */
188
189 if ((dsmp->dsm_offer_timer = iu_schedule_timer(tq,
190 dsmp->dsm_offer_wait, dhcp_requesting, dsmp)) == -1) {
191
192 /*
193 * ugh. the best we can do at this point is
194 * revert back to INIT and wait for a user to
195 * restart us.
196 */
197
198 dhcpmsg(MSG_WARNING, "dhcp_requesting: cannot "
199 "reschedule callback, reverting to INIT state on "
200 "%s", dsmp->dsm_name);
201
202 stop_pkt_retransmission(dsmp);
203 (void) set_smach_state(dsmp, INIT);
204 dsmp->dsm_dflags |= DHCP_IF_FAILED;
205 ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY);
206 } else {
207 hold_smach(dsmp);
208 }
209
210 return;
211 }
212
213 /*
214 * With IPv4, the DHCPREQUEST packet we're about to transmit implicitly
215 * declines all other offers we've received. We can no longer use any
216 * cached offers, so we must discard them now. With DHCPv6, though,
217 * we're permitted to hang onto the advertisements (offers) and try
218 * them if the preferred one doesn't pan out.
219 */
220 if (!isv6)
221 free_pkt_list(&dsmp->dsm_recv_pkt_list);
222
223 /* stop collecting packets. */
224
225 stop_pkt_retransmission(dsmp);
226
227 /*
228 * For IPv4, check to see whether we got an OFFER or a BOOTP packet.
229 * If we got a BOOTP packet, go to the BOUND state now.
230 */
231 if (!isv6 && offer->opts[CD_DHCP_TYPE] == NULL) {
232 free_pkt_list(&dsmp->dsm_recv_pkt_list);
233
234 if (!set_smach_state(dsmp, REQUESTING)) {
235 dhcp_restart(dsmp);
236 return;
237 }
238
239 if (!dhcp_bound(dsmp, offer)) {
240 dhcpmsg(MSG_WARNING, "dhcp_requesting: dhcp_bound "
241 "failed for %s", dsmp->dsm_name);
242 dhcp_restart(dsmp);
243 return;
244 }
245
246 return;
247 }
248
249 save_domainname(dsmp, offer);
250
251 if (isv6) {
252 const char *estr, *msg;
253 const dhcpv6_option_t *d6o;
254 uint_t olen, msglen;
255
256 /* If there's a Status Code option, print the message */
257 d6o = dhcpv6_pkt_option(offer, NULL, DHCPV6_OPT_STATUS_CODE,
258 &olen);
259 (void) dhcpv6_status_code(d6o, olen, &estr, &msg, &msglen);
260 print_server_msg(dsmp, msg, msglen);
261
262 /* Copy in the Server ID (guaranteed to be present now) */
263 if (!save_server_id(dsmp, offer))
264 goto failure;
265
266 /*
267 * Determine how to send this message. If the Advertisement
268 * (offer) has the unicast option, then use the address
269 * specified in the option. Otherwise, send via multicast.
270 */
271 server_unicast_option(dsmp, offer);
272
273 send_v6_request(dsmp);
274 } else {
275 /* if we got a message from the server, display it. */
276 if (offer->opts[CD_MESSAGE] != NULL) {
277 print_server_msg(dsmp,
278 (char *)offer->opts[CD_MESSAGE]->value,
279 offer->opts[CD_MESSAGE]->len);
280 }
281
282 /*
283 * assemble a DHCPREQUEST, with the ciaddr field set to 0,
284 * since we got here from the INIT state.
285 */
286
287 dpkt = init_pkt(dsmp, REQUEST);
288
289 /*
290 * Grab the lease out of the OFFER; we know it's valid because
291 * select_best() already checked. The max dhcp message size
292 * option is set to the interface max, minus the size of the
293 * udp and ip headers.
294 */
295
296 (void) memcpy(&lease, offer->opts[CD_LEASE_TIME]->value,
297 sizeof (lease_t));
298
299 (void) add_pkt_opt32(dpkt, CD_LEASE_TIME, lease);
300 (void) add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE,
301 htons(dsmp->dsm_lif->lif_pif->pif_mtu -
302 sizeof (struct udpiphdr)));
303 (void) add_pkt_opt32(dpkt, CD_REQUESTED_IP_ADDR,
304 offer->pkt->yiaddr.s_addr);
305 (void) add_pkt_opt(dpkt, CD_SERVER_ID,
306 offer->opts[CD_SERVER_ID]->value,
307 offer->opts[CD_SERVER_ID]->len);
308
309 if (class_id_len != 0) {
310 (void) add_pkt_opt(dpkt, CD_CLASS_ID, class_id,
311 class_id_len);
312 }
313 (void) add_pkt_prl(dpkt, dsmp);
314
315 /*
316 * dsm_reqhost was set for this state machine in
317 * dhcp_selecting() if the DF_REQUEST_HOSTNAME option set and a
318 * host name was found
319 */
320 if (!dhcp_add_fqdn_opt(dpkt, dsmp) &&
321 dsmp->dsm_reqhost != NULL) {
322 (void) add_pkt_opt(dpkt, CD_HOSTNAME, dsmp->dsm_reqhost,
323 strlen(dsmp->dsm_reqhost));
324 }
325 (void) add_pkt_opt(dpkt, CD_END, NULL, 0);
326
327 /*
328 * send out the REQUEST, trying retransmissions. either a NAK
329 * or too many REQUEST attempts will revert us to SELECTING.
330 */
331
332 if (!set_smach_state(dsmp, REQUESTING)) {
333 dhcpmsg(MSG_ERROR, "dhcp_requesting: cannot switch to "
334 "REQUESTING state; reverting to INIT on %s",
335 dsmp->dsm_name);
336 goto failure;
337 }
338
339 (void) send_pkt(dsmp, dpkt, htonl(INADDR_BROADCAST),
340 stop_requesting);
341 }
342
343 /* all done with the offer */
344 free_pkt_entry(offer);
345
346 return;
347
348 failure:
349 dsmp->dsm_dflags |= DHCP_IF_FAILED;
350 (void) set_smach_state(dsmp, INIT);
351 ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY);
352 free_pkt_list(&dsmp->dsm_recv_pkt_list);
353 }
354
355 /*
356 * compute_points_v6(): compute the number of "points" for a given v6
357 * advertisement.
358 *
359 * input: const PKT_LIST *: packet to inspect
360 * const dhcp_smach_t *: state machine that received the packet
361 * output: int: -1 to discard, -2 to accept immediately, >=0 for preference.
362 */
363
364 static int
compute_points_v6(const PKT_LIST * pkt,const dhcp_smach_t * dsmp)365 compute_points_v6(const PKT_LIST *pkt, const dhcp_smach_t *dsmp)
366 {
367 char abuf[INET6_ADDRSTRLEN];
368 int points = 0;
369 const dhcpv6_option_t *d6o, *d6so;
370 uint_t olen, solen;
371 int i;
372 const char *estr, *msg;
373 uint_t msglen;
374
375 /*
376 * Look through the packet contents. Valid packets must have our
377 * client ID and a server ID, which has already been checked by
378 * dhcp_packet_lif. Bonus points for each option.
379 */
380
381 /* One point for having a valid message. */
382 points++;
383
384 /*
385 * Per RFC 3315, if the Advertise message says, "yes, we have no
386 * bananas today," then ignore the entire message. (Why it's just
387 * _this_ error and no other is a bit of a mystery, but a standard is a
388 * standard.)
389 */
390 d6o = dhcpv6_pkt_option(pkt, NULL, DHCPV6_OPT_STATUS_CODE, &olen);
391 if (dhcpv6_status_code(d6o, olen, &estr, &msg, &msglen) ==
392 DHCPV6_STAT_NOADDRS) {
393 dhcpmsg(MSG_INFO,
394 "discard advertisement from %s on %s: no address status",
395 inet_ntop(AF_INET6,
396 &((struct sockaddr_in6 *)&pkt->pktfrom)->sin6_addr,
397 abuf, sizeof (abuf)), dsmp->dsm_name);
398 return (-1);
399 }
400
401 /* Two points for each batch of offered IP addresses */
402 d6o = NULL;
403 while ((d6o = dhcpv6_pkt_option(pkt, d6o, DHCPV6_OPT_IA_NA,
404 &olen)) != NULL) {
405
406 /*
407 * Note that it's possible to have "no bananas" on an
408 * individual IA. We must look for that here.
409 *
410 * RFC 3315 section 17.1.3 does not refer to the status code
411 * embedded in the IA itself. However, the TAHI test suite
412 * checks for this specific case. Because it's extremely
413 * unlikely that any usable server is going to report that it
414 * has no addresses on a network using DHCP for address
415 * assignment, we allow such messages to be dropped.
416 */
417 d6so = dhcpv6_find_option(
418 (const char *)d6o + sizeof (dhcpv6_ia_na_t),
419 olen - sizeof (dhcpv6_ia_na_t), NULL,
420 DHCPV6_OPT_STATUS_CODE, &solen);
421 if (dhcpv6_status_code(d6so, solen, &estr, &msg, &msglen) ==
422 DHCPV6_STAT_NOADDRS)
423 return (-1);
424 points += 2;
425 }
426
427 /*
428 * Note that we drive on in the case where there are no addresses. The
429 * hope here is that we'll at least get some useful configuration
430 * information.
431 */
432
433 /* One point for each requested option */
434 for (i = 0; i < dsmp->dsm_prllen; i++) {
435 if (dhcpv6_pkt_option(pkt, NULL, dsmp->dsm_prl[i], NULL) !=
436 NULL)
437 points++;
438 }
439
440 /*
441 * Ten points for each point of "preference." Note: the value 255 is
442 * special. It means "stop right now and select this server."
443 */
444 d6o = dhcpv6_pkt_option(pkt, NULL, DHCPV6_OPT_PREFERENCE, &olen);
445 if (d6o != NULL && olen == sizeof (*d6o) + 1) {
446 int pref = *(const uchar_t *)(d6o + 1);
447
448 if (pref == 255)
449 return (-2);
450 points += 10 * pref;
451 }
452
453 return (points);
454 }
455
456 /*
457 * compute_points_v4(): compute the number of "points" for a given v4 offer.
458 *
459 * input: const PKT_LIST *: packet to inspect
460 * const dhcp_smach_t *: state machine that received the packet
461 * output: int: -1 to discard, >=0 for preference.
462 */
463
464 static int
compute_points_v4(const PKT_LIST * pkt)465 compute_points_v4(const PKT_LIST *pkt)
466 {
467 int points = 0;
468
469 if (pkt->opts[CD_DHCP_TYPE] == NULL) {
470 dhcpmsg(MSG_VERBOSE, "compute_points_v4: valid BOOTP reply");
471 goto valid_offer;
472 }
473
474 if (pkt->opts[CD_LEASE_TIME] == NULL) {
475 dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER without lease "
476 "time");
477 return (-1);
478 }
479
480 if (pkt->opts[CD_LEASE_TIME]->len != sizeof (lease_t)) {
481 dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER with garbled "
482 "lease time");
483 return (-1);
484 }
485
486 if (pkt->opts[CD_SERVER_ID] == NULL) {
487 dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER without server "
488 "id");
489 return (-1);
490 }
491
492 if (pkt->opts[CD_SERVER_ID]->len != sizeof (ipaddr_t)) {
493 dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER with garbled "
494 "server id");
495 return (-1);
496 }
497
498 /* valid DHCP OFFER. see if we got our parameters. */
499 dhcpmsg(MSG_VERBOSE, "compute_points_v4: valid OFFER packet");
500 points += 30;
501
502 valid_offer:
503 if (pkt->rfc1048)
504 points += 5;
505
506 /*
507 * also could be faked, though more difficult because the encapsulation
508 * is hard to encode on a BOOTP server; plus there's not as much real
509 * estate in the packet for options, so it's likely this option would
510 * get dropped.
511 */
512
513 if (pkt->opts[CD_VENDOR_SPEC] != NULL)
514 points += 80;
515
516 if (pkt->opts[CD_SUBNETMASK] != NULL)
517 points++;
518
519 if (pkt->opts[CD_ROUTER] != NULL)
520 points++;
521
522 if (pkt->opts[CD_HOSTNAME] != NULL)
523 points += 5;
524
525 return (points);
526 }
527
528 /*
529 * select_best(): selects the best offer from a list of IPv4 OFFER packets or
530 * DHCPv6 Advertise packets.
531 *
532 * input: dhcp_smach_t *: state machine with enqueued offers
533 * output: PKT_LIST *: the best packet, or NULL if none are acceptable
534 */
535
536 static PKT_LIST *
select_best(dhcp_smach_t * dsmp)537 select_best(dhcp_smach_t *dsmp)
538 {
539 PKT_LIST *current = dsmp->dsm_recv_pkt_list;
540 PKT_LIST *next, *best = NULL;
541 int points, best_points = -1;
542
543 /*
544 * pick out the best offer. point system.
545 * what's important for IPv4?
546 *
547 * 0) DHCP (30 points)
548 * 1) no option overload
549 * 2) encapsulated vendor option (80 points)
550 * 3) non-null sname and siaddr fields
551 * 4) non-null file field
552 * 5) hostname (5 points)
553 * 6) subnetmask (1 point)
554 * 7) router (1 point)
555 */
556
557 for (; current != NULL; current = next) {
558 next = current->next;
559
560 points = current->isv6 ?
561 compute_points_v6(current, dsmp) :
562 compute_points_v4(current);
563
564 /*
565 * Just discard any unacceptable entries we encounter.
566 */
567 if (points == -1) {
568 remque(current);
569 free_pkt_entry(current);
570 continue;
571 }
572
573 dhcpmsg(MSG_DEBUG, "select_best: OFFER had %d points", points);
574
575 /* Special case: stop now and select */
576 if (points == -2) {
577 best = current;
578 break;
579 }
580
581 if (points >= best_points) {
582 best_points = points;
583 best = current;
584 }
585 }
586
587 if (best != NULL) {
588 dhcpmsg(MSG_DEBUG, "select_best: most points: %d", best_points);
589 remque(best);
590 } else {
591 dhcpmsg(MSG_DEBUG, "select_best: no valid OFFER/BOOTP reply");
592 }
593
594 return (best);
595 }
596
597 /*
598 * accept_v4_acknak(): determine what to do with a DHCPv4 ACK/NAK based on the
599 * current state. If we're renewing or rebinding, the ACK
600 * must be for the same address and must have a new lease
601 * time. If it's a NAK, then our cache is garbage, and we
602 * must restart. Finally, call dhcp_bound on accepted
603 * ACKs.
604 *
605 * input: dhcp_smach_t *: the state machine to handle the ACK/NAK
606 * PKT_LIST *: the ACK/NAK message
607 * output: void
608 */
609
610 static void
accept_v4_acknak(dhcp_smach_t * dsmp,PKT_LIST * plp)611 accept_v4_acknak(dhcp_smach_t *dsmp, PKT_LIST *plp)
612 {
613 /* Account for received and processed messages */
614 dsmp->dsm_received++;
615
616 if (*plp->opts[CD_DHCP_TYPE]->value == ACK) {
617 if (dsmp->dsm_state != INFORM_SENT &&
618 dsmp->dsm_state != INFORMATION &&
619 (plp->opts[CD_LEASE_TIME] == NULL ||
620 plp->opts[CD_LEASE_TIME]->len != sizeof (lease_t))) {
621 dhcpmsg(MSG_WARNING, "accept_v4_acknak: ACK packet on "
622 "%s missing mandatory lease option, ignored",
623 dsmp->dsm_name);
624 dsmp->dsm_bad_offers++;
625 free_pkt_entry(plp);
626 return;
627 }
628 if ((dsmp->dsm_state == RENEWING ||
629 dsmp->dsm_state == REBINDING) &&
630 dsmp->dsm_leases->dl_lifs->lif_addr !=
631 plp->pkt->yiaddr.s_addr) {
632 dhcpmsg(MSG_WARNING, "accept_v4_acknak: renewal ACK "
633 "packet has a different IP address (%s), ignored",
634 inet_ntoa(plp->pkt->yiaddr));
635 dsmp->dsm_bad_offers++;
636 free_pkt_entry(plp);
637 return;
638 }
639 }
640
641 /*
642 * looks good; cancel the retransmission timer and unregister
643 * the acknak handler. ACK to BOUND, NAK back to SELECTING.
644 */
645
646 stop_pkt_retransmission(dsmp);
647
648 if (*plp->opts[CD_DHCP_TYPE]->value == NAK) {
649 dhcpmsg(MSG_WARNING, "accept_v4_acknak: NAK on interface %s",
650 dsmp->dsm_name);
651 dsmp->dsm_bad_offers++;
652 free_pkt_entry(plp);
653 dhcp_restart(dsmp);
654
655 /*
656 * remove any bogus cached configuration we might have
657 * around (right now would only happen if we got here
658 * from INIT_REBOOT).
659 */
660
661 (void) remove_hostconf(dsmp->dsm_name, dsmp->dsm_isv6);
662 return;
663 }
664
665 if (plp->opts[CD_SERVER_ID] == NULL ||
666 plp->opts[CD_SERVER_ID]->len != sizeof (ipaddr_t)) {
667 dhcpmsg(MSG_ERROR, "accept_v4_acknak: ACK with no valid "
668 "server id on %s", dsmp->dsm_name);
669 dsmp->dsm_bad_offers++;
670 free_pkt_entry(plp);
671 dhcp_restart(dsmp);
672 return;
673 }
674
675 if (plp->opts[CD_MESSAGE] != NULL) {
676 print_server_msg(dsmp, (char *)plp->opts[CD_MESSAGE]->value,
677 plp->opts[CD_MESSAGE]->len);
678 }
679
680 dhcpmsg(MSG_VERBOSE, "accept_v4_acknak: ACK on %s", dsmp->dsm_name);
681 if (!dhcp_bound(dsmp, plp)) {
682 dhcpmsg(MSG_WARNING, "accept_v4_acknak: dhcp_bound failed "
683 "for %s", dsmp->dsm_name);
684 dhcp_restart(dsmp);
685 }
686 }
687
688 /*
689 * accept_v6_message(): determine what to do with a DHCPv6 message based on the
690 * current state.
691 *
692 * input: dhcp_smach_t *: the state machine to handle the message
693 * PKT_LIST *: the DHCPv6 message
694 * const char *: type of message (for logging)
695 * uchar_t: type of message (extracted from packet)
696 * output: void
697 */
698
699 static void
accept_v6_message(dhcp_smach_t * dsmp,PKT_LIST * plp,const char * pname,uchar_t recv_type)700 accept_v6_message(dhcp_smach_t *dsmp, PKT_LIST *plp, const char *pname,
701 uchar_t recv_type)
702 {
703 const dhcpv6_option_t *d6o;
704 uint_t olen;
705 const char *estr, *msg;
706 uint_t msglen;
707 int status;
708
709 /* Account for received and processed messages */
710 dsmp->dsm_received++;
711
712 /* We don't yet support Reconfigure at all. */
713 if (recv_type == DHCPV6_MSG_RECONFIGURE) {
714 dhcpmsg(MSG_VERBOSE, "accept_v6_message: ignored Reconfigure "
715 "on %s", dsmp->dsm_name);
716 free_pkt_entry(plp);
717 return;
718 }
719
720 /*
721 * All valid DHCPv6 messages must have our Client ID specified.
722 */
723 d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_CLIENTID, &olen);
724 olen -= sizeof (*d6o);
725 if (d6o == NULL || olen != dsmp->dsm_cidlen ||
726 memcmp(d6o + 1, dsmp->dsm_cid, olen) != 0) {
727 dhcpmsg(MSG_VERBOSE,
728 "accept_v6_message: discarded %s on %s: %s Client ID",
729 pname, dsmp->dsm_name, d6o == NULL ? "no" : "wrong");
730 free_pkt_entry(plp);
731 return;
732 }
733
734 /*
735 * All valid DHCPv6 messages must have a Server ID specified.
736 *
737 * If this is a Reply and it's not in response to Solicit, Confirm,
738 * Rebind, or Information-Request, then it must also match the Server
739 * ID we're expecting.
740 *
741 * For Reply in the Solicit, Confirm, Rebind, and Information-Request
742 * cases, the Server ID needs to be saved. This is done inside of
743 * dhcp_bound().
744 */
745 d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_SERVERID, &olen);
746 if (d6o == NULL) {
747 dhcpmsg(MSG_DEBUG,
748 "accept_v6_message: discarded %s on %s: no Server ID",
749 pname, dsmp->dsm_name);
750 free_pkt_entry(plp);
751 return;
752 }
753 if (recv_type == DHCPV6_MSG_REPLY && dsmp->dsm_state != SELECTING &&
754 dsmp->dsm_state != INIT_REBOOT && dsmp->dsm_state != REBINDING &&
755 dsmp->dsm_state != INFORM_SENT) {
756 olen -= sizeof (*d6o);
757 if (olen != dsmp->dsm_serveridlen ||
758 memcmp(d6o + 1, dsmp->dsm_serverid, olen) != 0) {
759 dhcpmsg(MSG_DEBUG, "accept_v6_message: discarded %s on "
760 "%s: wrong Server ID", pname, dsmp->dsm_name);
761 free_pkt_entry(plp);
762 return;
763 }
764 }
765
766 /*
767 * Break out of the switch if the input message needs to be discarded.
768 * Return from the function if the message has been enqueued or
769 * consumed.
770 */
771 switch (dsmp->dsm_state) {
772 case SELECTING:
773 /* A Reply message signifies a Rapid-Commit. */
774 if (recv_type == DHCPV6_MSG_REPLY) {
775 if (dhcpv6_pkt_option(plp, NULL,
776 DHCPV6_OPT_RAPID_COMMIT, &olen) == NULL) {
777 dhcpmsg(MSG_DEBUG, "accept_v6_message: Reply "
778 "on %s lacks Rapid-Commit; ignoring",
779 dsmp->dsm_name);
780 break;
781 }
782 dhcpmsg(MSG_VERBOSE,
783 "accept_v6_message: rapid-commit Reply on %s",
784 dsmp->dsm_name);
785 cancel_offer_timer(dsmp);
786 goto rapid_commit;
787 }
788
789 /* Otherwise, we're looking for Advertisements. */
790 if (recv_type != DHCPV6_MSG_ADVERTISE)
791 break;
792
793 /*
794 * Special case: if this advertisement has preference 255, then
795 * we must stop right now and select this server.
796 */
797 d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_PREFERENCE,
798 &olen);
799 if (d6o != NULL && olen == sizeof (*d6o) + 1 &&
800 *(const uchar_t *)(d6o + 1) == 255) {
801 pkt_smach_enqueue(dsmp, plp);
802 dhcpmsg(MSG_DEBUG, "accept_v6_message: preference 255;"
803 " immediate Request on %s", dsmp->dsm_name);
804 dhcp_requesting(NULL, dsmp);
805 } else {
806 pkt_smach_enqueue(dsmp, plp);
807 }
808 return;
809
810 case PRE_BOUND:
811 case BOUND:
812 /*
813 * Not looking for anything in these states. (If we
814 * implemented reconfigure, that might go here.)
815 */
816 break;
817
818 case REQUESTING:
819 case INIT_REBOOT:
820 case RENEWING:
821 case REBINDING:
822 case INFORM_SENT:
823 /*
824 * We're looking for Reply messages.
825 */
826 if (recv_type != DHCPV6_MSG_REPLY)
827 break;
828 dhcpmsg(MSG_VERBOSE,
829 "accept_v6_message: received Reply message on %s",
830 dsmp->dsm_name);
831 rapid_commit:
832 /*
833 * Extract the status code option. If one is present and the
834 * request failed, then try to go to another advertisement in
835 * the list or restart the selection machinery.
836 */
837 d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_STATUS_CODE,
838 &olen);
839 status = dhcpv6_status_code(d6o, olen, &estr, &msg, &msglen);
840 /*
841 * Check for the UseMulticast status code. If this is present,
842 * and if we were actually using unicast, then drop back and
843 * try again. If we weren't using unicast, then just pretend
844 * we never saw this message -- the peer is confused. (TAHI
845 * does this.)
846 */
847 if (status == DHCPV6_STAT_USEMCAST) {
848 if (IN6_IS_ADDR_MULTICAST(
849 &dsmp->dsm_send_dest.v6.sin6_addr)) {
850 break;
851 } else {
852 free_pkt_entry(plp);
853 dsmp->dsm_send_dest.v6.sin6_addr =
854 ipv6_all_dhcp_relay_and_servers;
855 retransmit_now(dsmp);
856 return;
857 }
858 }
859 print_server_msg(dsmp, msg, msglen);
860 /*
861 * We treat NoBinding at the top level as "success." Granted,
862 * this doesn't make much sense, but the TAHI test suite does
863 * this. NoBinding really only makes sense in the context of a
864 * specific IA, as it refers to the GUID:IAID binding, so
865 * ignoring it at the top level is safe.
866 */
867 if (status == DHCPV6_STAT_SUCCESS ||
868 status == DHCPV6_STAT_NOBINDING) {
869 if (dhcp_bound(dsmp, plp)) {
870 /*
871 * dhcp_bound will stop retransmission on
872 * success, if that's called for.
873 */
874 server_unicast_option(dsmp, plp);
875 } else {
876 stop_pkt_retransmission(dsmp);
877 dhcpmsg(MSG_WARNING, "accept_v6_message: "
878 "dhcp_bound failed for %s", dsmp->dsm_name);
879 (void) remove_hostconf(dsmp->dsm_name,
880 dsmp->dsm_isv6);
881 dhcp_restart(dsmp);
882 }
883 } else {
884 dhcpmsg(MSG_WARNING, "accept_v6_message: Reply: %s",
885 estr);
886 stop_pkt_retransmission(dsmp);
887 free_pkt_entry(plp);
888 if (dsmp->dsm_state == INFORM_SENT) {
889 (void) set_smach_state(dsmp, INIT);
890 ipc_action_finish(dsmp, DHCP_IPC_E_SRVFAILED);
891 } else {
892 (void) remove_hostconf(dsmp->dsm_name,
893 dsmp->dsm_isv6);
894 request_failed(dsmp);
895 }
896 }
897 return;
898
899 case DECLINING:
900 /*
901 * We're looking for Reply messages.
902 */
903 if (recv_type != DHCPV6_MSG_REPLY)
904 break;
905 stop_pkt_retransmission(dsmp);
906 /*
907 * Extract the status code option. Note that it's not a
908 * failure if the server reports an error.
909 */
910 d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_STATUS_CODE,
911 &olen);
912 if (dhcpv6_status_code(d6o, olen, &estr, &msg,
913 &msglen) == DHCPV6_STAT_SUCCESS) {
914 print_server_msg(dsmp, msg, msglen);
915 } else {
916 dhcpmsg(MSG_WARNING, "accept_v6_message: Reply: %s",
917 estr);
918 }
919 free_pkt_entry(plp);
920 if (dsmp->dsm_leases == NULL) {
921 dhcpmsg(MSG_VERBOSE, "accept_v6_message: %s has no "
922 "leases left", dsmp->dsm_name);
923 dhcp_restart(dsmp);
924 } else if (dsmp->dsm_lif_wait == 0) {
925 (void) set_smach_state(dsmp, BOUND);
926 } else {
927 (void) set_smach_state(dsmp, PRE_BOUND);
928 }
929 return;
930
931 case RELEASING:
932 /*
933 * We're looking for Reply messages.
934 */
935 if (recv_type != DHCPV6_MSG_REPLY)
936 break;
937 stop_pkt_retransmission(dsmp);
938 /*
939 * Extract the status code option.
940 */
941 d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_STATUS_CODE,
942 &olen);
943 if (dhcpv6_status_code(d6o, olen, &estr, &msg,
944 &msglen) == DHCPV6_STAT_SUCCESS) {
945 print_server_msg(dsmp, msg, msglen);
946 } else {
947 dhcpmsg(MSG_WARNING, "accept_v6_message: Reply: %s",
948 estr);
949 }
950 free_pkt_entry(plp);
951 finished_smach(dsmp, DHCP_IPC_SUCCESS);
952 return;
953 }
954
955 /*
956 * Break from above switch means that the message must be discarded.
957 */
958 dhcpmsg(MSG_VERBOSE,
959 "accept_v6_message: discarded v6 %s on %s; state %s",
960 pname, dsmp->dsm_name, dhcp_state_to_string(dsmp->dsm_state));
961 free_pkt_entry(plp);
962 }
963
964 /*
965 * dhcp_acknak_global(): Processes reception of an ACK or NAK packet on the
966 * global socket -- broadcast packets for IPv4, all
967 * packets for DHCPv6.
968 *
969 * input: iu_eh_t *: unused
970 * int: the global file descriptor the ACK/NAK arrived on
971 * short: unused
972 * iu_event_id_t: unused
973 * void *: unused
974 * output: void
975 */
976
977 /* ARGSUSED */
978 void
dhcp_acknak_global(iu_eh_t * ehp,int fd,short events,iu_event_id_t id,void * arg)979 dhcp_acknak_global(iu_eh_t *ehp, int fd, short events, iu_event_id_t id,
980 void *arg)
981 {
982 PKT_LIST *plp;
983 dhcp_pif_t *pif;
984 uchar_t recv_type;
985 const char *pname;
986 uint_t xid;
987 dhcp_smach_t *dsmp;
988 boolean_t isv6 = (fd == v6_sock_fd);
989 struct sockaddr_in sin;
990 const char *reason;
991 size_t sinlen = sizeof (sin);
992 int sock;
993
994 plp = recv_pkt(fd, get_max_mtu(isv6), isv6);
995 if (plp == NULL)
996 return;
997
998 recv_type = pkt_recv_type(plp);
999 pname = pkt_type_to_string(recv_type, isv6);
1000
1001 /*
1002 * Find the corresponding state machine and pif.
1003 *
1004 * Note that DHCPv6 Reconfigure would be special: it's not the reply to
1005 * any transaction, and thus we would need to search on transaction ID
1006 * zero (all state machines) to find the match. However, Reconfigure
1007 * is not yet supported.
1008 */
1009 xid = pkt_get_xid(plp->pkt, isv6);
1010
1011 for (dsmp = lookup_smach_by_xid(xid, NULL, isv6); dsmp != NULL;
1012 dsmp = lookup_smach_by_xid(xid, dsmp, isv6)) {
1013 pif = dsmp->dsm_lif->lif_pif;
1014 if (pif->pif_index == plp->ifindex ||
1015 pif->pif_under_ipmp && pif->pif_grindex == plp->ifindex)
1016 break;
1017 }
1018
1019 if (dsmp == NULL) {
1020 dhcpmsg(MSG_VERBOSE, "dhcp_acknak_global: ignored v%d %s packet"
1021 " on ifindex %d: unknown state machine", isv6 ? 6 : 4,
1022 pname, plp->ifindex);
1023 free_pkt_entry(plp);
1024 return;
1025 }
1026
1027 if (!isv6 && !pkt_v4_match(recv_type, DHCP_PACK|DHCP_PNAK)) {
1028 reason = "not ACK or NAK";
1029 goto drop;
1030 }
1031
1032 /*
1033 * For IPv4, most packets will be handled by dhcp_packet_lif(). The
1034 * only exceptions are broadcast packets sent when lif_sock_ip_fd has
1035 * bound to something other than INADDR_ANY.
1036 */
1037 if (!isv6) {
1038 sock = dsmp->dsm_lif->lif_sock_ip_fd;
1039
1040 if (getsockname(sock, (struct sockaddr *)&sin, &sinlen) != -1 &&
1041 sin.sin_addr.s_addr == INADDR_ANY) {
1042 reason = "handled by lif_sock_ip_fd";
1043 goto drop;
1044 }
1045 }
1046
1047 /*
1048 * We've got a packet; make sure it's acceptable and cancel the REQUEST
1049 * retransmissions.
1050 */
1051 if (isv6)
1052 accept_v6_message(dsmp, plp, pname, recv_type);
1053 else
1054 accept_v4_acknak(dsmp, plp);
1055 return;
1056 drop:
1057 dhcpmsg(MSG_VERBOSE, "dhcp_acknak_global: ignored v%d %s packet for %s "
1058 "received on global socket: %s", isv6 ? 6 : 4, pname, pif->pif_name,
1059 reason);
1060 free_pkt_entry(plp);
1061 }
1062
1063 /*
1064 * request_failed(): Attempt to request an address has failed. Take an
1065 * appropriate action.
1066 *
1067 * input: dhcp_smach_t *: state machine that has failed
1068 * output: void
1069 */
1070
1071 static void
request_failed(dhcp_smach_t * dsmp)1072 request_failed(dhcp_smach_t *dsmp)
1073 {
1074 PKT_LIST *offer;
1075
1076 dsmp->dsm_server = ipv6_all_dhcp_relay_and_servers;
1077 if ((offer = select_best(dsmp)) != NULL) {
1078 insque(offer, &dsmp->dsm_recv_pkt_list);
1079 dhcp_requesting(NULL, dsmp);
1080 } else {
1081 dhcpmsg(MSG_INFO, "no offers left on %s; restarting",
1082 dsmp->dsm_name);
1083 dhcp_selecting(dsmp);
1084 }
1085 }
1086
1087 /*
1088 * dhcp_packet_lif(): Processes reception of an ACK, NAK, or OFFER packet on
1089 * a given logical interface for IPv4 (only).
1090 *
1091 * input: iu_eh_t *: unused
1092 * int: the file descriptor the packet arrived on
1093 * short: unused
1094 * iu_event_id_t: the id of this event callback with the handler
1095 * void *: pointer to logical interface receiving message
1096 * output: void
1097 */
1098
1099 /* ARGSUSED */
1100 void
dhcp_packet_lif(iu_eh_t * ehp,int fd,short events,iu_event_id_t id,void * arg)1101 dhcp_packet_lif(iu_eh_t *ehp, int fd, short events, iu_event_id_t id,
1102 void *arg)
1103 {
1104 dhcp_lif_t *lif = arg;
1105 PKT_LIST *plp;
1106 uchar_t recv_type;
1107 const char *pname;
1108 uint_t xid;
1109 dhcp_smach_t *dsmp;
1110
1111 if ((plp = recv_pkt(fd, lif->lif_pif->pif_mtu, B_FALSE)) == NULL)
1112 return;
1113
1114 recv_type = pkt_recv_type(plp);
1115 pname = pkt_type_to_string(recv_type, B_FALSE);
1116
1117 if (!pkt_v4_match(recv_type,
1118 DHCP_PACK | DHCP_PNAK | DHCP_PUNTYPED | DHCP_POFFER)) {
1119 dhcpmsg(MSG_VERBOSE, "dhcp_packet_lif: ignored v4 %s packet "
1120 "received via LIF %s", pname, lif->lif_name);
1121 free_pkt_entry(plp);
1122 return;
1123 }
1124
1125 /*
1126 * Find the corresponding state machine.
1127 */
1128 xid = pkt_get_xid(plp->pkt, B_FALSE);
1129 for (dsmp = lookup_smach_by_xid(xid, NULL, B_FALSE); dsmp != NULL;
1130 dsmp = lookup_smach_by_xid(xid, dsmp, B_FALSE)) {
1131 if (dsmp->dsm_lif == lif)
1132 break;
1133 }
1134
1135 if (dsmp == NULL)
1136 goto drop;
1137
1138 if (pkt_v4_match(recv_type, DHCP_PACK|DHCP_PNAK)) {
1139 /*
1140 * We've got an ACK/NAK; make sure it's acceptable and cancel
1141 * the REQUEST retransmissions.
1142 */
1143 accept_v4_acknak(dsmp, plp);
1144 } else {
1145 if (is_bound_state(dsmp->dsm_state))
1146 goto drop;
1147 /*
1148 * Must be an OFFER or a BOOTP message: enqueue it for later
1149 * processing by select_best().
1150 */
1151 pkt_smach_enqueue(dsmp, plp);
1152 }
1153 return;
1154 drop:
1155 dhcpmsg(MSG_VERBOSE, "dhcp_packet_lif: ignored %s packet xid "
1156 "%x received via LIF %s; %s", pname, xid, lif->lif_name,
1157 dsmp == NULL ? "unknown state machine" : "bound");
1158 free_pkt_entry(plp);
1159 }
1160
1161 /*
1162 * dhcp_restart(): restarts DHCP (from INIT) on a given state machine, but only
1163 * if we're leasing addresses. Doesn't restart for information-
1164 * only interfaces.
1165 *
1166 * input: dhcp_smach_t *: the state machine to restart DHCP on
1167 * output: void
1168 */
1169
1170 void
dhcp_restart(dhcp_smach_t * dsmp)1171 dhcp_restart(dhcp_smach_t *dsmp)
1172 {
1173 if (dsmp->dsm_state == INFORM_SENT || dsmp->dsm_state == INFORMATION)
1174 return;
1175
1176 /*
1177 * As we're returning to INIT state, we need to discard any leases we
1178 * may have, and (for v4) canonize the LIF. There's a bit of tension
1179 * between keeping around a possibly still working address, and obeying
1180 * the RFCs. A more elaborate design would be to mark the addresses as
1181 * DEPRECATED, and then start a removal timer. Such a design would
1182 * probably compromise testing.
1183 */
1184 deprecate_leases(dsmp);
1185
1186 if (!set_start_timer(dsmp)) {
1187 dhcpmsg(MSG_ERROR, "dhcp_restart: cannot schedule dhcp_start, "
1188 "reverting to INIT state on %s", dsmp->dsm_name);
1189
1190 (void) set_smach_state(dsmp, INIT);
1191 dsmp->dsm_dflags |= DHCP_IF_FAILED;
1192 ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY);
1193 } else {
1194 dhcpmsg(MSG_DEBUG, "dhcp_restart: restarting DHCP on %s",
1195 dsmp->dsm_name);
1196 }
1197 }
1198
1199 /*
1200 * stop_requesting(): decides when to stop retransmitting REQUESTs
1201 *
1202 * input: dhcp_smach_t *: the state machine REQUESTs are being sent from
1203 * unsigned int: the number of REQUESTs sent so far
1204 * output: boolean_t: B_TRUE if retransmissions should stop
1205 */
1206
1207 static boolean_t
stop_requesting(dhcp_smach_t * dsmp,unsigned int n_requests)1208 stop_requesting(dhcp_smach_t *dsmp, unsigned int n_requests)
1209 {
1210 uint_t maxreq;
1211
1212 maxreq = dsmp->dsm_isv6 ? DHCPV6_REQ_MAX_RC : DHCP_MAX_REQUESTS;
1213 if (n_requests >= maxreq) {
1214
1215 dhcpmsg(MSG_INFO, "no ACK/NAK/Reply to REQUEST on %s",
1216 dsmp->dsm_name);
1217
1218 request_failed(dsmp);
1219 return (B_TRUE);
1220 } else {
1221 return (B_FALSE);
1222 }
1223 }
1224