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