xref: /illumos-gate/usr/src/cmd/cmd-inet/sbin/dhcpagent/agent.c (revision 7ae111d47a973fff4c6e231cc31f271dd9cef473)
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 2006 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 #include <sys/types.h>
29 #include <stdlib.h>
30 #include <assert.h>
31 #include <errno.h>
32 #include <locale.h>
33 #include <string.h>
34 #include <unistd.h>
35 #include <signal.h>
36 #include <stdio.h>
37 #include <stdio_ext.h>
38 #include <dhcp_hostconf.h>
39 #include <dhcp_symbol.h>
40 #include <dhcpagent_ipc.h>
41 #include <dhcpmsg.h>
42 #include <netinet/dhcp.h>
43 
44 #include "async.h"
45 #include "agent.h"
46 #include "script_handler.h"
47 #include "util.h"
48 #include "class_id.h"
49 #include "states.h"
50 #include "packet.h"
51 
52 #ifndef	TEXT_DOMAIN
53 #define	TEXT_DOMAIN	"SYS_TEST"
54 #endif
55 
56 iu_timer_id_t		inactivity_id;
57 int			class_id_len = 0;
58 char			*class_id;
59 iu_eh_t			*eh;
60 iu_tq_t			*tq;
61 pid_t			grandparent;
62 
63 static boolean_t	shutdown_started = B_FALSE;
64 static boolean_t	do_adopt = B_FALSE;
65 static unsigned int	debug_level = 0;
66 static iu_eh_callback_t	accept_event, ipc_event;
67 
68 /*
69  * The ipc_cmd_allowed[] table indicates which IPC commands are allowed in
70  * which states; a non-zero value indicates the command is permitted.
71  *
72  * START is permitted if the interface is fresh, or if we are in the process
73  * of trying to obtain a lease (as a convenience to save the administrator
74  * from having to do an explicit DROP).  EXTEND, RELEASE, and GET_TAG require
75  * a lease to be obtained in order to make sense.  INFORM is permitted if the
76  * interface is fresh or has an INFORM in progress or previously done on it --
77  * otherwise a DROP or RELEASE is first required.  PING and STATUS always make
78  * sense and thus are always permitted, as is DROP in order to permit the
79  * administrator to always bail out.
80  */
81 static int ipc_cmd_allowed[DHCP_NSTATES][DHCP_NIPC] = {
82 	/*			  D  E	P  R  S	 S  I  G */
83 	/*			  R  X	I  E  T	 T  N  E */
84 	/*			  O  T	N  L  A	 A  F  T */
85 	/*			  P  E	G  E  R	 T  O  _ */
86 	/*			  .  N  .  A  T  U  R  T */
87 	/*			  .  D	.  S  .  S  M  A */
88 	/*			  .  .  .  E  .  .  .  G */
89 	/* INIT		*/	{ 1, 0, 1, 0, 1, 1, 1, 0 },
90 	/* SELECTING	*/	{ 1, 0, 1, 0, 1, 1, 0, 0 },
91 	/* REQUESTING	*/	{ 1, 0, 1, 0, 1, 1, 0, 0 },
92 	/* BOUND	*/	{ 1, 1, 1, 1, 0, 1, 0, 1 },
93 	/* RENEWING	*/	{ 1, 1, 1, 1, 0, 1, 0, 1 },
94 	/* REBINDING	*/	{ 1, 1, 1, 1, 0, 1, 0, 1 },
95 	/* INFORMATION  */	{ 1, 0, 1, 0, 0, 1, 1, 1 },
96 	/* INIT_REBOOT  */	{ 1, 0, 1, 0, 1, 1, 0, 0 },
97 	/* ADOPTING	*/	{ 1, 0, 1, 0, 0, 1, 0, 0 },
98 	/* INFORM_SENT  */	{ 1, 0, 1, 0, 0, 1, 1, 0 }
99 };
100 
101 int
102 main(int argc, char **argv)
103 {
104 	boolean_t	is_daemon  = B_TRUE;
105 	boolean_t	is_verbose = B_FALSE;
106 	int		ipc_fd;
107 	int		c;
108 	struct rlimit	rl;
109 
110 	/*
111 	 * -l is ignored for compatibility with old agent.
112 	 */
113 
114 	while ((c = getopt(argc, argv, "vd:l:fa")) != EOF) {
115 
116 		switch (c) {
117 
118 		case 'a':
119 			do_adopt = B_TRUE;
120 			grandparent = getpid();
121 			break;
122 
123 		case 'd':
124 			debug_level = strtoul(optarg, NULL, 0);
125 			break;
126 
127 		case 'f':
128 			is_daemon = B_FALSE;
129 			break;
130 
131 		case 'v':
132 			is_verbose = B_TRUE;
133 			break;
134 
135 		case '?':
136 			(void) fprintf(stderr, "usage: %s [-a] [-d n] [-f] [-v]"
137 			    "\n", argv[0]);
138 			return (EXIT_FAILURE);
139 
140 		default:
141 			break;
142 		}
143 	}
144 
145 	(void) setlocale(LC_ALL, "");
146 	(void) textdomain(TEXT_DOMAIN);
147 
148 	if (geteuid() != 0) {
149 		dhcpmsg_init(argv[0], B_FALSE, is_verbose, debug_level);
150 		dhcpmsg(MSG_ERROR, "must be super-user");
151 		dhcpmsg_fini();
152 		return (EXIT_FAILURE);
153 	}
154 
155 	if (is_daemon && daemonize() == 0) {
156 		dhcpmsg_init(argv[0], B_FALSE, is_verbose, debug_level);
157 		dhcpmsg(MSG_ERR, "cannot become daemon, exiting");
158 		dhcpmsg_fini();
159 		return (EXIT_FAILURE);
160 	}
161 
162 	dhcpmsg_init(argv[0], is_daemon, is_verbose, debug_level);
163 	(void) atexit(dhcpmsg_fini);
164 
165 	tq = iu_tq_create();
166 	eh = iu_eh_create();
167 
168 	if (eh == NULL || tq == NULL) {
169 		errno = ENOMEM;
170 		dhcpmsg(MSG_ERR, "cannot create timer queue or event handler");
171 		return (EXIT_FAILURE);
172 	}
173 
174 	/*
175 	 * ignore most signals that could be reasonably generated.
176 	 */
177 
178 	(void) signal(SIGTERM, graceful_shutdown);
179 	(void) signal(SIGQUIT, graceful_shutdown);
180 	(void) signal(SIGPIPE, SIG_IGN);
181 	(void) signal(SIGUSR1, SIG_IGN);
182 	(void) signal(SIGUSR2, SIG_IGN);
183 	(void) signal(SIGINT,  SIG_IGN);
184 	(void) signal(SIGHUP,  SIG_IGN);
185 	(void) signal(SIGCHLD, SIG_IGN);
186 
187 	/*
188 	 * upon SIGTHAW we need to refresh any non-infinite leases.
189 	 */
190 
191 	(void) iu_eh_register_signal(eh, SIGTHAW, refresh_ifslist, NULL);
192 
193 	class_id = get_class_id();
194 	if (class_id != NULL)
195 		class_id_len = strlen(class_id);
196 	else
197 		dhcpmsg(MSG_WARNING, "get_class_id failed, continuing "
198 		    "with no vendor class id");
199 
200 	/*
201 	 * the inactivity timer is enabled any time there are no
202 	 * interfaces under DHCP control.  if DHCP_INACTIVITY_WAIT
203 	 * seconds transpire without an interface under DHCP control,
204 	 * the agent shuts down.
205 	 */
206 
207 	inactivity_id = iu_schedule_timer(tq, DHCP_INACTIVITY_WAIT,
208 	    inactivity_shutdown, NULL);
209 
210 	/*
211 	 * max out the number available descriptors, just in case..
212 	 */
213 
214 	rl.rlim_cur = RLIM_INFINITY;
215 	rl.rlim_max = RLIM_INFINITY;
216 	if (setrlimit(RLIMIT_NOFILE, &rl) == -1)
217 		dhcpmsg(MSG_ERR, "setrlimit failed");
218 
219 	(void) enable_extended_FILE_stdio(-1, -1);
220 
221 	/*
222 	 * create the ipc channel that the agent will listen for
223 	 * requests on, and register it with the event handler so that
224 	 * `accept_event' will be called back.
225 	 */
226 
227 	switch (dhcp_ipc_init(&ipc_fd)) {
228 
229 	case 0:
230 		break;
231 
232 	case DHCP_IPC_E_BIND:
233 		dhcpmsg(MSG_ERROR, "dhcp_ipc_init: cannot bind to port "
234 		    "%i (agent already running?)", IPPORT_DHCPAGENT);
235 		return (EXIT_FAILURE);
236 
237 	default:
238 		dhcpmsg(MSG_ERROR, "dhcp_ipc_init failed");
239 		return (EXIT_FAILURE);
240 	}
241 
242 	if (iu_register_event(eh, ipc_fd, POLLIN, accept_event, 0) == -1) {
243 		dhcpmsg(MSG_ERR, "cannot register ipc fd for messages");
244 		return (EXIT_FAILURE);
245 	}
246 
247 	/*
248 	 * if the -a (adopt) option was specified, try to adopt the
249 	 * kernel-managed interface before we start. Our grandparent
250 	 * will be waiting for us to finish this, so signal him when
251 	 * we're done.
252 	 */
253 
254 	if (do_adopt) {
255 		int result;
256 
257 		result = dhcp_adopt();
258 
259 		if (grandparent != (pid_t)0) {
260 			dhcpmsg(MSG_DEBUG, "adoption complete, signalling "
261 			    "parent (%i) to exit.", grandparent);
262 			(void) kill(grandparent, SIGALRM);
263 		}
264 
265 		if (result == 0)
266 			return (EXIT_FAILURE);
267 	}
268 
269 	/*
270 	 * enter the main event loop; this is where all the real work
271 	 * takes place (through registering events and scheduling timers).
272 	 * this function only returns when the agent is shutting down.
273 	 */
274 
275 	switch (iu_handle_events(eh, tq)) {
276 
277 	case -1:
278 		dhcpmsg(MSG_WARNING, "iu_handle_events exited abnormally");
279 		break;
280 
281 	case DHCP_REASON_INACTIVITY:
282 		dhcpmsg(MSG_INFO, "no interfaces to manage, shutting down...");
283 		break;
284 
285 	case DHCP_REASON_TERMINATE:
286 		dhcpmsg(MSG_INFO, "received SIGTERM, shutting down...");
287 		break;
288 
289 	case DHCP_REASON_SIGNAL:
290 		dhcpmsg(MSG_WARNING, "received unexpected signal, shutting "
291 		    "down...");
292 		break;
293 	}
294 
295 	(void) iu_eh_unregister_signal(eh, SIGTHAW, NULL);
296 
297 	iu_eh_destroy(eh);
298 	iu_tq_destroy(tq);
299 
300 	return (EXIT_SUCCESS);
301 }
302 
303 /*
304  * drain_script(): event loop callback during shutdown
305  *
306  *   input: eh_t *: unused
307  *	    void *: unused
308  *  output: boolean_t: B_TRUE if event loop should exit; B_FALSE otherwise
309  */
310 
311 /* ARGSUSED */
312 boolean_t
313 drain_script(iu_eh_t *ehp, void *arg)
314 {
315 	if (shutdown_started == B_FALSE) {
316 		shutdown_started = B_TRUE;
317 		if (do_adopt == B_FALSE)	/* see 4291141 */
318 			nuke_ifslist(B_TRUE);
319 	}
320 	return (script_count == 0);
321 }
322 
323 /*
324  * accept_event(): accepts a new connection on the ipc socket and registers
325  *		   to receive its messages with the event handler
326  *
327  *   input: iu_eh_t *: unused
328  *	    int: the file descriptor in the iu_eh_t * the connection came in on
329  *	    (other arguments unused)
330  *  output: void
331  */
332 
333 /* ARGSUSED */
334 static void
335 accept_event(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg)
336 {
337 	int	client_fd;
338 	int	is_priv;
339 
340 	if (dhcp_ipc_accept(fd, &client_fd, &is_priv) != 0) {
341 		dhcpmsg(MSG_ERR, "accept_event: accept on ipc socket");
342 		return;
343 	}
344 
345 	if (iu_register_event(eh, client_fd, POLLIN, ipc_event,
346 	    (void *)is_priv) == -1) {
347 		dhcpmsg(MSG_ERROR, "accept_event: cannot register ipc socket "
348 		    "for callback");
349 	}
350 }
351 
352 /*
353  * ipc_event(): processes incoming ipc requests
354  *
355  *   input: iu_eh_t *: unused
356  *	    int: the file descriptor in the iu_eh_t * the request came in on
357  *	    short: unused
358  *	    iu_event_id_t: unused
359  *	    void *: indicates whether the request is from a privileged client
360  *  output: void
361  */
362 
363 /* ARGSUSED */
364 static void
365 ipc_event(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg)
366 {
367 	dhcp_ipc_request_t	*request;
368 	struct ifslist		*ifsp, *primary_ifsp;
369 	int			error, is_priv = (int)arg;
370 	PKT_LIST 		*plp[2];
371 	dhcp_ipc_type_t		cmd;
372 
373 	(void) iu_unregister_event(eh, id, NULL);
374 
375 	if (dhcp_ipc_recv_request(fd, &request, DHCP_IPC_REQUEST_WAIT) != 0) {
376 		dhcpmsg(MSG_ERROR, "ipc_event: dhcp_ipc_recv_request failed");
377 		(void) dhcp_ipc_close(fd);
378 		return;
379 	}
380 
381 	cmd = DHCP_IPC_CMD(request->message_type);
382 	if (cmd >= DHCP_NIPC) {
383 		send_error_reply(request, DHCP_IPC_E_CMD_UNKNOWN, &fd);
384 		return;
385 	}
386 
387 	/* return EPERM for any of the privileged actions */
388 
389 	if (!is_priv) {
390 		switch (cmd) {
391 
392 		case DHCP_STATUS:
393 		case DHCP_PING:
394 		case DHCP_GET_TAG:
395 			break;
396 
397 		default:
398 			dhcpmsg(MSG_WARNING, "ipc_event: privileged ipc "
399 			    "command (%i) attempted on %s", cmd,
400 			    request->ifname);
401 
402 			send_error_reply(request, DHCP_IPC_E_PERM, &fd);
403 			return;
404 		}
405 	}
406 
407 	/*
408 	 * try to locate the ifs associated with this command.  if the
409 	 * command is DHCP_START or DHCP_INFORM, then if there isn't
410 	 * an ifs already, make one (there may already be one from a
411 	 * previous failed attempt to START or INFORM).  otherwise,
412 	 * verify the interface is still valid.
413 	 */
414 
415 	ifsp = lookup_ifs(request->ifname);
416 
417 	switch (cmd) {
418 
419 	case DHCP_START:			/* FALLTHRU */
420 	case DHCP_INFORM:
421 		/*
422 		 * it's possible that the interface already exists, but
423 		 * has been abandoned.  usually in those cases we should
424 		 * return DHCP_IPC_E_UNKIF, but that makes little sense
425 		 * in the case of "start" or "inform", so just ignore
426 		 * the abandoned interface and start over anew.
427 		 */
428 
429 		if (ifsp != NULL && verify_ifs(ifsp) == 0)
430 			ifsp = NULL;
431 
432 		/*
433 		 * as part of initializing the ifs, insert_ifs()
434 		 * creates a DLPI stream at ifsp->if_dlpi_fd.
435 		 */
436 
437 		if (ifsp == NULL) {
438 			ifsp = insert_ifs(request->ifname, B_FALSE, &error);
439 			if (ifsp == NULL) {
440 				send_error_reply(request, error, &fd);
441 				return;
442 			}
443 		}
444 		break;
445 
446 	default:
447 		if (ifsp == NULL) {
448 			if (request->ifname[0] == '\0')
449 				error = DHCP_IPC_E_NOPRIMARY;
450 			else
451 				error = DHCP_IPC_E_UNKIF;
452 
453 			send_error_reply(request, error, &fd);
454 			return;
455 		}
456 		break;
457 	}
458 
459 	if (verify_ifs(ifsp) == 0) {
460 		send_error_reply(request, DHCP_IPC_E_UNKIF, &fd);
461 		return;
462 	}
463 
464 	if (ifsp->if_dflags & DHCP_IF_BOOTP) {
465 		switch (cmd) {
466 
467 		case DHCP_EXTEND:
468 		case DHCP_RELEASE:
469 		case DHCP_INFORM:
470 			send_error_reply(request, DHCP_IPC_E_BOOTP, &fd);
471 			return;
472 
473 		default:
474 			break;
475 		}
476 	}
477 
478 	/*
479 	 * verify that the interface is in a state which will allow the
480 	 * command.  we do this up front so that we can return an error
481 	 * *before* needlessly cancelling an in-progress transaction.
482 	 */
483 
484 	if (!ipc_cmd_allowed[ifsp->if_state][cmd]) {
485 		send_error_reply(request, DHCP_IPC_E_OUTSTATE, &fd);
486 		return;
487 	}
488 
489 	if ((request->message_type & DHCP_PRIMARY) && is_priv) {
490 		if ((primary_ifsp = lookup_ifs("")) != NULL)
491 			primary_ifsp->if_dflags &= ~DHCP_IF_PRIMARY;
492 		ifsp->if_dflags |= DHCP_IF_PRIMARY;
493 	}
494 
495 	/*
496 	 * current design dictates that there can be only one
497 	 * outstanding transaction per interface -- this simplifies
498 	 * the code considerably and also fits well with RFC2131.
499 	 * it is worth classifying the different DHCP commands into
500 	 * synchronous (those which we will handle now and be done
501 	 * with) and asynchronous (those which require transactions
502 	 * and will be completed at an indeterminate time in the
503 	 * future):
504 	 *
505 	 *    DROP: removes the agent's management of an interface.
506 	 *	    asynchronous as the script program may be invoked.
507 	 *
508 	 *    PING: checks to see if the agent controls an interface.
509 	 *	    synchronous, since no packets need to be sent
510 	 *	    to the DHCP server.
511 	 *
512 	 *  STATUS: returns information about the an interface.
513 	 *	    synchronous, since no packets need to be sent
514 	 *	    to the DHCP server.
515 	 *
516 	 * RELEASE: releases the agent's management of an interface
517 	 *	    and brings the interface down.  asynchronous as
518 	 *	    the script program may be invoked.
519 	 *
520 	 *  EXTEND: renews a lease.  asynchronous, since the agent
521 	 *	    needs to wait for an ACK, etc.
522 	 *
523 	 *   START: starts DHCP on an interface.  asynchronous since
524 	 *	    the agent needs to wait for OFFERs, ACKs, etc.
525 	 *
526 	 *  INFORM: obtains configuration parameters for an externally
527 	 *	    configured interface.  asynchronous, since the
528 	 *	    agent needs to wait for an ACK.
529 	 *
530 	 * notice that EXTEND, INFORM, START, DROP and RELEASE are
531 	 * asynchronous. notice also that asynchronous commands may
532 	 * occur from within the agent -- for instance, the agent
533 	 * will need to do implicit EXTENDs to extend the lease. in
534 	 * order to make the code simpler, the following rules apply
535 	 * for asynchronous commands:
536 	 *
537 	 * there can only be one asynchronous command at a time per
538 	 * interface.  the current asynchronous command is managed by
539 	 * the async_* api: async_start(), async_finish(),
540 	 * async_timeout(), async_cancel(), and async_pending().
541 	 * async_start() starts management of a new asynchronous
542 	 * command on an interface, which should only be done after
543 	 * async_pending() is called to check that there are no
544 	 * pending asynchronous commands on that interface.  when the
545 	 * command is completed, async_finish() should be called.  all
546 	 * asynchronous commands have an associated timer, which calls
547 	 * async_timeout() when it times out.  if async_timeout()
548 	 * decides that the asynchronous command should be cancelled
549 	 * (see below), it calls async_cancel() to attempt
550 	 * cancellation.
551 	 *
552 	 * asynchronous commands started by a user command have an
553 	 * associated ipc_action which provides the agent with
554 	 * information for how to get in touch with the user command
555 	 * when the action completes.  these ipc_action records also
556 	 * have an associated timeout which may be infinite.
557 	 * ipc_action_start() should be called when starting an
558 	 * asynchronous command requested by a user, which sets up the
559 	 * timer and keeps track of the ipc information (file
560 	 * descriptor, request type).  when the asynchronous command
561 	 * completes, ipc_action_finish() should be called to return a
562 	 * command status code to the user and close the ipc
563 	 * connection).  if the command does not complete before the
564 	 * timer fires, ipc_action_timeout() is called which closes
565 	 * the ipc connection and returns DHCP_IPC_E_TIMEOUT to the
566 	 * user.  note that independent of ipc_action_timeout(),
567 	 * ipc_action_finish() should be called.
568 	 *
569 	 * on a case-by-case basis, here is what happens (per interface):
570 	 *
571 	 *    o when an asynchronous command is requested, then
572 	 *	async_pending() is called to see if there is already
573 	 *	an asynchronous event.  if so, the command does not
574 	 *	proceed, and if there is an associated ipc_action,
575 	 *	the user command is sent DHCP_IPC_E_PEND.
576 	 *
577 	 *    o otherwise, the the transaction is started with
578 	 *	async_start().  if the transaction is on behalf
579 	 *	of a user, ipc_action_start() is called to keep
580 	 *	track of the ipc information and set up the
581 	 *	ipc_action timer.
582 	 *
583 	 *    o if the command completes normally and before a
584 	 *	timeout fires, then async_finish() is called.
585 	 *	if there was an associated ipc_action,
586 	 *	ipc_action_finish() is called to complete it.
587 	 *
588 	 *    o if the command fails before a timeout fires, then
589 	 *	async_finish() is called, and the interface is
590 	 *	is returned to a known state based on the command.
591 	 *	if there was an associated ipc_action,
592 	 *	ipc_action_finish() is called to complete it.
593 	 *
594 	 *    o if the ipc_action timer fires before command
595 	 *	completion, then DHCP_IPC_E_TIMEOUT is returned to
596 	 *	the user.  however, the transaction continues to
597 	 *	be carried out asynchronously.
598 	 *
599 	 *    o if async_timeout() fires before command completion,
600 	 *	then if the command was internal to the agent, it
601 	 *	is cancelled.  otherwise, if it was a user command,
602 	 *	then if the user is still waiting for the command
603 	 *	to complete, the command continues and async_timeout()
604 	 *	is rescheduled.
605 	 */
606 
607 	switch (cmd) {
608 
609 	case DHCP_DROP:					/* FALLTHRU */
610 	case DHCP_RELEASE:				/* FALLTHRU */
611 	case DHCP_EXTEND:				/* FALLTHRU */
612 	case DHCP_INFORM:				/* FALLTHRU */
613 	case DHCP_START:
614 		/*
615 		 * if shutdown request has been received, send back an error.
616 		 */
617 		if (shutdown_started) {
618 			send_error_reply(request, DHCP_IPC_E_OUTSTATE, &fd);
619 			return;
620 		}
621 
622 		if (async_pending(ifsp)) {
623 			send_error_reply(request, DHCP_IPC_E_PEND, &fd);
624 			return;
625 		}
626 
627 		if (ipc_action_start(ifsp, request, fd) == 0) {
628 			dhcpmsg(MSG_WARNING, "ipc_event: ipc_action_start "
629 			    "failed for %s", ifsp->if_name);
630 			send_error_reply(request, DHCP_IPC_E_MEMORY, &fd);
631 			return;
632 		}
633 
634 		if (async_start(ifsp, cmd, B_TRUE) == 0) {
635 			ipc_action_finish(ifsp, DHCP_IPC_E_MEMORY);
636 			return;
637 		}
638 		break;
639 
640 	default:
641 		break;
642 	}
643 
644 	switch (cmd) {
645 
646 	case DHCP_DROP:
647 		(void) script_start(ifsp, EVENT_DROP, dhcp_drop, NULL, NULL);
648 		return;
649 
650 	case DHCP_EXTEND:
651 		(void) dhcp_extending(ifsp);
652 		break;
653 
654 	case DHCP_GET_TAG: {
655 		dhcp_optnum_t	optnum;
656 		DHCP_OPT	*opt = NULL;
657 		boolean_t	did_alloc = B_FALSE;
658 		PKT_LIST	*ack = ifsp->if_ack;
659 
660 		/*
661 		 * verify the request makes sense.
662 		 */
663 
664 		if (request->data_type   != DHCP_TYPE_OPTNUM ||
665 		    request->data_length != sizeof (dhcp_optnum_t)) {
666 			send_error_reply(request, DHCP_IPC_E_PROTO, &fd);
667 			return;
668 		}
669 
670 		(void) memcpy(&optnum, request->buffer, sizeof (dhcp_optnum_t));
671 load_option:
672 		switch (optnum.category) {
673 
674 		case DSYM_SITE:			/* FALLTHRU */
675 		case DSYM_STANDARD:
676 			if (optnum.code <= DHCP_LAST_OPT)
677 				opt = ack->opts[optnum.code];
678 			break;
679 
680 		case DSYM_VENDOR:
681 			/*
682 			 * the test against VS_OPTION_START is broken up into
683 			 * two tests to avoid compiler warnings under intel.
684 			 */
685 
686 			if ((optnum.code > VS_OPTION_START ||
687 			    optnum.code == VS_OPTION_START) &&
688 			    optnum.code <= VS_OPTION_END)
689 				opt = ack->vs[optnum.code];
690 			break;
691 
692 		case DSYM_FIELD:
693 			if (optnum.code + optnum.size > sizeof (PKT))
694 				break;
695 
696 			/* + 2 to account for option code and length byte */
697 			opt = malloc(optnum.size + 2);
698 			if (opt == NULL) {
699 				send_error_reply(request, DHCP_IPC_E_MEMORY,
700 				    &fd);
701 				return;
702 			}
703 
704 			did_alloc = B_TRUE;
705 			opt->len  = optnum.size;
706 			opt->code = optnum.code;
707 			(void) memcpy(&opt->value, (caddr_t)ack->pkt +
708 			    opt->code, opt->len);
709 
710 			break;
711 
712 		default:
713 			send_error_reply(request, DHCP_IPC_E_PROTO, &fd);
714 			return;
715 		}
716 
717 		/*
718 		 * return the option payload, if there was one.  the "+ 2"
719 		 * accounts for the option code number and length byte.
720 		 */
721 
722 		if (opt != NULL) {
723 			send_data_reply(request, &fd, 0, DHCP_TYPE_OPTION, opt,
724 			    opt->len + 2);
725 
726 			if (did_alloc)
727 				free(opt);
728 			return;
729 		} else if (ack != ifsp->if_orig_ack) {
730 			/*
731 			 * There wasn't any definition for the option in the
732 			 * current ack, so now retry with the original ack if
733 			 * the original ack is not the current ack.
734 			 */
735 			ack = ifsp->if_orig_ack;
736 			goto load_option;
737 		}
738 
739 		/*
740 		 * note that an "okay" response is returned either in
741 		 * the case of an unknown option or a known option
742 		 * with no payload.  this is okay (for now) since
743 		 * dhcpinfo checks whether an option is valid before
744 		 * ever performing ipc with the agent.
745 		 */
746 
747 		send_ok_reply(request, &fd);
748 		return;
749 	}
750 
751 	case DHCP_INFORM:
752 		dhcp_inform(ifsp);
753 		/* next destination: dhcp_acknak() */
754 		return;
755 
756 	case DHCP_PING:
757 		if (ifsp->if_dflags & DHCP_IF_FAILED)
758 			send_error_reply(request, DHCP_IPC_E_FAILEDIF, &fd);
759 		else
760 			send_ok_reply(request, &fd);
761 		return;
762 
763 	case DHCP_RELEASE:
764 		(void) script_start(ifsp, EVENT_RELEASE, dhcp_release,
765 		    "Finished with lease.", NULL);
766 		return;
767 
768 	case DHCP_START:
769 		(void) canonize_ifs(ifsp);
770 
771 		/*
772 		 * if we have a valid hostconf lying around, then jump
773 		 * into INIT_REBOOT.  if it fails, we'll end up going
774 		 * through the whole selecting() procedure again.
775 		 */
776 
777 		error = read_hostconf(ifsp->if_name, plp, 2);
778 		if (error != -1) {
779 			ifsp->if_orig_ack = ifsp->if_ack = plp[0];
780 			if (error > 1) {
781 				/*
782 				 * Return indicated we had more than one packet
783 				 * second one is the original ack.  Older
784 				 * versions of the agent wrote only one ack
785 				 * to the file, we now keep both the first
786 				 * ack as well as the last one.
787 				 */
788 				ifsp->if_orig_ack = plp[1];
789 			}
790 			dhcp_init_reboot(ifsp);
791 			/* next destination: dhcp_acknak() */
792 			return;
793 		}
794 
795 		/*
796 		 * if not debugging, wait for a few seconds before
797 		 * going into SELECTING.
798 		 */
799 
800 		if (debug_level == 0) {
801 			if (iu_schedule_timer_ms(tq,
802 			    lrand48() % DHCP_SELECT_WAIT, dhcp_start, ifsp)
803 			    != -1) {
804 				hold_ifs(ifsp);
805 				/* next destination: dhcp_start() */
806 				return;
807 			}
808 		}
809 
810 		dhcp_selecting(ifsp);
811 		/* next destination: dhcp_requesting() */
812 		return;
813 
814 	case DHCP_STATUS: {
815 		dhcp_status_t	status;
816 
817 		status.if_began = monosec_to_time(ifsp->if_curstart_monosec);
818 
819 		if (ifsp->if_lease == DHCP_PERM) {
820 			status.if_t1	= DHCP_PERM;
821 			status.if_t2	= DHCP_PERM;
822 			status.if_lease	= DHCP_PERM;
823 		} else {
824 			status.if_t1	= status.if_began + ifsp->if_t1;
825 			status.if_t2	= status.if_began + ifsp->if_t2;
826 			status.if_lease	= status.if_began + ifsp->if_lease;
827 		}
828 
829 		status.version		= DHCP_STATUS_VER;
830 		status.if_state		= ifsp->if_state;
831 		status.if_dflags	= ifsp->if_dflags;
832 		status.if_sent		= ifsp->if_sent;
833 		status.if_recv		= ifsp->if_received;
834 		status.if_bad_offers	= ifsp->if_bad_offers;
835 
836 		(void) strlcpy(status.if_name, ifsp->if_name, IFNAMSIZ);
837 
838 		send_data_reply(request, &fd, 0, DHCP_TYPE_STATUS, &status,
839 		    sizeof (dhcp_status_t));
840 		return;
841 	}
842 
843 	default:
844 		return;
845 	}
846 }
847