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