xref: /titanic_44/usr/src/cmd/cmd-inet/sbin/dhcpagent/request.c (revision 49b225e1cfa7bbf7738d4df0a03f18e3283426eb)
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
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
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
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
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
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 *
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
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
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
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
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
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
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
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