/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h> /* snprintf */
#include "dhcpagent_ipc.h"
#include "dhcpagent_util.h"
/*
* sends a dhcp_ipc_request_t message to the agent, and the agent
* sends a dhcp_ipc_reply_t back to the client. since the requests
* and replies can be variable-length, they are prefixed on "the wire"
* by a 32-bit number that tells the other end how many bytes to
* expect.
*
* the format of a request consists of a single dhcp_ipc_request_t;
* note that the length of this dhcp_ipc_request_t is variable (using
* the standard c array-of-size-1 trick). the type of the payload is
* given by `data_type', which is guaranteed to be `data_length' bytes
* long starting at `buffer'. note that `buffer' is guaranteed to be
* 32-bit aligned but it is poor taste to rely on this.
*
* the format of a reply is much the same: a single dhcp_ipc_reply_t;
* note again that the length of the dhcp_ipc_reply_t is variable.
* the type of the payload is given by `data_type', which is
* guaranteed to be `data_length' bytes long starting at `buffer'.
* once again, note that `buffer' is guaranteed to be 32-bit aligned
* but it is poor taste to rely on this.
*
* requests and replies can be paired up by comparing `ipc_id' fields.
*/
static int dhcp_ipc_timed_read(int, void *, unsigned int, int *);
static char *get_ifnames(int, int);
/* must be kept in sync with enum in dhcpagent_ipc.h */
static const char *ipc_typestr[] = {
"drop", "extend", "ping", "release", "start", "status",
"inform", "get_tag"
};
/*
* dhcp_ipc_alloc_request(): allocates a dhcp_ipc_request_t of the given type
* and interface, with a timeout of 0.
*
* input: dhcp_ipc_type_t: the type of ipc request to allocate
* const char *: the interface to associate the request with
* const void *: the payload to send with the message (NULL if none)
* uint32_t: the payload size (0 if none)
* dhcp_data_type_t: the description of the type of payload
* output: dhcp_ipc_request_t *: the request on success, NULL on failure
*/
{
return (NULL);
return (request);
}
/*
* dhcp_ipc_alloc_reply(): allocates a dhcp_ipc_reply_t
*
* input: dhcp_ipc_request_t *: the request the reply is for
* int: the return code (0 for success, DHCP_IPC_E_* otherwise)
* const void *: the payload to send with the message (NULL if none)
* uint32_t: the payload size (0 if none)
* dhcp_data_type_t: the description of the type of payload
* output: dhcp_ipc_reply_t *: the reply on success, NULL on failure
*/
{
return (NULL);
return (reply);
}
/*
* dhcp_ipc_get_data(): gets the data and data type from a dhcp_ipc_reply_t
*
* input: dhcp_ipc_reply_t *: the reply to get data from
* size_t *: the size of the resulting data
* dhcp_data_type_t *: the type of the message (returned)
* output: void *: a pointer to the data, if there is any.
*/
void *
{
*size = 0;
return (NULL);
}
}
/*
* dhcp_ipc_recv_msg(): gets a message using the agent's ipc protocol
*
* input: int: the file descriptor to get the message from
* void **: the address of a pointer to store the message
* (dynamically allocated)
* uint32_t: the minimum length of the packet
* int: the # of milliseconds to wait for the message (-1 is forever)
* output: int: DHCP_IPC_SUCCESS on success, DHCP_IPC_E_* otherwise
*/
static int
{
int retval;
if (retval != DHCP_IPC_SUCCESS)
return (retval);
if (length == 0)
return (DHCP_IPC_E_PROTO);
return (DHCP_IPC_E_MEMORY);
if (retval != DHCP_IPC_SUCCESS) {
return (retval);
}
if (length < base_length) {
return (DHCP_IPC_E_PROTO);
}
/*
* the data_length field is in the same place in either ipc message.
*/
return (DHCP_IPC_E_PROTO);
}
return (DHCP_IPC_SUCCESS);
}
/*
* dhcp_ipc_recv_request(): gets a request using the agent's ipc protocol
*
* input: int: the file descriptor to get the message from
* dhcp_ipc_request_t **: address of a pointer to store the request
* (dynamically allocated)
* int: the # of milliseconds to wait for the message (-1 is forever)
* output: int: 0 on success, DHCP_IPC_E_* otherwise
*/
int
{
int retval;
msec);
/* guarantee that ifname will be NUL-terminated */
if (retval == 0)
return (retval);
}
/*
* dhcp_ipc_recv_reply(): gets a reply using the agent's ipc protocol
*
* input: int: the file descriptor to get the message from
* dhcp_ipc_reply_t **: address of a pointer to store the reply
* (dynamically allocated)
* int32_t: timeout (in seconds), or DHCP_IPC_WAIT_FOREVER,
* or DHCP_IPC_WAIT_DEFAULT
* output: int: 0 on success, DHCP_IPC_E_* otherwise
*/
static int
{
/*
* If the caller doesn't want to wait forever, and the amount of time
* he wants to wait is expressible as an integer number of milliseconds
* (as needed by the msg function), then we wait that amount of time
* plus an extra two seconds for the daemon to do its work. The extra
* two seconds is arbitrary; it should allow plenty of time for the
* daemon to respond within the existing timeout, as specified in the
* original request, so the only time we give up is when the daemon is
* stopped or otherwise malfunctioning.
*
* Note that the wait limit (milliseconds in an 'int') is over 24 days,
* so it's unlikely that any request will actually be that long, and
* it's unlikely that anyone will care if we wait forever on a request
* for a 30 day timer. The point is to protect against daemon
* malfunction in the usual cases, not to provide an absolute command
* timer.
*/
if (timeout == DHCP_IPC_WAIT_DEFAULT)
else
timeout = -1;
timeout));
}
/*
* dhcp_ipc_send_msg(): transmits a message using the agent's ipc protocol
*
* input: int: the file descriptor to transmit on
* void *: the message to send
* uint32_t: the message length
* output: int: 0 on success, DHCP_IPC_E_* otherwise
*/
static int
{
return (DHCP_IPC_E_WRITEV);
return (0);
}
/*
* dhcp_ipc_send_reply(): transmits a reply using the agent's ipc protocol
*
* input: int: the file descriptor to transmit on
* dhcp_ipc_reply_t *: the reply to send
* output: int: 0 on success, DHCP_IPC_E_* otherwise
*/
int
{
reply->data_length));
}
/*
* dhcp_ipc_send_request(): transmits a request using the agent's ipc protocol
*
* input: int: the file descriptor to transmit on
* dhcp_ipc_request_t *: the request to send
* output: int: 0 on success, DHCP_IPC_E_* otherwise
*/
static int
{
/*
* for now, ipc_ids aren't really used, but they're intended
* to make it easy to send several requests and then collect
* all of the replies (and pair them with the requests).
*/
request->data_length));
}
/*
* dhcp_ipc_make_request(): sends the provided request to the agent and reaps
* the reply
*
* input: dhcp_ipc_request_t *: the request to make
* dhcp_ipc_reply_t **: the reply (dynamically allocated)
* int32_t: timeout (in seconds), or DHCP_IPC_WAIT_FOREVER,
* or DHCP_IPC_WAIT_DEFAULT
* output: int: 0 on success, DHCP_IPC_E_* otherwise
*/
int
{
if (fd == -1)
return (DHCP_IPC_E_SOCKET);
/*
* Bind a privileged port if we have sufficient privilege to do so.
* Continue as non-privileged otherwise.
*/
on = 1;
(void) dhcp_ipc_close(fd);
return (DHCP_IPC_E_BIND);
}
if (retval == -1) {
(void) dhcp_ipc_close(fd);
return (DHCP_IPC_E_CONNECT);
}
if (retval == 0)
(void) dhcp_ipc_close(fd);
return (retval);
}
/*
* dhcp_ipc_init(): initializes the ipc channel for use by the agent
*
* input: int *: the file descriptor to accept on (returned)
* output: int: 0 on success, DHCP_IPC_E_* otherwise
*/
int
{
if (*listen_fd == -1)
return (DHCP_IPC_E_SOCKET);
/*
* we use SO_REUSEADDR here since in the case where there
* really is another daemon running that is using the agent's
* port, bind(3N) will fail. so we can't lose.
*/
sizeof (on));
return (DHCP_IPC_E_BIND);
}
return (DHCP_IPC_E_LISTEN);
}
return (0);
}
/*
* dhcp_ipc_accept(): accepts an incoming connection for the agent
*
* input: int: the file descriptor to accept on
* int *: the accepted file descriptor (returned)
* int *: nonzero if the client is privileged (returned)
* output: int: 0 on success, DHCP_IPC_E_* otherwise
* note: sets the socket into nonblocking mode
*/
int
{
int sockflags;
/*
* if we were extremely concerned with portability, we would
* set the socket into nonblocking mode before doing the
* accept(3N), since on BSD-based networking stacks, there is
* a potential race that can occur if the socket which
* connected to us performs a TCP RST before we accept, since
* BSD handles this case entirely in the kernel and as a
* result even though select said we will not block, we can
* end up blocking since there is no longer a connection to
* accept. on SVR4-based systems, this should be okay,
* and we will get EPROTO back, even though POSIX.1g says
* we should get ECONNABORTED.
*/
if (*fd == -1)
return (DHCP_IPC_E_ACCEPT);
/* get credentials */
/*
* kick the socket into non-blocking mode so that later
* operations on the socket don't block and hold up the whole
* application. with the event demuxing approach, this may
* and to handle our internal protocol for passing data
* between the agent and its consumers, this is needed.
*/
return (DHCP_IPC_E_FCNTL);
}
return (DHCP_IPC_E_FCNTL);
}
return (0);
}
/*
* dhcp_ipc_close(): closes an ipc descriptor
*
* input: int: the file descriptor to close
* output: int: 0 on success, DHCP_IPC_E_* otherwise
*/
int
{
}
/*
* dhcp_ipc_strerror(): maps an ipc error code into a human-readable string
*
* input: int: the ipc error code to map
* output: const char *: the corresponding human-readable string
*/
const char *
{
/* note: this must be kept in sync with DHCP_IPC_E_* definitions */
const char *syscalls[] = {
"<unknown>", "socket", "fcntl", "read", "accept", "close",
"bind", "listen", "malloc", "connect", "writev", "poll"
};
const char *error_string;
switch (error) {
/*
* none of these errors actually go over the wire.
* hence, we assume that errno is still fresh.
*/
case DHCP_IPC_E_SOCKET: /* FALLTHRU */
case DHCP_IPC_E_FCNTL: /* FALLTHRU */
case DHCP_IPC_E_READ: /* FALLTHRU */
case DHCP_IPC_E_ACCEPT: /* FALLTHRU */
case DHCP_IPC_E_CLOSE: /* FALLTHRU */
case DHCP_IPC_E_BIND: /* FALLTHRU */
case DHCP_IPC_E_LISTEN: /* FALLTHRU */
case DHCP_IPC_E_CONNECT: /* FALLTHRU */
case DHCP_IPC_E_WRITEV: /* FALLTHRU */
case DHCP_IPC_E_POLL:
if (error_string == NULL)
error_string = "unknown error";
break;
case DHCP_IPC_E_MEMORY:
error_string = "out of memory";
break;
case DHCP_IPC_E_TIMEOUT:
error_string = "wait timed out, operation still pending...";
break;
case DHCP_IPC_E_INVIF:
error_string = "interface does not exist or cannot be managed "
"using DHCP";
break;
case DHCP_IPC_E_INT:
error_string = "internal error (might work later)";
break;
case DHCP_IPC_E_PERM:
error_string = "permission denied";
break;
case DHCP_IPC_E_OUTSTATE:
error_string = "interface not in appropriate state for command";
break;
case DHCP_IPC_E_PEND:
error_string = "interface currently has a pending command "
"(try later)";
break;
case DHCP_IPC_E_BOOTP:
error_string = "interface is administered with BOOTP, not DHCP";
break;
case DHCP_IPC_E_CMD_UNKNOWN:
error_string = "unknown command";
break;
case DHCP_IPC_E_UNKIF:
error_string = "interface is not under DHCP control";
break;
case DHCP_IPC_E_PROTO:
error_string = "ipc protocol violation";
break;
case DHCP_IPC_E_FAILEDIF:
error_string = "interface is in a FAILED state and must be "
"manually restarted";
break;
case DHCP_IPC_E_NOPRIMARY:
error_string = "primary interface requested but no primary "
"interface is set";
break;
case DHCP_IPC_E_NOIPIF:
error_string = "interface currently has no IP address";
break;
case DHCP_IPC_E_DOWNIF:
error_string = "interface is currently down";
break;
case DHCP_IPC_E_NOVALUE:
error_string = "no value was found for this option";
break;
case DHCP_IPC_E_RUNNING:
error_string = "DHCP is already running";
break;
case DHCP_IPC_E_SRVFAILED:
error_string = "DHCP server refused request";
break;
case DHCP_IPC_E_EOF:
error_string = "ipc connection closed";
break;
default:
error_string = "unknown error";
break;
}
/*
* TODO: internationalize this error string
*/
return (error_string);
}
/*
* dhcp_string_to_request(): maps a string into a request code
*
* input: const char *: the string to map
* output: dhcp_ipc_type_t: the request code, or -1 if unknown
*/
{
unsigned int i;
for (i = 0; i < DHCP_NIPC; i++)
return ((dhcp_ipc_type_t)i);
return ((dhcp_ipc_type_t)-1);
}
/*
* dhcp_ipc_type_to_string(): maps an ipc command code into a human-readable
* string
*
* input: int: the ipc command code to map
* output: const char *: the corresponding human-readable string
*/
const char *
{
return ("unknown");
else
return (ipc_typestr[(int)type]);
}
/*
* getinfo_ifnames(): checks the value of a specified option on a list of
* interface names.
* input: const char *: a list of interface names to query (in order) for
* the option; "" queries the primary interface
* dhcp_optnum_t *: a description of the desired option
* DHCP_OPT **: filled in with the (dynamically allocated) value of
* the option upon success.
* output: int: DHCP_IPC_E_* on error, 0 on success or if no value was
* found but no error occurred either (*result will be NULL)
*/
static int
{
int retval = 0;
return (DHCP_IPC_E_MEMORY);
sizeof (dhcp_optnum_t), DHCP_TYPE_OPTNUM);
return (DHCP_IPC_E_MEMORY);
}
ifnames = "";
if (retval != 0)
break;
if (reply->return_code == 0) {
else
break;
}
}
if (ifnames[0] == '\0')
break;
}
return (retval);
}
/*
* get_ifnames(): returns a space-separated list of interface names that
* match the specified flags
*
* input: int: flags which must be on in each interface returned
* int: flags which must be off in each interface returned
* output: char *: a dynamically-allocated list of interface names, or
* NULL upon failure.
*/
static char *
{
char *ifnames;
if (sock_fd == -1)
return (NULL);
return (NULL);
}
return (NULL);
}
for (i = 0; i < n_ifs; i++) {
continue;
}
}
return (ifnames);
}
/*
* dhcp_ipc_getinfo(): attempts to retrieve a value for the specified DHCP
* option; tries primary interface, then all DHCP-owned
* interfaces, then INFORMs on the remaining interfaces
* (these interfaces are dropped prior to returning).
* input: dhcp_optnum_t *: a description of the desired option
* DHCP_OPT **: filled in with the (dynamically allocated) value of
* the option upon success.
* int32_t: timeout (in seconds), or DHCP_IPC_WAIT_FOREVER,
* or DHCP_IPC_WAIT_DEFAULT.
* output: int: DHCP_IPC_E_* on error, 0 upon success.
*/
int
{
int retval;
if (timeout == DHCP_IPC_WAIT_DEFAULT)
/*
* wait at most 5 seconds for the agent to start.
*/
return (DHCP_IPC_E_INT);
/*
* check the primary interface for the option value first.
*/
return (retval);
/*
* no luck. get a list of the interfaces under DHCP control
* and perform a GET_TAG on each one.
*/
return (retval);
}
}
/*
* still no luck. retrieve a list of all interfaces on the
* system that could use DHCP but aren't. send INFORMs out on
* each one. after that, sit in a loop for the next `timeout'
* seconds, trying every second to see if a response for the
* option we want has come in on one of the interfaces.
*/
return (DHCP_IPC_E_NOVALUE);
}
if (ifnames_copy == NULL) {
return (DHCP_IPC_E_MEMORY);
}
return (DHCP_IPC_E_MEMORY);
}
}
for (;;) {
if ((timeout != DHCP_IPC_WAIT_FOREVER) &&
break;
}
break;
(void) sleep(1);
}
/*
* drop any interfaces that weren't under DHCP control before
* we got here; this keeps this function more of a black box
* and the behavior more consistent from call to call.
*/
}
return (retval);
}
/*
* dhcp_ipc_timed_read(): reads from a descriptor using a maximum timeout
*
* input: int: the file descriptor to read from
* void *: the buffer to read into
* unsigned int: the total length of data to read
* int *: the number of milliseconds to wait; the number of
* milliseconds left are returned (-1 is "forever")
* output: int: DHCP_IPC_SUCCESS on success, DHCP_IPC_E_* otherwise
*/
static int
{
unsigned int n_total = 0;
int retv;
if (retv == 0) {
/* This can happen only if *msec is not -1 */
*msec = 0;
return (DHCP_IPC_E_TIMEOUT);
}
if (*msec != -1) {
if (*msec < 0)
*msec = 0;
}
if (retv == -1) {
return (DHCP_IPC_E_POLL);
else if (*msec == 0)
return (DHCP_IPC_E_TIMEOUT);
continue;
}
return (DHCP_IPC_E_POLL);
}
if (n_read == -1) {
return (DHCP_IPC_E_READ);
else if (*msec == 0)
return (DHCP_IPC_E_TIMEOUT);
continue;
}
if (n_read == 0) {
return (n_total == 0 ? DHCP_IPC_E_EOF :
}
return (DHCP_IPC_E_TIMEOUT);
}
return (DHCP_IPC_SUCCESS);
}