xref: /titanic_51/usr/src/lib/libdhcpagent/common/dhcpagent_ipc.c (revision 1e49577a7fcde812700ded04431b49d67cc57d6d)
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