xref: /illumos-gate/usr/src/lib/libdhcpagent/common/dhcpagent_ipc.c (revision 45ede40b2394db7967e59f19288fae9b62efd4aa)
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 /*
26  * Copyright (c) 2016 by Delphix. All rights reserved.
27  */
28 
29 #include <string.h>
30 #include <unistd.h>
31 #include <stdlib.h>
32 #include <sys/uio.h>
33 #include <sys/socket.h>
34 #include <sys/types.h>
35 #include <fcntl.h>
36 #include <errno.h>
37 #include <limits.h>
38 #include <netinet/in.h>
39 #include <netinet/tcp.h>
40 #include <net/if.h>
41 #include <sys/sockio.h>
42 #include <sys/fcntl.h>
43 #include <sys/time.h>
44 #include <stdio.h>		/* snprintf */
45 #include <arpa/inet.h>		/* ntohl, ntohs, etc */
46 
47 #include "dhcpagent_ipc.h"
48 #include "dhcpagent_util.h"
49 
50 /*
51  * the protocol used here is a simple request/reply scheme: a client
52  * sends a dhcp_ipc_request_t message to the agent, and the agent
53  * sends a dhcp_ipc_reply_t back to the client.  since the requests
54  * and replies can be variable-length, they are prefixed on "the wire"
55  * by a 32-bit number that tells the other end how many bytes to
56  * expect.
57  *
58  * the format of a request consists of a single dhcp_ipc_request_t;
59  * note that the length of this dhcp_ipc_request_t is variable (using
60  * the standard c array-of-size-1 trick).  the type of the payload is
61  * given by `data_type', which is guaranteed to be `data_length' bytes
62  * long starting at `buffer'.  note that `buffer' is guaranteed to be
63  * 32-bit aligned but it is poor taste to rely on this.
64  *
65  * the format of a reply is much the same: a single dhcp_ipc_reply_t;
66  * note again that the length of the dhcp_ipc_reply_t is variable.
67  * the type of the payload is given by `data_type', which is
68  * guaranteed to be `data_length' bytes long starting at `buffer'.
69  * once again, note that `buffer' is guaranteed to be 32-bit aligned
70  * but it is poor taste to rely on this.
71  *
72  * requests and replies can be paired up by comparing `ipc_id' fields.
73  */
74 
75 #define	BUFMAX	256
76 
77 static int	dhcp_ipc_timed_read(int, void *, unsigned int, int *);
78 static int	getinfo_ifnames(const char *, dhcp_optnum_t *, DHCP_OPT **);
79 static char	*get_ifnames(int, int);
80 
81 /* must be kept in sync with enum in dhcpagent_ipc.h */
82 static const char *ipc_typestr[] = {
83 	"drop", "extend", "ping", "release", "start", "status",
84 	"inform", "get_tag"
85 };
86 
87 /*
88  * dhcp_ipc_alloc_request(): allocates a dhcp_ipc_request_t of the given type
89  *			     and interface, with a timeout of 0.
90  *
91  *   input: dhcp_ipc_type_t: the type of ipc request to allocate
92  *	    const char *: the interface to associate the request with
93  *	    const void *: the payload to send with the message (NULL if none)
94  *	    uint32_t: the payload size (0 if none)
95  *	    dhcp_data_type_t: the description of the type of payload
96  *  output: dhcp_ipc_request_t *: the request on success, NULL on failure
97  */
98 
99 dhcp_ipc_request_t *
100 dhcp_ipc_alloc_request(dhcp_ipc_type_t type, const char *ifname,
101     const void *buffer, uint32_t buffer_size, dhcp_data_type_t data_type)
102 {
103 	dhcp_ipc_request_t *request = calloc(1, DHCP_IPC_REQUEST_SIZE +
104 	    buffer_size);
105 
106 	if (request == NULL)
107 		return (NULL);
108 
109 	request->message_type   = type;
110 	request->data_length    = buffer_size;
111 	request->data_type	= data_type;
112 
113 	if (ifname != NULL)
114 		(void) strlcpy(request->ifname, ifname, LIFNAMSIZ);
115 
116 	if (buffer != NULL)
117 		(void) memcpy(request->buffer, buffer, buffer_size);
118 
119 	return (request);
120 }
121 
122 /*
123  * dhcp_ipc_alloc_reply(): allocates a dhcp_ipc_reply_t
124  *
125  *   input: dhcp_ipc_request_t *: the request the reply is for
126  *	    int: the return code (0 for success, DHCP_IPC_E_* otherwise)
127  *	    const void *: the payload to send with the message (NULL if none)
128  *	    uint32_t: the payload size (0 if none)
129  *	    dhcp_data_type_t: the description of the type of payload
130  *  output: dhcp_ipc_reply_t *: the reply on success, NULL on failure
131  */
132 
133 dhcp_ipc_reply_t *
134 dhcp_ipc_alloc_reply(dhcp_ipc_request_t *request, int return_code,
135     const void *buffer, uint32_t buffer_size, dhcp_data_type_t data_type)
136 {
137 	dhcp_ipc_reply_t *reply = calloc(1, DHCP_IPC_REPLY_SIZE + buffer_size);
138 
139 	if (reply == NULL)
140 		return (NULL);
141 
142 	reply->message_type	= request->message_type;
143 	reply->ipc_id		= request->ipc_id;
144 	reply->return_code	= return_code;
145 	reply->data_length	= buffer_size;
146 	reply->data_type	= data_type;
147 
148 	if (buffer != NULL)
149 		(void) memcpy(reply->buffer, buffer, buffer_size);
150 
151 	return (reply);
152 }
153 
154 /*
155  * dhcp_ipc_get_data(): gets the data and data type from a dhcp_ipc_reply_t
156  *
157  *   input: dhcp_ipc_reply_t *: the reply to get data from
158  *	    size_t *: the size of the resulting data
159  *	    dhcp_data_type_t *: the type of the message (returned)
160  *  output: void *: a pointer to the data, if there is any.
161  */
162 
163 void *
164 dhcp_ipc_get_data(dhcp_ipc_reply_t *reply, size_t *size, dhcp_data_type_t *type)
165 {
166 	if (reply == NULL || reply->data_length == 0) {
167 		*size = 0;
168 		return (NULL);
169 	}
170 
171 	if (type != NULL)
172 		*type = reply->data_type;
173 
174 	*size = reply->data_length;
175 	return (reply->buffer);
176 }
177 
178 /*
179  * dhcp_ipc_recv_msg(): gets a message using the agent's ipc protocol
180  *
181  *   input: int: the file descriptor to get the message from
182  *	    void **: the address of a pointer to store the message
183  *		     (dynamically allocated)
184  *	    uint32_t: the minimum length of the packet
185  *	    int: the # of milliseconds to wait for the message (-1 is forever)
186  *  output: int: DHCP_IPC_SUCCESS on success, DHCP_IPC_E_* otherwise
187  */
188 
189 static int
190 dhcp_ipc_recv_msg(int fd, void **msg, uint32_t base_length, int msec)
191 {
192 	int			retval;
193 	dhcp_ipc_reply_t	*ipc_msg;
194 	uint32_t		length;
195 
196 	retval = dhcp_ipc_timed_read(fd, &length, sizeof (uint32_t), &msec);
197 	if (retval != DHCP_IPC_SUCCESS)
198 		return (retval);
199 
200 	if (length == 0)
201 		return (DHCP_IPC_E_PROTO);
202 
203 	*msg = malloc(length);
204 	if (*msg == NULL)
205 		return (DHCP_IPC_E_MEMORY);
206 
207 	retval = dhcp_ipc_timed_read(fd, *msg, length, &msec);
208 	if (retval != DHCP_IPC_SUCCESS) {
209 		free(*msg);
210 		return (retval);
211 	}
212 
213 	if (length < base_length) {
214 		free(*msg);
215 		return (DHCP_IPC_E_PROTO);
216 	}
217 
218 	/*
219 	 * the data_length field is in the same place in either ipc message.
220 	 */
221 
222 	ipc_msg = (dhcp_ipc_reply_t *)(*msg);
223 	if (ipc_msg->data_length + base_length != length) {
224 		free(*msg);
225 		return (DHCP_IPC_E_PROTO);
226 	}
227 
228 	return (DHCP_IPC_SUCCESS);
229 }
230 
231 /*
232  * dhcp_ipc_recv_request(): gets a request using the agent's ipc protocol
233  *
234  *   input: int: the file descriptor to get the message from
235  *	    dhcp_ipc_request_t **: address of a pointer to store the request
236  *				 (dynamically allocated)
237  *	    int: the # of milliseconds to wait for the message (-1 is forever)
238  *  output: int: 0 on success, DHCP_IPC_E_* otherwise
239  */
240 
241 int
242 dhcp_ipc_recv_request(int fd, dhcp_ipc_request_t **request, int msec)
243 {
244 	int	retval;
245 
246 	retval = dhcp_ipc_recv_msg(fd, (void **)request, DHCP_IPC_REQUEST_SIZE,
247 	    msec);
248 
249 	/* guarantee that ifname will be NUL-terminated */
250 	if (retval == 0)
251 		(*request)->ifname[LIFNAMSIZ - 1] = '\0';
252 
253 	return (retval);
254 }
255 
256 /*
257  * dhcp_ipc_recv_reply(): gets a reply using the agent's ipc protocol
258  *
259  *   input: int: the file descriptor to get the message from
260  *	    dhcp_ipc_reply_t **: address of a pointer to store the reply
261  *				 (dynamically allocated)
262  *	    int32_t: timeout (in seconds), or DHCP_IPC_WAIT_FOREVER,
263  *		     or DHCP_IPC_WAIT_DEFAULT
264  *  output: int: 0 on success, DHCP_IPC_E_* otherwise
265  */
266 
267 static int
268 dhcp_ipc_recv_reply(int fd, dhcp_ipc_reply_t **reply, int32_t timeout)
269 {
270 	/*
271 	 * If the caller doesn't want to wait forever, and the amount of time
272 	 * it wants to wait is expressible as an integer number of milliseconds
273 	 * (as needed by the msg function), then we wait that amount of time
274 	 * plus an extra two seconds for the daemon to do its work.  The extra
275 	 * two seconds is arbitrary; it should allow plenty of time for the
276 	 * daemon to respond within the existing timeout, as specified in the
277 	 * original request, so the only time we give up is when the daemon is
278 	 * stopped or otherwise malfunctioning.
279 	 *
280 	 * Note that the wait limit (milliseconds in an 'int') is over 24 days,
281 	 * so it's unlikely that any request will actually be that long, and
282 	 * it's unlikely that anyone will care if we wait forever on a request
283 	 * for a 30 day timer.  The point is to protect against daemon
284 	 * malfunction in the usual cases, not to provide an absolute command
285 	 * timer.
286 	 */
287 	if (timeout == DHCP_IPC_WAIT_DEFAULT)
288 		timeout = DHCP_IPC_DEFAULT_WAIT;
289 	if (timeout != DHCP_IPC_WAIT_FOREVER && timeout < INT_MAX / 1000 - 2)
290 		timeout = (timeout + 2) * 1000;
291 	else
292 		timeout = -1;
293 	return (dhcp_ipc_recv_msg(fd, (void **)reply, DHCP_IPC_REPLY_SIZE,
294 	    timeout));
295 }
296 
297 /*
298  * dhcp_ipc_send_msg(): transmits a message using the agent's ipc protocol
299  *
300  *   input: int: the file descriptor to transmit on
301  *	    void *: the message to send
302  *	    uint32_t: the message length
303  *  output: int: 0 on success, DHCP_IPC_E_* otherwise
304  */
305 
306 static int
307 dhcp_ipc_send_msg(int fd, void *msg, uint32_t message_length)
308 {
309 	struct iovec	iovec[2];
310 
311 	iovec[0].iov_base = (caddr_t)&message_length;
312 	iovec[0].iov_len  = sizeof (uint32_t);
313 	iovec[1].iov_base = msg;
314 	iovec[1].iov_len  = message_length;
315 
316 	if (writev(fd, iovec, sizeof (iovec) / sizeof (*iovec)) == -1)
317 		return (DHCP_IPC_E_WRITEV);
318 
319 	return (0);
320 }
321 
322 /*
323  * dhcp_ipc_send_reply(): transmits a reply using the agent's ipc protocol
324  *
325  *   input: int: the file descriptor to transmit on
326  *	    dhcp_ipc_reply_t *: the reply to send
327  *  output: int: 0 on success, DHCP_IPC_E_* otherwise
328  */
329 
330 int
331 dhcp_ipc_send_reply(int fd, dhcp_ipc_reply_t *reply)
332 {
333 	return (dhcp_ipc_send_msg(fd, reply, DHCP_IPC_REPLY_SIZE +
334 	    reply->data_length));
335 }
336 
337 /*
338  * dhcp_ipc_send_request(): transmits a request using the agent's ipc protocol
339  *
340  *   input: int: the file descriptor to transmit on
341  *	    dhcp_ipc_request_t *: the request to send
342  *  output: int: 0 on success, DHCP_IPC_E_* otherwise
343  */
344 
345 static int
346 dhcp_ipc_send_request(int fd, dhcp_ipc_request_t *request)
347 {
348 	/*
349 	 * for now, ipc_ids aren't really used, but they're intended
350 	 * to make it easy to send several requests and then collect
351 	 * all of the replies (and pair them with the requests).
352 	 */
353 
354 	request->ipc_id = gethrtime();
355 
356 	return (dhcp_ipc_send_msg(fd, request, DHCP_IPC_REQUEST_SIZE +
357 	    request->data_length));
358 }
359 
360 /*
361  * dhcp_ipc_make_request(): sends the provided request to the agent and reaps
362  *			    the reply
363  *
364  *   input: dhcp_ipc_request_t *: the request to make
365  *	    dhcp_ipc_reply_t **: the reply (dynamically allocated)
366  *	    int32_t: timeout (in seconds), or DHCP_IPC_WAIT_FOREVER,
367  *		     or DHCP_IPC_WAIT_DEFAULT
368  *  output: int: 0 on success, DHCP_IPC_E_* otherwise
369  */
370 
371 int
372 dhcp_ipc_make_request(dhcp_ipc_request_t *request, dhcp_ipc_reply_t **reply,
373     int32_t timeout)
374 {
375 	int			fd, on, retval;
376 	struct sockaddr_in	sinv;
377 
378 	fd = socket(AF_INET, SOCK_STREAM, 0);
379 	if (fd == -1)
380 		return (DHCP_IPC_E_SOCKET);
381 
382 	/*
383 	 * Bind a privileged port if we have sufficient privilege to do so.
384 	 * Continue as non-privileged otherwise.
385 	 */
386 	on = 1;
387 	(void) setsockopt(fd, IPPROTO_TCP, TCP_ANONPRIVBIND, &on, sizeof (on));
388 
389 	(void) memset(&sinv, 0, sizeof (sinv));
390 	sinv.sin_family	 = AF_INET;
391 	if (bind(fd, (struct sockaddr *)&sinv, sizeof (sinv)) == -1) {
392 		(void) dhcp_ipc_close(fd);
393 		return (DHCP_IPC_E_BIND);
394 	}
395 
396 	sinv.sin_port = htons(IPPORT_DHCPAGENT);
397 	sinv.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
398 	retval = connect(fd, (struct sockaddr *)&sinv, sizeof (sinv));
399 	if (retval == -1) {
400 		(void) dhcp_ipc_close(fd);
401 		return (DHCP_IPC_E_CONNECT);
402 	}
403 
404 	request->timeout = timeout;
405 
406 	retval = dhcp_ipc_send_request(fd, request);
407 	if (retval == 0)
408 		retval = dhcp_ipc_recv_reply(fd, reply, timeout);
409 
410 	(void) dhcp_ipc_close(fd);
411 
412 	return (retval);
413 }
414 
415 /*
416  * dhcp_ipc_init(): initializes the ipc channel for use by the agent
417  *
418  *   input: int *: the file descriptor to accept on (returned)
419  *  output: int: 0 on success, DHCP_IPC_E_* otherwise
420  */
421 
422 int
423 dhcp_ipc_init(int *listen_fd)
424 {
425 	struct sockaddr_in	sin;
426 	int			on = 1;
427 
428 	(void) memset(&sin, 0, sizeof (struct sockaddr_in));
429 
430 	sin.sin_family		= AF_INET;
431 	sin.sin_port		= htons(IPPORT_DHCPAGENT);
432 	sin.sin_addr.s_addr	= htonl(INADDR_LOOPBACK);
433 
434 	*listen_fd = socket(AF_INET, SOCK_STREAM, 0);
435 	if (*listen_fd == -1)
436 		return (DHCP_IPC_E_SOCKET);
437 
438 	/*
439 	 * we use SO_REUSEADDR here since in the case where there
440 	 * really is another daemon running that is using the agent's
441 	 * port, bind(3N) will fail.  so we can't lose.
442 	 */
443 
444 	(void) setsockopt(*listen_fd, SOL_SOCKET, SO_REUSEADDR, &on,
445 	    sizeof (on));
446 
447 	if (bind(*listen_fd, (struct sockaddr *)&sin, sizeof (sin)) == -1) {
448 		(void) close(*listen_fd);
449 		return (DHCP_IPC_E_BIND);
450 	}
451 
452 	if (listen(*listen_fd, DHCP_IPC_LISTEN_BACKLOG) == -1) {
453 		(void) close(*listen_fd);
454 		return (DHCP_IPC_E_LISTEN);
455 	}
456 
457 	return (0);
458 }
459 
460 /*
461  * dhcp_ipc_accept(): accepts an incoming connection for the agent
462  *
463  *   input: int: the file descriptor to accept on
464  *	    int *: the accepted file descriptor (returned)
465  *	    int *: nonzero if the client is privileged (returned)
466  *  output: int: 0 on success, DHCP_IPC_E_* otherwise
467  *    note: sets the socket into nonblocking mode
468  */
469 
470 int
471 dhcp_ipc_accept(int listen_fd, int *fd, int *is_priv)
472 {
473 	struct sockaddr_in	sin_peer;
474 	int			sin_len = sizeof (sin_peer);
475 	int			sockflags;
476 
477 	/*
478 	 * if we were extremely concerned with portability, we would
479 	 * set the socket into nonblocking mode before doing the
480 	 * accept(3N), since on BSD-based networking stacks, there is
481 	 * a potential race that can occur if the socket which
482 	 * connected to us performs a TCP RST before we accept, since
483 	 * BSD handles this case entirely in the kernel and as a
484 	 * result even though select said we will not block, we can
485 	 * end up blocking since there is no longer a connection to
486 	 * accept.  on SVR4-based systems, this should be okay,
487 	 * and we will get EPROTO back, even though POSIX.1g says
488 	 * we should get ECONNABORTED.
489 	 */
490 
491 	*fd = accept(listen_fd, (struct sockaddr *)&sin_peer, &sin_len);
492 	if (*fd == -1)
493 		return (DHCP_IPC_E_ACCEPT);
494 
495 	/* get credentials */
496 	*is_priv = ntohs(sin_peer.sin_port) < IPPORT_RESERVED;
497 
498 	/*
499 	 * kick the socket into non-blocking mode so that later
500 	 * operations on the socket don't block and hold up the whole
501 	 * application.  with the event demuxing approach, this may
502 	 * seem unnecessary, but in order to get partial reads/writes
503 	 * and to handle our internal protocol for passing data
504 	 * between the agent and its consumers, this is needed.
505 	 */
506 
507 	if ((sockflags = fcntl(*fd, F_GETFL, 0)) == -1) {
508 		(void) close(*fd);
509 		return (DHCP_IPC_E_FCNTL);
510 	}
511 
512 	if (fcntl(*fd, F_SETFL, sockflags | O_NONBLOCK) == -1) {
513 		(void) close(*fd);
514 		return (DHCP_IPC_E_FCNTL);
515 	}
516 
517 	return (0);
518 }
519 
520 /*
521  * dhcp_ipc_close(): closes an ipc descriptor
522  *
523  *   input: int: the file descriptor to close
524  *  output: int: 0 on success, DHCP_IPC_E_* otherwise
525  */
526 
527 int
528 dhcp_ipc_close(int fd)
529 {
530 	return ((close(fd) == -1) ? DHCP_IPC_E_CLOSE : 0);
531 }
532 
533 /*
534  * dhcp_ipc_strerror(): maps an ipc error code into a human-readable string
535  *
536  *   input: int: the ipc error code to map
537  *  output: const char *: the corresponding human-readable string
538  */
539 
540 const char *
541 dhcp_ipc_strerror(int error)
542 {
543 	/* note: this must be kept in sync with DHCP_IPC_E_* definitions */
544 	const char *syscalls[] = {
545 		"<unknown>", "socket", "fcntl", "read", "accept", "close",
546 		"bind", "listen", "malloc", "connect", "writev", "poll"
547 	};
548 
549 	const char	*error_string;
550 	static char	buffer[BUFMAX];
551 
552 	switch (error) {
553 
554 	/*
555 	 * none of these errors actually go over the wire.
556 	 * hence, we assume that errno is still fresh.
557 	 */
558 
559 	case DHCP_IPC_E_SOCKET:			/* FALLTHRU */
560 	case DHCP_IPC_E_FCNTL:			/* FALLTHRU */
561 	case DHCP_IPC_E_READ:			/* FALLTHRU */
562 	case DHCP_IPC_E_ACCEPT:			/* FALLTHRU */
563 	case DHCP_IPC_E_CLOSE:			/* FALLTHRU */
564 	case DHCP_IPC_E_BIND:			/* FALLTHRU */
565 	case DHCP_IPC_E_LISTEN:			/* FALLTHRU */
566 	case DHCP_IPC_E_CONNECT:		/* FALLTHRU */
567 	case DHCP_IPC_E_WRITEV:			/* FALLTHRU */
568 	case DHCP_IPC_E_POLL:
569 
570 		error_string = strerror(errno);
571 		if (error_string == NULL)
572 			error_string = "unknown error";
573 
574 		(void) snprintf(buffer, sizeof (buffer), "%s: %s",
575 		    syscalls[error], error_string);
576 
577 		error_string = buffer;
578 		break;
579 
580 	case DHCP_IPC_E_MEMORY:
581 		error_string = "out of memory";
582 		break;
583 
584 	case DHCP_IPC_E_TIMEOUT:
585 		error_string = "wait timed out, operation still pending...";
586 		break;
587 
588 	case DHCP_IPC_E_INVIF:
589 		error_string = "interface does not exist or cannot be managed "
590 		    "using DHCP";
591 		break;
592 
593 	case DHCP_IPC_E_INT:
594 		error_string = "internal error (might work later)";
595 		break;
596 
597 	case DHCP_IPC_E_PERM:
598 		error_string = "permission denied";
599 		break;
600 
601 	case DHCP_IPC_E_OUTSTATE:
602 		error_string = "interface not in appropriate state for command";
603 		break;
604 
605 	case DHCP_IPC_E_PEND:
606 		error_string = "interface currently has a pending command "
607 		    "(try later)";
608 		break;
609 
610 	case DHCP_IPC_E_BOOTP:
611 		error_string = "interface is administered with BOOTP, not DHCP";
612 		break;
613 
614 	case DHCP_IPC_E_CMD_UNKNOWN:
615 		error_string = "unknown command";
616 		break;
617 
618 	case DHCP_IPC_E_UNKIF:
619 		error_string = "interface is not under DHCP control";
620 		break;
621 
622 	case DHCP_IPC_E_PROTO:
623 		error_string = "ipc protocol violation";
624 		break;
625 
626 	case DHCP_IPC_E_FAILEDIF:
627 		error_string = "interface is in a FAILED state and must be "
628 		    "manually restarted";
629 		break;
630 
631 	case DHCP_IPC_E_NOPRIMARY:
632 		error_string = "primary interface requested but no primary "
633 		    "interface is set";
634 		break;
635 
636 	case DHCP_IPC_E_NOIPIF:
637 		error_string = "interface currently has no IP address";
638 		break;
639 
640 	case DHCP_IPC_E_DOWNIF:
641 		error_string = "interface is currently down";
642 		break;
643 
644 	case DHCP_IPC_E_NOVALUE:
645 		error_string = "no value was found for this option";
646 		break;
647 
648 	case DHCP_IPC_E_RUNNING:
649 		error_string = "DHCP is already running";
650 		break;
651 
652 	case DHCP_IPC_E_SRVFAILED:
653 		error_string = "DHCP server refused request";
654 		break;
655 
656 	case DHCP_IPC_E_EOF:
657 		error_string = "ipc connection closed";
658 		break;
659 
660 	default:
661 		error_string = "unknown error";
662 		break;
663 	}
664 
665 	/*
666 	 * TODO: internationalize this error string
667 	 */
668 
669 	return (error_string);
670 }
671 
672 /*
673  * dhcp_string_to_request(): maps a string into a request code
674  *
675  *    input: const char *: the string to map
676  *   output: dhcp_ipc_type_t: the request code, or -1 if unknown
677  */
678 
679 dhcp_ipc_type_t
680 dhcp_string_to_request(const char *request)
681 {
682 	unsigned int	i;
683 
684 	for (i = 0; i < DHCP_NIPC; i++)
685 		if (strcmp(ipc_typestr[i], request) == 0)
686 			return ((dhcp_ipc_type_t)i);
687 
688 	return ((dhcp_ipc_type_t)-1);
689 }
690 
691 /*
692  * dhcp_ipc_type_to_string(): maps an ipc command code into a human-readable
693  *			      string
694  *
695  *   input: int: the ipc command code to map
696  *  output: const char *: the corresponding human-readable string
697  */
698 
699 const char *
700 dhcp_ipc_type_to_string(dhcp_ipc_type_t type)
701 {
702 	if (type < 0 || type >= DHCP_NIPC)
703 		return ("unknown");
704 	else
705 		return (ipc_typestr[(int)type]);
706 }
707 
708 /*
709  * getinfo_ifnames(): checks the value of a specified option on a list of
710  *		      interface names.
711  *   input: const char *: a list of interface names to query (in order) for
712  *			  the option; "" queries the primary interface
713  *	    dhcp_optnum_t *: a description of the desired option
714  *	    DHCP_OPT **:  filled in with the (dynamically allocated) value of
715  *			  the option upon success.
716  *  output: int: DHCP_IPC_E_* on error, 0 on success or if no value was
717  *	         found but no error occurred either (*result will be NULL)
718  */
719 
720 static int
721 getinfo_ifnames(const char *ifn, dhcp_optnum_t *optnum, DHCP_OPT **result)
722 {
723 	dhcp_ipc_request_t	*request;
724 	dhcp_ipc_reply_t	*reply;
725 	char			*ifnames, *ifnames_head;
726 	DHCP_OPT		*opt;
727 	size_t			opt_size;
728 	int			retval = 0;
729 
730 	*result = NULL;
731 	ifnames_head = ifnames = strdup(ifn);
732 	if (ifnames == NULL)
733 		return (DHCP_IPC_E_MEMORY);
734 
735 	request = dhcp_ipc_alloc_request(DHCP_GET_TAG, "", optnum,
736 	    sizeof (dhcp_optnum_t), DHCP_TYPE_OPTNUM);
737 
738 	if (request == NULL) {
739 		free(ifnames_head);
740 		return (DHCP_IPC_E_MEMORY);
741 	}
742 
743 	ifnames = strtok(ifnames, " ");
744 	if (ifnames == NULL)
745 		ifnames = "";
746 
747 	for (; ifnames != NULL; ifnames = strtok(NULL, " ")) {
748 
749 		(void) strlcpy(request->ifname, ifnames, LIFNAMSIZ);
750 		retval = dhcp_ipc_make_request(request, &reply, 0);
751 		if (retval != 0)
752 			break;
753 
754 		if (reply->return_code == 0) {
755 			opt = dhcp_ipc_get_data(reply, &opt_size, NULL);
756 			if (opt_size > 2 && (opt->len == opt_size - 2)) {
757 				*result = malloc(opt_size);
758 				if (*result == NULL)
759 					retval = DHCP_IPC_E_MEMORY;
760 				else
761 					(void) memcpy(*result, opt, opt_size);
762 
763 				free(reply);
764 				break;
765 			}
766 		}
767 
768 		free(reply);
769 		if (ifnames[0] == '\0')
770 			break;
771 	}
772 
773 	free(request);
774 	free(ifnames_head);
775 
776 	return (retval);
777 }
778 
779 /*
780  * get_ifnames(): returns a space-separated list of interface names that
781  *		  match the specified flags
782  *
783  *   input: int: flags which must be on in each interface returned
784  *	    int: flags which must be off in each interface returned
785  *  output: char *: a dynamically-allocated list of interface names, or
786  *		    NULL upon failure.
787  */
788 
789 static char *
790 get_ifnames(int flags_on, int flags_off)
791 {
792 	struct ifconf	ifc;
793 	int		n_ifs, i, sock_fd;
794 	char		*ifnames;
795 
796 
797 	sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
798 	if (sock_fd == -1)
799 		return (NULL);
800 
801 	if ((ioctl(sock_fd, SIOCGIFNUM, &n_ifs) == -1) || (n_ifs <= 0)) {
802 		(void) close(sock_fd);
803 		return (NULL);
804 	}
805 
806 	ifnames = calloc(1, n_ifs * (LIFNAMSIZ + 1));
807 	ifc.ifc_len = n_ifs * sizeof (struct ifreq);
808 	ifc.ifc_req = calloc(n_ifs, sizeof (struct ifreq));
809 	if (ifc.ifc_req != NULL && ifnames != NULL) {
810 
811 		if (ioctl(sock_fd, SIOCGIFCONF, &ifc) == -1) {
812 			(void) close(sock_fd);
813 			free(ifnames);
814 			free(ifc.ifc_req);
815 			return (NULL);
816 		}
817 
818 		for (i = 0; i < n_ifs; i++) {
819 
820 			if (ioctl(sock_fd, SIOCGIFFLAGS, &ifc.ifc_req[i]) == 0)
821 				if ((ifc.ifc_req[i].ifr_flags &
822 				    (flags_on | flags_off)) != flags_on)
823 					continue;
824 
825 			(void) strcat(ifnames, ifc.ifc_req[i].ifr_name);
826 			(void) strcat(ifnames, " ");
827 		}
828 
829 		if (strlen(ifnames) > 1)
830 			ifnames[strlen(ifnames) - 1] = '\0';
831 	}
832 
833 	(void) close(sock_fd);
834 	free(ifc.ifc_req);
835 	return (ifnames);
836 }
837 
838 /*
839  * dhcp_ipc_getinfo(): attempts to retrieve a value for the specified DHCP
840  *		       option; tries primary interface, then all DHCP-owned
841  *		       interfaces, then INFORMs on the remaining interfaces
842  *		       (these interfaces are dropped prior to returning).
843  *   input: dhcp_optnum_t *: a description of the desired option
844  *	    DHCP_OPT **:  filled in with the (dynamically allocated) value of
845  *			  the option upon success.
846  *	    int32_t: timeout (in seconds), or DHCP_IPC_WAIT_FOREVER,
847  *		     or DHCP_IPC_WAIT_DEFAULT.
848  *  output: int: DHCP_IPC_E_* on error, 0 upon success.
849  */
850 
851 int
852 dhcp_ipc_getinfo(dhcp_optnum_t *optnum, DHCP_OPT **result, int32_t timeout)
853 {
854 	dhcp_ipc_request_t	*request;
855 	dhcp_ipc_reply_t	*reply;
856 	char			*ifnames, *ifnames_copy, *ifnames_head;
857 	int			retval;
858 	time_t			start_time = time(NULL);
859 
860 	if (timeout == DHCP_IPC_WAIT_DEFAULT)
861 		timeout = DHCP_IPC_DEFAULT_WAIT;
862 
863 	/*
864 	 * wait at most 5 seconds for the agent to start.
865 	 */
866 
867 	if (dhcp_start_agent((timeout > 5 || timeout < 0) ? 5 : timeout) == -1)
868 		return (DHCP_IPC_E_INT);
869 
870 	/*
871 	 * check the primary interface for the option value first.
872 	 */
873 
874 	retval = getinfo_ifnames("", optnum, result);
875 	if ((retval != 0) || (retval == 0 && *result != NULL))
876 		return (retval);
877 
878 	/*
879 	 * no luck.  get a list of the interfaces under DHCP control
880 	 * and perform a GET_TAG on each one.
881 	 */
882 
883 	ifnames = get_ifnames(IFF_DHCPRUNNING, 0);
884 	if (ifnames != NULL && strlen(ifnames) != 0) {
885 		retval = getinfo_ifnames(ifnames, optnum, result);
886 		if ((retval != 0) || (retval == 0 && *result != NULL)) {
887 			free(ifnames);
888 			return (retval);
889 		}
890 	}
891 	free(ifnames);
892 
893 	/*
894 	 * still no luck.  retrieve a list of all interfaces on the
895 	 * system that could use DHCP but aren't.  send INFORMs out on
896 	 * each one. after that, sit in a loop for the next `timeout'
897 	 * seconds, trying every second to see if a response for the
898 	 * option we want has come in on one of the interfaces.
899 	 */
900 
901 	ifnames = get_ifnames(IFF_UP|IFF_RUNNING, IFF_LOOPBACK|IFF_DHCPRUNNING);
902 	if (ifnames == NULL || strlen(ifnames) == 0) {
903 		free(ifnames);
904 		return (DHCP_IPC_E_NOVALUE);
905 	}
906 
907 	ifnames_head = ifnames_copy = strdup(ifnames);
908 	if (ifnames_copy == NULL) {
909 		free(ifnames);
910 		return (DHCP_IPC_E_MEMORY);
911 	}
912 
913 	request = dhcp_ipc_alloc_request(DHCP_INFORM, "", NULL, 0,
914 	    DHCP_TYPE_NONE);
915 	if (request == NULL) {
916 		free(ifnames);
917 		free(ifnames_head);
918 		return (DHCP_IPC_E_MEMORY);
919 	}
920 
921 	ifnames_copy = strtok(ifnames_copy, " ");
922 	for (; ifnames_copy != NULL; ifnames_copy = strtok(NULL, " ")) {
923 		(void) strlcpy(request->ifname, ifnames_copy, LIFNAMSIZ);
924 		if (dhcp_ipc_make_request(request, &reply, 0) == 0)
925 			free(reply);
926 	}
927 
928 	for (;;) {
929 		if ((timeout != DHCP_IPC_WAIT_FOREVER) &&
930 		    (time(NULL) - start_time > timeout)) {
931 			retval = DHCP_IPC_E_TIMEOUT;
932 			break;
933 		}
934 
935 		retval = getinfo_ifnames(ifnames, optnum, result);
936 		if (retval != 0 || (retval == 0 && *result != NULL))
937 			break;
938 
939 		(void) sleep(1);
940 	}
941 
942 	/*
943 	 * drop any interfaces that weren't under DHCP control before
944 	 * we got here; this keeps this function more of a black box
945 	 * and the behavior more consistent from call to call.
946 	 */
947 
948 	request->message_type = DHCP_DROP;
949 
950 	ifnames_copy = strcpy(ifnames_head, ifnames);
951 	ifnames_copy = strtok(ifnames_copy, " ");
952 	for (; ifnames_copy != NULL; ifnames_copy = strtok(NULL, " ")) {
953 		(void) strlcpy(request->ifname, ifnames_copy, LIFNAMSIZ);
954 		if (dhcp_ipc_make_request(request, &reply, 0) == 0)
955 			free(reply);
956 	}
957 
958 	free(request);
959 	free(ifnames_head);
960 	free(ifnames);
961 	return (retval);
962 }
963 
964 /*
965  * dhcp_ipc_timed_read(): reads from a descriptor using a maximum timeout
966  *
967  *   input: int: the file descriptor to read from
968  *	    void *: the buffer to read into
969  *	    unsigned int: the total length of data to read
970  *	    int *: the number of milliseconds to wait; the number of
971  *		   milliseconds left are returned (-1 is "forever")
972  *  output: int: DHCP_IPC_SUCCESS on success, DHCP_IPC_E_* otherwise
973  */
974 
975 static int
976 dhcp_ipc_timed_read(int fd, void *buffer, unsigned int length, int *msec)
977 {
978 	unsigned int	n_total = 0;
979 	ssize_t		n_read;
980 	struct pollfd	pollfd;
981 	hrtime_t	start, end;
982 	int		retv;
983 
984 	pollfd.fd	= fd;
985 	pollfd.events	= POLLIN;
986 
987 	while (n_total < length) {
988 
989 		start = gethrtime();
990 
991 		retv = poll(&pollfd, 1, *msec);
992 		if (retv == 0) {
993 			/* This can happen only if *msec is not -1 */
994 			*msec = 0;
995 			return (DHCP_IPC_E_TIMEOUT);
996 		}
997 
998 		if (*msec != -1) {
999 			end = gethrtime();
1000 			*msec -= NSEC2MSEC(end - start);
1001 			if (*msec < 0)
1002 				*msec = 0;
1003 		}
1004 
1005 		if (retv == -1) {
1006 			if (errno != EINTR)
1007 				return (DHCP_IPC_E_POLL);
1008 			else if (*msec == 0)
1009 				return (DHCP_IPC_E_TIMEOUT);
1010 			continue;
1011 		}
1012 
1013 		if (!(pollfd.revents & POLLIN)) {
1014 			errno = EINVAL;
1015 			return (DHCP_IPC_E_POLL);
1016 		}
1017 
1018 		n_read = read(fd, (caddr_t)buffer + n_total, length - n_total);
1019 
1020 		if (n_read == -1) {
1021 			if (errno != EINTR)
1022 				return (DHCP_IPC_E_READ);
1023 			else if (*msec == 0)
1024 				return (DHCP_IPC_E_TIMEOUT);
1025 			continue;
1026 		}
1027 
1028 		if (n_read == 0) {
1029 			return (n_total == 0 ? DHCP_IPC_E_EOF :
1030 			    DHCP_IPC_E_PROTO);
1031 		}
1032 
1033 		n_total += n_read;
1034 
1035 		if (*msec == 0 && n_total < length)
1036 			return (DHCP_IPC_E_TIMEOUT);
1037 	}
1038 
1039 	return (DHCP_IPC_SUCCESS);
1040 }
1041