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