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