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