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