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