/*
* 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
*/
/*
*/
/*
* The core of ilbd daemon is a single-threaded event loop using
* event completion framework; it receives requests from client using
* the libilb functions, handles timeouts, initiates health checks, and
* populates the kernel state.
*
* The daemon has the following privileges (in addition to the basic ones):
*
* PRIV_PROC_OWNER, PRIV_NET_ICMPACCESS,
* PRIV_SYS_IP_CONFIG, PRIV_PROC_AUDIT
*
* The aforementioned privileges will be specified in the SMF manifest.
*
* AF_UNIX socket is used for IPC between libilb and this daemon as
* both processes will run on the same machine.
*
* To do health check, the daemon will create a timer for every health
* check probe. Each of these timers will be associated with the
* event port. When a timer goes off, the daemon will initiate a
* pipe to a separate process to execute the specific health check
* probe. This new process will run with the same user-id as that of
* ilbd daemon and will inherit all the privileges from the ilbd
* daemon parent process except the following:
*
* PRIV_PROC_OWNER, PRIV_PROC_AUDIT
*
* All health checks, will be implemented as external methods
* (binary or script). The following arguments will be passed
* to external methods:
*
* $1 VIP (literal IPv4 or IPv6 address)
* $2 Server IP (literal IPv4 or IPv6 address)
* $3 Protocol (UDP, TCP as a string)
* $4 The load balance mode, "DSR", "NAT", "HALF_NAT"
* $5 Numeric port range
* $6 maximum time (in seconds) the method
* should wait before returning failure. If the method runs for
* longer, it may be killed, and the test considered failed.
*
* Upon success, a health check method should print the RTT to the
* it finds to its STDOUT for ilbd to consume. The implicit unit
* is microseconds but only the number needs to be printed. If it
* cannot find the RTT, it should print 0. If the method decides
* that the server is dead, it should print -1 to its STDOUT.
*
* By default, an user-supplied health check probe process will
* also run with the same set of privileges as ILB's built-in
* probes. If the administrator has an user-supplied health check
* to implement setuid program.
*
* Each health check will have a timeout, such that if the health
* check process is hung, it will be killed after the timeout interval
* and the daemon will notify the kernel ILB engine of the server's
* unresponsiveness, so that load distribution can be appropriately
* adjusted. If on the other hand the health check is successful
* the timeout timer is cancelled.
*/
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <libgen.h>
#include <fcntl.h>
#include <stddef.h>
#include <signal.h>
#include <port.h>
#include <ctype.h>
#include <sys/resource.h>
#include <unistd.h>
#include <errno.h>
#include <ucred.h>
#include <priv_utils.h>
#include <libilb.h>
#include <assert.h>
#include <libintl.h>
#include <fcntl.h>
#include <rpcsvc/daemon_utils.h>
#include "libilb_impl.h"
#include "ilbd.h"
/*
* NOTE: The following needs to be kept up to date.
*/
#define ILBD_COPYRIGHT \
"All rights reserved.\n"
/*
* Global reply buffer to client request. Note that ilbd is single threaded,
* so a global buffer is OK. If ilbd becomes multi-threaded, this needs to
* be changed.
*/
static void
{
}
static void
ilbd_reset_kernel_state(void)
{
if (rc != ILB_STATUS_OK)
logdebug("ilbd_reset_kernel_state: do_ioctl failed: %s",
}
/* Signal handler to do clean up. */
/* ARGSUSED */
static void
{
(void) remove(SOCKET_PATH);
exit(0);
}
/*
* Create a socket and return it to caller. If there is a failure, this
* function calls exit(2). Hence it always returns a valid listener socket.
*
* Note that this function is called before ilbd becomes a daemon. So
* we call perror(3C) to print out error message directly so that SMF can
* catch them.
*/
static int
{
int s;
int sobufsz;
if (s == -1) {
perror("ilbd_create_client_socket: socket to"
" client failed");
}
perror("ilbd_create_client_socket: fcntl(FD_CLOEXEC)");
}
sizeof (sobufsz)) != 0) {
perror("ilbd_creat_client_socket: setsockopt(SO_SNDBUF) "
"failed");
}
sizeof (sobufsz)) != 0) {
perror("ilbd_creat_client_socket: setsockopt(SO_RCVBUF) "
"failed");
}
/*
* since everybody can talk to us, we need to open up permissions
* we check peer privileges on a per-operation basis.
* This is no security issue as long as we're single-threaded.
*/
/* just in case we didn't clean up properly after last exit */
(void) remove(SOCKET_PATH);
perror("ilbd_create_client_socket(): bind to client"
" socket failed");
}
/* re-instate old umask */
perror("ilbd_create_client_socket: listen to client"
" socket failed");
}
return (s);
}
/*
* Return the minimum size of a given request. The returned size does not
* include the variable part of a request.
*/
static size_t
{
case ILBD_RETRIEVE_SG_NAMES:
case ILBD_RETRIEVE_RULE_NAMES:
case ILBD_RETRIEVE_HC_NAMES:
case ILBD_CMD_OK:
break;
case ILBD_CMD_ERROR:
cmd_sz += sizeof (ilb_status_t);
break;
case ILBD_RETRIEVE_SG_HOSTS:
case ILBD_CREATE_SERVERGROUP:
case ILBD_DESTROY_SERVERGROUP:
case ILBD_DESTROY_RULE:
case ILBD_ENABLE_RULE:
case ILBD_DISABLE_RULE:
case ILBD_RETRIEVE_RULE:
case ILBD_DESTROY_HC:
case ILBD_GET_HC_INFO:
case ILBD_GET_HC_SRVS:
cmd_sz += sizeof (ilbd_name_t);
break;
case ILBD_ENABLE_SERVER:
case ILBD_DISABLE_SERVER:
case ILBD_ADD_SERVER_TO_GROUP:
cmd_sz += sizeof (ilb_sg_info_t);
break;
case ILBD_SRV_ADDR2ID:
case ILBD_SRV_ID2ADDR:
break;
case ILBD_CREATE_RULE:
cmd_sz += sizeof (ilb_rule_info_t);
break;
case ILBD_CREATE_HC:
cmd_sz += sizeof (ilb_hc_info_t);
break;
case ILBD_SHOW_NAT:
case ILBD_SHOW_PERSIST:
cmd_sz += sizeof (ilb_show_info_t);
break;
}
return (cmd_sz);
}
/*
* Given a request and its size, check that the size is big enough to
* contain the variable part of a request.
*/
static ilb_status_t
{
case ILBD_CREATE_SERVERGROUP:
case ILBD_ENABLE_SERVER:
case ILBD_DISABLE_SERVER:
case ILBD_ADD_SERVER_TO_GROUP:
sizeof (ilb_sg_srv_t)) {
}
break;
case ILBD_ENABLE_RULE:
case ILBD_DISABLE_RULE:
case ILBD_DESTROY_RULE:
sizeof (ilbd_name_t)) {
}
break;
}
return (rc);
}
/*
* being passed in (currently via the SOCK_SEQPACKET socket type).
*
* Note that the size of ip is at most ILBD_MSG_SIZE.
*/
static ilb_status_t
int ev_port)
{
/*
* cli_ev must be overridden during handling of individual commands,
* if there's a special need; otherwise, leave this for
* the "default" case
*/
/* Sanity check on the size of the static part of a request. */
goto out;
}
case ILBD_CREATE_SERVERGROUP: {
/*
* ilbd_create_sg() only needs the sg_name field. But it
* takes in a ilb_sg_info_t because it is used as a callback
* in ilbd_walk_sg_pgs().
*/
break;
}
case ILBD_DESTROY_SERVERGROUP:
break;
case ILBD_ADD_SERVER_TO_GROUP:
break;
break;
break;
break;
case ILBD_ENABLE_SERVER:
break;
break;
case ILBD_DISABLE_SERVER:
break;
break;
case ILBD_SRV_ADDR2ID:
if (rc == ILB_STATUS_OK)
break;
case ILBD_SRV_ID2ADDR:
if (rc == ILB_STATUS_OK)
break;
case ILBD_RETRIEVE_SG_HOSTS:
if (rc == ILB_STATUS_OK)
break;
case ILBD_RETRIEVE_SG_NAMES:
case ILBD_RETRIEVE_RULE_NAMES:
case ILBD_RETRIEVE_HC_NAMES:
if (rc == ILB_STATUS_OK)
break;
case ILBD_CREATE_RULE:
break;
case ILBD_DESTROY_RULE:
/* Copy the name to ensure that name is NULL terminated. */
break;
case ILBD_ENABLE_RULE:
break;
case ILBD_DISABLE_RULE:
break;
case ILBD_RETRIEVE_RULE:
if (rc == ILB_STATUS_OK)
break;
case ILBD_CREATE_HC:
break;
case ILBD_DESTROY_HC:
break;
case ILBD_GET_HC_INFO:
if (rc == ILB_STATUS_OK)
break;
case ILBD_GET_HC_SRVS:
if (rc == ILB_STATUS_OK)
break;
case ILBD_SHOW_NAT:
if (rc == ILB_STATUS_OK)
break;
case ILBD_SHOW_PERSIST:
if (rc == ILB_STATUS_OK)
break;
default:
logdebug("consume_common_struct: unknown command");
break;
}
out:
/*
* a transaction requires multiple exchanges, the client will send
* in multiple requests to get multiple responses. The show-nat and
* show-persist request are examples of this. The end of transaction
* is marked with ic_flags set to ILB_COMM_END.
*/
/* This is the standard reply. */
if (standard_reply) {
if (rc == ILB_STATUS_OK)
else
}
if (ret == -1) {
if (errno != EWOULDBLOCK) {
logdebug("consume_common_struct: send: %s",
goto err_out;
}
/*
* The reply is blocked, save the reply. handle_req()
* will associate the event port for the re-send.
*/
/*
* Set the error to ILB_STATUS_SEND so that
* handle_req() will free the client.
*/
logdebug("consume_common_struct: failure to "
"allocate memory to save reply");
goto err_out;
}
return (ILB_STATUS_EWOULDBLOCK);
}
}
return (rc);
}
/*
* Accept a new client request. A struct ilbd_client_t is allocated to
* store the client info. The accepted socket is port_associate() with
* the given port. And the allocated ilbd_client_t struct is passed as
* the user pointer.
*/
static void
{
int sa_len;
int new_sd;
int sflags;
int res;
/* don't log if we're out of file descriptors */
logperror("new_req: accept failed");
goto done;
}
/* Set the new socket to be non-blocking. */
logperror("new_req: fcntl(F_GETFL)");
goto clean_up;
}
logperror("new_req: fcntl(F_SETFL)");
goto clean_up;
}
logperror("new_req: fcntl(FD_CLOEXEC)");
goto clean_up;
}
logerr("new_req: malloc(ilbd_client_t)");
goto clean_up;
}
if (res == -1) {
logperror("new_req: getpeerucred failed");
goto clean_up;
}
logperror("new_req: ucred_getruid failed");
goto clean_up;
}
logerr("new_req: malloc(cli_pw_buf)");
goto clean_up;
}
logperror("new_req: invalid user");
goto clean_up;
}
cli->cli_saved_size = 0;
cli) == -1) {
logperror("new_req: port_associate(cli) failed");
}
}
done:
/* Re-associate the listener with the event port. */
ev_obj) == -1) {
logperror("new_req: port_associate(listener) failed");
exit(1);
}
}
static void
{
/* All request should be smaller than ILBD_MSG_SIZE */
union {
} ic_u;
ssize_t r;
if (event == ILBD_EVENT_REQ) {
/*
* Something is wrong with the client since there is a
* pending reply, the client should not send us another
* request. Kill this client.
*/
logerr("handle_req: misbehaving client, more than one "
"outstanding request");
goto err_out;
}
/*
* Our socket is message based so we should be able
* to get the request in one single read.
*/
if (r < 0) {
logperror("handle_req: read failed");
goto err_out;
}
/*
* If interrupted, just re-associate the cli_sd
* with the port.
*/
goto done;
}
if (rc == ILB_STATUS_EWOULDBLOCK)
goto blocked;
/* Fatal error communicating with client, free it. */
if (rc == ILB_STATUS_SEND)
goto err_out;
} else {
/*
* The reply to client was previously blocked, we will
* send again.
*/
if (errno != EWOULDBLOCK) {
logdebug("handle_req: send: %s",
goto err_out;
}
goto blocked;
}
cli->cli_saved_size = 0;
}
done:
/* Re-associate with the event port for more requests. */
logperror("handle_req: port_associate(POLLRDNORM)");
goto err_out;
}
return;
/* Re-associate with the event port. */
cli) == -1) {
logperror("handle_req: port_associate(POLLWRNORM)");
goto err_out;
}
return;
}
static void
{
}
/*
* main event loop for ilbd
* asserts that argument 'listener' is a server socket ready to accept() on.
*/
static void
{
ev_port = port_create();
if (ev_port == -1) {
logperror("main_loop: port_create failed");
exit(-1);
}
&ev_obj) == -1) {
logperror("main_loop: port_associate failed");
exit(1);
}
while (B_TRUE) {
int r;
if (r == -1) {
continue;
logperror("main_loop: port_get failed");
break;
}
switch (event) {
case ILBD_EVENT_TIMER:
break;
case ILBD_EVENT_PROBE:
break;
case ILBD_EVENT_NEW_REQ:
/*
* An error happens in the listener. Exit
* for now....
*/
logerr("main_loop: listener error");
exit(1);
}
break;
case ILBD_EVENT_REP_OK:
case ILBD_EVENT_REQ:
/*
* An error happens in the newly accepted
* client request. Clean up the client.
* this also happens when client closes socket,
* so not necessarily a reason for alarm
*/
break;
}
break;
default:
break;
}
}
}
static void
i_ilbd_setup_lists(void)
{
}
/*
* Usage message - call only during startup. it will print its
* message on stderr and exit
*/
static void
{
exit(1);
}
static void
{
exit(0);
}
/*
* Increase the file descriptor limit for handling a lot of health check
* processes (each requires a pipe).
*
* Note that this function is called before ilbd becomes a daemon. So
* we call perror(3C) to print out error message directly so that SMF
* can catch them.
*/
static void
set_rlim(void)
{
perror("ilbd: getrlimit");
}
perror("ilbd: setrlimit");
}
}
int
{
int s;
int c;
#if !defined(TEXT_DOMAIN)
#endif
(void) textdomain(TEXT_DOMAIN);
switch ((char)c) {
/* not reached */
break;
/* not reached */
break;
case 'd': ilbd_enable_debug();
break;
/* not reached */
break;
}
}
/*
* Whenever the daemon starts, it needs to start with a clean
* slate in the kernel. We need sys_ip_config privilege for
* this.
*/
/* Increase the limit on the number of file descriptors. */
set_rlim();
/*
* ilbd daemon starts off as root, just so it can create
* the daemon switches to "daemon" uid. This is similar to what
* rpcbind does.
*/
} else {
perror("main: mkdir failed");
}
/*
* Now lets switch ilbd as uid = daemon, gid = daemon with a
* trimmed down privilege set
*/
}
/*
* Opens a PF_UNIX socket to the client. No privilege needed
* for this.
*/
s = ilbd_create_client_socket();
/*
* Daemonify if ilbd is not running with -d option
* Need proc_fork privilege for this
*/
if (!is_debugging_on()) {
logdebug("daemonizing...");
if (daemon(0, 0) != 0) {
logperror("daemon failed");
}
}
/* if daemonified then set up syslog */
if (!is_debugging_on())
main_loop(s);
/*
* if we come here, then we experienced an error or a shutdown
* indicator, so clean up after ourselves.
*/
logdebug("main(): terminating");
(void) remove(SOCKET_PATH);
return (0);
}