#include <stdlib.h>
#include <strings.h>
#include <stddef.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/list.h>
#include <net/if.h>
#include <assert.h>
#include <errno.h>
#include <libintl.h>
#include <libilb.h>
#include <inet/ilb.h>
#include "libilb_impl.h"
#include "ilbd.h"
#define AF_2_IPPROTO(_af) (_af == AF_INET)?IPPROTO_IP:IPPROTO_IPV6
#define IPPROTO_2_AF(_i) (_i == IPPROTO_IP)?AF_INET:AF_INET6
#define PROTOCOL_LEN 16
#define ADDR_LEN (2 * INET6_ADDRSTRLEN + 1)
#define PORT_LEN 6
static ilb_status_t ilbd_disable_one_rule(ilbd_rule_t *, boolean_t);
static uint32_t i_flags_d2k(int);
#define ILB_SGSRV_2_KSRV(s, k) \
(k)->addr = (s)->sgs_addr; \
(k)->min_port = (s)->sgs_minport; \
(k)->max_port = (s)->sgs_maxport; \
(k)->flags = i_flags_d2k((s)->sgs_flags); \
(k)->err = 0; \
(void) strlcpy((k)->name, (s)->sgs_srvID, sizeof ((k)->name))
list_t ilbd_rule_hlist;
static ilb_algo_t
algo_impl2lib(ilb_algo_impl_t a)
{
switch (a) {
case ILB_ALG_IMPL_ROUNDROBIN:
return (ILB_ALG_ROUNDROBIN);
case ILB_ALG_IMPL_HASH_IP:
return (ILB_ALG_HASH_IP);
case ILB_ALG_IMPL_HASH_IP_SPORT:
return (ILB_ALG_HASH_IP_SPORT);
case ILB_ALG_IMPL_HASH_IP_VIP:
return (ILB_ALG_HASH_IP_VIP);
}
return (0);
}
static ilb_topo_t
topo_impl2lib(ilb_topo_impl_t t)
{
switch (t) {
case ILB_TOPO_IMPL_DSR:
return (ILB_TOPO_DSR);
case ILB_TOPO_IMPL_NAT:
return (ILB_TOPO_NAT);
case ILB_TOPO_IMPL_HALF_NAT:
return (ILB_TOPO_HALF_NAT);
}
return (0);
}
ilb_algo_impl_t
algo_lib2impl(ilb_algo_t a)
{
switch (a) {
case ILB_ALG_ROUNDROBIN:
return (ILB_ALG_IMPL_ROUNDROBIN);
case ILB_ALG_HASH_IP:
return (ILB_ALG_IMPL_HASH_IP);
case ILB_ALG_HASH_IP_SPORT:
return (ILB_ALG_IMPL_HASH_IP_SPORT);
case ILB_ALG_HASH_IP_VIP:
return (ILB_ALG_IMPL_HASH_IP_VIP);
}
return (0);
}
ilb_topo_impl_t
topo_lib2impl(ilb_topo_t t)
{
switch (t) {
case ILB_TOPO_DSR:
return (ILB_TOPO_IMPL_DSR);
case ILB_TOPO_NAT:
return (ILB_TOPO_IMPL_NAT);
case ILB_TOPO_HALF_NAT:
return (ILB_TOPO_IMPL_HALF_NAT);
}
return (0);
}
ilb_status_t
i_check_srv2rules(list_t *rlist, ilb_sg_srv_t *srv)
{
ilb_status_t rc = ILB_STATUS_OK;
ilbd_rule_t *rl;
int server_portrange, rule_portrange;
int srv_minport, srv_maxport;
int r_minport, r_maxport;
if (srv == NULL)
return (ILB_STATUS_OK);
srv_minport = ntohs(srv->sgs_minport);
srv_maxport = ntohs(srv->sgs_maxport);
for (rl = list_head(rlist); rl != NULL; rl = list_next(rlist, rl)) {
r_minport = ntohs(rl->irl_minport);
r_maxport = ntohs(rl->irl_maxport);
if ((srv_minport != 0) && (srv_minport == srv_maxport)) {
if (rl->irl_topo == ILB_TOPO_DSR) {
if (r_maxport > r_minport) {
rc = ILB_STATUS_INVAL_SRVR;
break;
} else if (srv_minport != r_minport) {
rc = ILB_STATUS_BADPORT;
break;
}
}
if (rl->irl_hcpflag == ILB_HCI_PROBE_FIX &&
rl->irl_hcport != srv_minport) {
rc = ILB_STATUS_BADPORT;
break;
}
} else if (srv_maxport > srv_minport) {
if ((rl->irl_topo == ILB_TOPO_DSR) &&
(r_maxport > r_minport)) {
if ((r_minport != srv_minport) ||
(r_maxport != srv_maxport)) {
rc = ILB_STATUS_BADPORT;
break;
}
} else if ((rl->irl_topo == ILB_TOPO_DSR) &&
(r_maxport == r_minport)) {
rc = ILB_STATUS_INVAL_SRVR;
break;
} else if (((rl->irl_topo == ILB_TOPO_NAT) ||
(rl->irl_topo == ILB_TOPO_HALF_NAT)) &&
(r_maxport > r_minport)) {
server_portrange = srv_maxport - srv_minport;
rule_portrange = r_maxport - r_minport;
if (rule_portrange != server_portrange) {
rc = ILB_STATUS_INVAL_SRVR;
break;
}
}
if (rl->irl_hcpflag == ILB_HCI_PROBE_FIX &&
(rl->irl_hcport > srv_maxport ||
rl->irl_hcport < srv_minport)) {
rc = ILB_STATUS_BADPORT;
break;
}
}
}
return (rc);
}
void
i_setup_rule_hlist(void)
{
list_create(&ilbd_rule_hlist, sizeof (ilbd_rule_t),
offsetof(ilbd_rule_t, irl_link));
}
ilb_status_t
i_ilbd_save_rule(ilbd_rule_t *irl, ilbd_scf_cmd_t scf_cmd)
{
boolean_t enable = irl->irl_flags & ILB_FLAGS_RULE_ENABLED;
switch (scf_cmd) {
case ILBD_SCF_CREATE:
return (ilbd_create_pg(ILBD_SCF_RULE, (void *)irl));
case ILBD_SCF_DESTROY:
return (ilbd_destroy_pg(ILBD_SCF_RULE, irl->irl_name));
case ILBD_SCF_ENABLE_DISABLE:
return (ilbd_change_prop(ILBD_SCF_RULE, irl->irl_name,
"status", &enable));
default:
logdebug("i_ilbd_save_rule: invalid scf cmd %d", scf_cmd);
return (ILB_STATUS_INVAL_CMD);
}
}
static ilbd_rule_t *
i_alloc_ilbd_rule(ilb_rule_info_t *r)
{
ilbd_rule_t *rl;
rl = calloc(sizeof (*rl), 1);
if (rl != NULL && r != NULL)
bcopy(r, &rl->irl_info, sizeof (*r));
return (rl);
}
static ilbd_rule_t *
i_find_rule_byname(const char *name)
{
ilbd_rule_t *rl;
rl = list_head(&ilbd_rule_hlist);
while (rl != NULL &&
strncmp(rl->irl_name, name, sizeof (rl->irl_name)) != 0) {
rl = list_next(&ilbd_rule_hlist, rl);
}
return (rl);
}
static ilb_status_t
ilb_get_krule(ilb_rule_info_t *rl)
{
ilb_status_t rc;
ilb_rule_cmd_t kcmd;
kcmd.cmd = ILB_LIST_RULE;
(void) strlcpy(kcmd.name, rl->rl_name, sizeof (kcmd.name));
kcmd.flags = 0;
rc = do_ioctl(&kcmd, 0);
if (rc != ILB_STATUS_OK)
return (rc);
rl->rl_flags = kcmd.flags;
rl->rl_ipversion = IPPROTO_2_AF(kcmd.ip_ver);
rl->rl_vip = kcmd.vip;
rl->rl_proto = kcmd.proto;
rl->rl_minport = kcmd.min_port;
rl->rl_maxport = kcmd.max_port;
rl->rl_algo = algo_impl2lib(kcmd.algo);
rl->rl_topo = topo_impl2lib(kcmd.topo);
rl->rl_stickymask = kcmd.sticky_mask;
rl->rl_nat_src_start = kcmd.nat_src_start;
rl->rl_nat_src_end = kcmd.nat_src_end;
(void) strlcpy(rl->rl_name, kcmd.name, sizeof (rl->rl_name));
rl->rl_conndrain = kcmd.conn_drain_timeout;
rl->rl_nat_timeout = kcmd.nat_expiry;
rl->rl_sticky_timeout = kcmd.sticky_expiry;
return (ILB_STATUS_OK);
}
ilb_status_t
ilbd_retrieve_rule(ilbd_name_t rl_name, uint32_t *rbuf, size_t *rbufsz)
{
ilbd_rule_t *irl = NULL;
ilb_status_t rc;
ilb_rule_info_t *rinfo;
irl = i_find_rule_byname(rl_name);
if (irl == NULL)
return (ILB_STATUS_ENOENT);
ilbd_reply_ok(rbuf, rbufsz);
rinfo = (ilb_rule_info_t *)&((ilb_comm_t *)rbuf)->ic_data;
bcopy(&irl->irl_info, rinfo, sizeof (*rinfo));
if (rinfo->rl_conndrain == 0 || rinfo->rl_nat_timeout == 0 ||
rinfo->rl_sticky_timeout == 0) {
ilb_rule_info_t tmp_info;
(void) strcpy(tmp_info.rl_name, rinfo->rl_name);
rc = ilb_get_krule(&tmp_info);
if (rc != ILB_STATUS_OK)
return (rc);
if (rinfo->rl_conndrain == 0)
rinfo->rl_conndrain = tmp_info.rl_conndrain;
if ((rinfo->rl_topo == ILB_TOPO_NAT ||
rinfo->rl_topo == ILB_TOPO_HALF_NAT) &&
rinfo->rl_nat_timeout == 0) {
rinfo->rl_nat_timeout = tmp_info.rl_nat_timeout;
}
if ((rinfo->rl_flags & ILB_FLAGS_RULE_STICKY) &&
rinfo->rl_sticky_timeout == 0) {
rinfo->rl_sticky_timeout = tmp_info.rl_sticky_timeout;
}
}
*rbufsz += sizeof (ilb_rule_info_t);
return (ILB_STATUS_OK);
}
static ilb_status_t
ilbd_destroy_one_rule(ilbd_rule_t *irl)
{
ilb_status_t rc;
ilb_name_cmd_t kcmd;
if ((irl->irl_flags & ILB_FLAGS_RULE_ALLRULES) == 0) {
kcmd.cmd = ILB_DESTROY_RULE;
(void) strlcpy(kcmd.name, irl->irl_name, sizeof (kcmd.name));
kcmd.flags = 0;
rc = do_ioctl(&kcmd, 0);
if (rc != ILB_STATUS_OK)
return (rc);
}
list_remove(&irl->irl_sg->isg_rulelist, irl);
list_remove(&ilbd_rule_hlist, irl);
if (RULE_HAS_HC(irl) && (rc = ilbd_hc_dissociate_rule(irl)) !=
ILB_STATUS_OK) {
logerr("ilbd_destroy_one_rule: cannot "
"dissociate %s from hc object %s: %d",
irl->irl_name, irl->irl_hcname, rc);
}
rc = i_ilbd_save_rule(irl, ILBD_SCF_DESTROY);
if (rc != ILB_STATUS_OK)
logdebug("ilbd_destroy_rule: save rule failed");
free(irl);
return (rc);
}
static ilb_status_t
ilbd_enable_one_rule(ilbd_rule_t *irl, boolean_t is_rollback)
{
ilb_status_t rc = ILB_STATUS_OK;
ilb_name_cmd_t kcmd;
if ((irl->irl_flags & ILB_FLAGS_RULE_ENABLED) != 0)
return (ILB_STATUS_OK);
irl->irl_flags |= ILB_FLAGS_RULE_ENABLED;
if ((irl->irl_flags & ILB_FLAGS_RULE_ALLRULES) == 0) {
kcmd.cmd = ILB_ENABLE_RULE;
(void) strlcpy(kcmd.name, irl->irl_name, sizeof (kcmd.name));
kcmd.flags = 0;
rc = do_ioctl(&kcmd, 0);
if (rc != ILB_STATUS_OK)
return (rc);
}
if (RULE_HAS_HC(irl) && (rc = ilbd_hc_enable_rule(irl)) !=
ILB_STATUS_OK) {
kcmd.cmd = ILB_DISABLE_RULE;
(void) do_ioctl(&kcmd, 0);
return (rc);
}
if (!is_rollback) {
if (rc == ILB_STATUS_OK)
rc = i_ilbd_save_rule(irl, ILBD_SCF_ENABLE_DISABLE);
if (rc != ILB_STATUS_OK)
(void) ilbd_disable_one_rule(irl, B_TRUE);
}
return (rc);
}
static ilb_status_t
ilbd_disable_one_rule(ilbd_rule_t *irl, boolean_t is_rollback)
{
ilb_status_t rc = ILB_STATUS_OK;
ilb_name_cmd_t kcmd;
if ((irl->irl_flags & ILB_FLAGS_RULE_ENABLED) == 0)
return (ILB_STATUS_OK);
irl->irl_flags &= ~ILB_FLAGS_RULE_ENABLED;
if ((irl->irl_flags & ILB_FLAGS_RULE_ALLRULES) == 0) {
kcmd.cmd = ILB_DISABLE_RULE;
(void) strlcpy(kcmd.name, irl->irl_name, sizeof (kcmd.name));
kcmd.flags = 0;
rc = do_ioctl(&kcmd, 0);
if (rc != ILB_STATUS_OK)
return (rc);
}
if (RULE_HAS_HC(irl) && (rc = ilbd_hc_disable_rule(irl)) !=
ILB_STATUS_OK) {
kcmd.cmd = ILB_ENABLE_RULE;
(void) do_ioctl(&kcmd, 0);
return (rc);
}
if (!is_rollback) {
if (rc == ILB_STATUS_OK)
rc = i_ilbd_save_rule(irl, ILBD_SCF_ENABLE_DISABLE);
if (rc != ILB_STATUS_OK)
(void) ilbd_enable_one_rule(irl, B_TRUE);
}
return (rc);
}
static void
ilbd_audit_rule_event(const char *audit_rule_name,
ilb_rule_info_t *rlinfo, ilbd_cmd_t cmd, ilb_status_t rc,
ucred_t *ucredp)
{
adt_session_data_t *ah;
adt_event_data_t *event;
au_event_t flag;
int scf_val_len = ILBD_MAX_VALUE_LEN;
char *aobuf = NULL;
char *valstr1 = NULL;
char *valstr2 = NULL;
char pbuf[PROTOCOL_LEN];
char hcpbuf[PORT_LEN];
int audit_error;
if ((ucredp == NULL) && (cmd == ILBD_CREATE_RULE)) {
return;
}
if (adt_start_session(&ah, NULL, 0) != 0) {
logerr("ilbd_audit_rule_event: adt_start_session failed");
exit(EXIT_FAILURE);
}
if (adt_set_from_ucred(ah, ucredp, ADT_NEW) != 0) {
(void) adt_end_session(ah);
logerr("ilbd_audit_rule_event: adt_set_from_ucred failed");
exit(EXIT_FAILURE);
}
if (cmd == ILBD_ENABLE_RULE)
flag = ADT_ilb_enable_rule;
else if (cmd == ILBD_DISABLE_RULE)
flag = ADT_ilb_disable_rule;
else if (cmd == ILBD_DESTROY_RULE)
flag = ADT_ilb_delete_rule;
else if (cmd == ILBD_CREATE_RULE)
flag = ADT_ilb_create_rule;
if ((event = adt_alloc_event(ah, flag)) == NULL) {
logerr("ilbd_audit_rule_event: adt_alloc_event failed");
exit(EXIT_FAILURE);
}
(void) memset((char *)event, 0, sizeof (adt_event_data_t));
switch (cmd) {
case ILBD_DESTROY_RULE:
event->adt_ilb_delete_rule.auth_used = NET_ILB_CONFIG_AUTH;
event->adt_ilb_delete_rule.rule_name = (char *)audit_rule_name;
break;
case ILBD_ENABLE_RULE:
event->adt_ilb_enable_rule.auth_used = NET_ILB_ENABLE_AUTH;
event->adt_ilb_enable_rule.rule_name = (char *)audit_rule_name;
break;
case ILBD_DISABLE_RULE:
event->adt_ilb_disable_rule.auth_used = NET_ILB_ENABLE_AUTH;
event->adt_ilb_disable_rule.rule_name = (char *)audit_rule_name;
break;
case ILBD_CREATE_RULE:
if (((aobuf = malloc(scf_val_len)) == NULL) ||
((valstr1 = malloc(scf_val_len)) == NULL) ||
((valstr2 = malloc(scf_val_len)) == NULL)) {
logerr("ilbd_audit_rule_event: could not"
" allocate buffer");
exit(EXIT_FAILURE);
}
event->adt_ilb_create_rule.auth_used = NET_ILB_CONFIG_AUTH;
if (IN6_IS_ADDR_V4MAPPED(&rlinfo->rl_vip)) {
event->adt_ilb_create_rule.virtual_ipaddress_type =
ADT_IPv4;
cvt_addr(event->adt_ilb_create_rule.virtual_ipaddress,
ADT_IPv4, rlinfo->rl_vip);
} else {
event->adt_ilb_create_rule.virtual_ipaddress_type =
ADT_IPv6;
cvt_addr(event->adt_ilb_create_rule.virtual_ipaddress,
ADT_IPv6, rlinfo->rl_vip);
}
event->adt_ilb_create_rule.min_port = ntohs(rlinfo->rl_minport);
if (ntohs(rlinfo->rl_maxport) > ntohs(rlinfo->rl_minport)) {
event->adt_ilb_create_rule.max_port =
ntohs(rlinfo->rl_maxport);
} else {
event->adt_ilb_create_rule.max_port =
ntohs(rlinfo->rl_minport);
}
if (rlinfo->rl_proto == IPPROTO_UDP)
(void) snprintf(pbuf, PROTOCOL_LEN, "UDP");
else
(void) snprintf(pbuf, PROTOCOL_LEN, "TCP");
event->adt_ilb_create_rule.protocol = pbuf;
ilbd_algo_to_str(rlinfo->rl_algo, valstr1);
ilbd_topo_to_str(rlinfo->rl_topo, valstr2);
(void) snprintf(aobuf, scf_val_len, "%s:%s",
valstr1, valstr2);
event->adt_ilb_create_rule.algo_optype = aobuf;
if (rlinfo->rl_topo == ILB_TOPO_NAT) {
if (IN6_IS_ADDR_V4MAPPED(&rlinfo->rl_nat_src_start)) {
event->adt_ilb_create_rule.proxy_src_min_type =
ADT_IPv4;
cvt_addr(
event->adt_ilb_create_rule.proxy_src_min,
ADT_IPv4, rlinfo->rl_nat_src_start);
} else {
event->adt_ilb_create_rule.proxy_src_min_type =
ADT_IPv6;
cvt_addr(
event->adt_ilb_create_rule.proxy_src_min,
ADT_IPv6, rlinfo->rl_nat_src_start);
}
if (IN6_IS_ADDR_UNSPECIFIED(&rlinfo->rl_nat_src_end)) {
event->adt_ilb_create_rule.proxy_src_max_type =
event->
adt_ilb_create_rule.proxy_src_min_type;
(void) memcpy(
event->adt_ilb_create_rule.proxy_src_max,
event->adt_ilb_create_rule.proxy_src_min,
(4 * sizeof (uint32_t)));
} else if (
IN6_IS_ADDR_V4MAPPED(&rlinfo->rl_nat_src_end)) {
event->adt_ilb_create_rule.proxy_src_max_type =
ADT_IPv4;
cvt_addr(
event->adt_ilb_create_rule.proxy_src_max,
ADT_IPv4, rlinfo->rl_nat_src_end);
} else {
event->adt_ilb_create_rule.proxy_src_max_type =
ADT_IPv6;
cvt_addr(
event->adt_ilb_create_rule.proxy_src_max,
ADT_IPv6, rlinfo->rl_nat_src_end);
}
}
valstr1[0] = '\0';
ilbd_ip_to_str(rlinfo->rl_ipversion, &rlinfo->rl_stickymask,
valstr1);
event->adt_ilb_create_rule.persist_mask = valstr1;
if (rlinfo->rl_hcname[0] != '\0')
event->adt_ilb_create_rule.hcname = rlinfo->rl_hcname;
if (rlinfo->rl_hcpflag == ILB_HCI_PROBE_FIX) {
(void) snprintf(hcpbuf, PORT_LEN, "%d",
rlinfo->rl_hcport);
event->adt_ilb_create_rule.hcport = hcpbuf;
} else if (rlinfo->rl_hcpflag == ILB_HCI_PROBE_ANY) {
(void) snprintf(hcpbuf, PORT_LEN, "ANY");
event->adt_ilb_create_rule.hcport = hcpbuf;
}
event->adt_ilb_create_rule.conndrain_timeout =
rlinfo->rl_conndrain;
event->adt_ilb_create_rule.nat_timeout =
rlinfo->rl_nat_timeout;
event->adt_ilb_create_rule.persist_timeout =
rlinfo->rl_sticky_timeout;
event->adt_ilb_create_rule.server_group = rlinfo->rl_sgname;
event->adt_ilb_create_rule.rule_name = rlinfo->rl_name;
break;
}
if (rc == ILB_STATUS_OK) {
if (adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS) != 0) {
logerr("ilbd_audit_rule_event:adt_put_event failed");
exit(EXIT_FAILURE);
}
} else {
audit_error = ilberror2auditerror(rc);
if (adt_put_event(event, ADT_FAILURE, audit_error) != 0) {
logerr("ilbd_audit_rule_event: adt_put_event failed");
exit(EXIT_FAILURE);
}
}
adt_free_event(event);
free(aobuf);
free(valstr1);
free(valstr2);
(void) adt_end_session(ah);
}
void
cvt_addr(uint32_t *audit, int32_t type, struct in6_addr address)
{
if (type == ADT_IPv4) {
audit[0] = address._S6_un._S6_u32[3];
} else {
(void) memcpy(audit, address._S6_un._S6_u32,
(4 * sizeof (uint32_t)));
}
}
static ilb_status_t
i_ilbd_action_switch(ilbd_rule_t *irl, ilbd_cmd_t cmd,
boolean_t is_rollback, ucred_t *ucredp)
{
ilb_status_t rc;
switch (cmd) {
case ILBD_DESTROY_RULE:
rc = ilbd_destroy_one_rule(irl);
if (!is_rollback) {
ilbd_audit_rule_event(irl->irl_name, NULL,
cmd, rc, ucredp);
}
return (rc);
case ILBD_ENABLE_RULE:
rc = ilbd_enable_one_rule(irl, is_rollback);
if (!is_rollback) {
ilbd_audit_rule_event(irl->irl_name, NULL, cmd,
rc, ucredp);
}
return (rc);
case ILBD_DISABLE_RULE:
rc = ilbd_disable_one_rule(irl, is_rollback);
if (!is_rollback) {
ilbd_audit_rule_event(irl->irl_name, NULL, cmd,
rc, ucredp);
}
return (rc);
}
return (ILB_STATUS_INVAL_CMD);
}
static ilb_cmd_t
i_ilbd2ilb_cmd(ilbd_cmd_t c)
{
ilb_cmd_t r;
switch (c) {
case ILBD_CREATE_RULE:
r = ILB_CREATE_RULE;
break;
case ILBD_DESTROY_RULE:
r = ILB_DESTROY_RULE;
break;
case ILBD_ENABLE_RULE:
r = ILB_ENABLE_RULE;
break;
case ILBD_DISABLE_RULE:
r = ILB_DISABLE_RULE;
break;
}
return (r);
}
static ilbd_cmd_t
get_undo_cmd(ilbd_cmd_t cmd)
{
ilbd_cmd_t u_cmd;
switch (cmd) {
case ILBD_DESTROY_RULE:
u_cmd = ILBD_BAD_CMD;
break;
case ILBD_ENABLE_RULE:
u_cmd = ILBD_DISABLE_RULE;
break;
case ILBD_DISABLE_RULE:
u_cmd = ILBD_ENABLE_RULE;
break;
}
return (u_cmd);
}
static ilb_status_t
i_ilbd_rule_action(const char *rule_name, const struct passwd *ps,
ilbd_cmd_t cmd, ucred_t *ucredp)
{
ilbd_rule_t *irl, *irl_next;
boolean_t is_all_rules = B_FALSE;
ilb_status_t rc = ILB_STATUS_OK;
ilb_name_cmd_t kcmd;
ilbd_cmd_t u_cmd;
char rulename[ILB_NAMESZ];
if (ps != NULL) {
if ((cmd == ILBD_ENABLE_RULE) || (cmd == ILBD_DISABLE_RULE))
rc = ilbd_check_client_enable_auth(ps);
else
rc = ilbd_check_client_config_auth(ps);
if (rc != ILB_STATUS_OK) {
if (*rule_name != '\0') {
ilbd_audit_rule_event(rule_name, NULL,
cmd, rc, ucredp);
} else {
(void) snprintf(rulename, sizeof (rulename),
"all");
ilbd_audit_rule_event(rulename, NULL, cmd, rc,
ucredp);
}
goto out;
}
}
is_all_rules = rule_name[0] == 0;
if (!is_all_rules) {
irl = i_find_rule_byname(rule_name);
if (irl == NULL) {
rc = ILB_STATUS_ENORULE;
ilbd_audit_rule_event(rule_name, NULL, cmd, rc, ucredp);
goto out;
}
rc = i_ilbd_action_switch(irl, cmd, B_FALSE, ucredp);
goto out;
}
kcmd.cmd = i_ilbd2ilb_cmd(cmd);
kcmd.flags = ILB_RULE_ALLRULES;
rc = do_ioctl(&kcmd, 0);
if (rc != ILB_STATUS_OK) {
(void) snprintf(rulename, sizeof (rulename), "all");
ilbd_audit_rule_event(rulename, NULL, cmd, rc, ucredp);
goto out;
}
irl = list_head(&ilbd_rule_hlist);
while (irl != NULL) {
irl_next = list_next(&ilbd_rule_hlist, irl);
irl->irl_flags |= ILB_FLAGS_RULE_ALLRULES;
rc = i_ilbd_action_switch(irl, cmd, B_FALSE, ucredp);
irl->irl_flags &= ~ILB_FLAGS_RULE_ALLRULES;
if (rc != ILB_STATUS_OK)
goto rollback_list;
irl = irl_next;
}
return (rc);
rollback_list:
u_cmd = get_undo_cmd(cmd);
if (u_cmd == ILBD_BAD_CMD)
return (rc);
if (is_all_rules) {
kcmd.cmd = i_ilbd2ilb_cmd(u_cmd);
(void) do_ioctl(&kcmd, 0);
}
irl = list_prev(&ilbd_rule_hlist, irl);
while (irl != NULL) {
if (is_all_rules)
irl->irl_flags |= ILB_FLAGS_RULE_ALLRULES;
rc = i_ilbd_action_switch(irl, u_cmd, B_TRUE, NULL);
irl->irl_flags &= ~ILB_FLAGS_RULE_ALLRULES;
irl = list_prev(&ilbd_rule_hlist, irl);
}
out:
return (rc);
}
ilb_status_t
ilbd_destroy_rule(ilbd_name_t rule_name, const struct passwd *ps,
ucred_t *ucredp)
{
return (i_ilbd_rule_action(rule_name, ps, ILBD_DESTROY_RULE, ucredp));
}
ilb_status_t
ilbd_enable_rule(ilbd_name_t rule_name, const struct passwd *ps,
ucred_t *ucredp)
{
return (i_ilbd_rule_action(rule_name, ps, ILBD_ENABLE_RULE, ucredp));
}
ilb_status_t
ilbd_disable_rule(ilbd_name_t rule_name, const struct passwd *ps,
ucred_t *ucredp)
{
return (i_ilbd_rule_action(rule_name, ps, ILBD_DISABLE_RULE, ucredp));
}
static ilb_rule_cmd_t *
i_alloc_kernel_rule_cmd(ilbd_rule_t *irl)
{
ilb_rule_cmd_t *kcmd;
kcmd = (ilb_rule_cmd_t *)malloc(sizeof (*kcmd));
if (kcmd == NULL)
return (kcmd);
bzero(kcmd, sizeof (*kcmd));
if (irl != NULL) {
kcmd->flags = irl->irl_flags;
kcmd->ip_ver = AF_2_IPPROTO(irl->irl_ipversion);
kcmd->vip = irl->irl_vip;
kcmd->proto = irl->irl_proto;
kcmd->min_port = irl->irl_minport;
kcmd->max_port = irl->irl_maxport;
kcmd->algo = algo_lib2impl(irl->irl_algo);
kcmd->topo = topo_lib2impl(irl->irl_topo);
kcmd->sticky_mask = irl->irl_stickymask;
kcmd->nat_src_start = irl->irl_nat_src_start;
kcmd->nat_src_end = irl->irl_nat_src_end;
kcmd->conn_drain_timeout = irl->irl_conndrain;
kcmd->nat_expiry = irl->irl_nat_timeout;
kcmd->sticky_expiry = irl->irl_sticky_timeout;
(void) strlcpy(kcmd->name, irl->irl_name,
sizeof (kcmd->name));
}
return (kcmd);
}
static ilb_status_t
adjust_srv_info_cmd(ilb_servers_info_cmd_t **kcmdp, int index)
{
ilb_servers_info_cmd_t *kcmd = *kcmdp;
size_t sz;
if (kcmd != NULL && kcmd->num_servers > index + 1)
return (ILB_STATUS_OK);
sz = sizeof (*kcmd) + (index * sizeof (ilb_server_info_t));
kcmd = (ilb_servers_info_cmd_t *)realloc(kcmd, sz);
if (kcmd == NULL)
return (ILB_STATUS_ENOMEM);
kcmd->num_servers = index;
*kcmdp = kcmd;
return (ILB_STATUS_OK);
}
static ilb_status_t
i_update_ksrv_rules(char *name, ilbd_sg_t *sg, ilbd_rule_t *rl)
{
ilb_status_t rc;
ilbd_srv_t *srvp;
ilb_servers_info_cmd_t *kcmd = NULL;
int i;
if (sg->isg_srvcount == 0)
return (ILB_STATUS_OK);
srvp = list_head(&sg->isg_srvlist);
for (i = 0; srvp != NULL; srvp = list_next(&sg->isg_srvlist, srvp)) {
rc = adjust_srv_info_cmd(&kcmd, i);
if (rc != ILB_STATUS_OK)
goto rollback_kcmd;
ILB_SGSRV_2_KSRV(&srvp->isv_srv, &kcmd->servers[i]);
if (kcmd->servers[i].min_port == 0) {
kcmd->servers[i].min_port = rl->irl_minport;
kcmd->servers[i].max_port = rl->irl_maxport;
}
i++;
}
assert(kcmd != NULL);
kcmd->cmd = ILB_ADD_SERVERS;
kcmd->num_servers = i;
(void) strlcpy(kcmd->name, name, sizeof (kcmd->name));
rc = do_ioctl(kcmd, 0);
if (rc != ILB_STATUS_OK)
goto rollback_kcmd;
for (i = 0; i < kcmd->num_servers; i++) {
int e;
if ((e = kcmd->servers[i].err) != 0) {
logerr("i_update_ksrv_rules "
"ioctl indicates failure: %s", strerror(e));
rc = ilb_map_errno2ilbstat(e);
kcmd->cmd = ILB_DEL_SERVERS;
(void) do_ioctl(kcmd, 0);
goto rollback_kcmd;
}
}
rollback_kcmd:
free(kcmd);
return (rc);
}
void
ilbd_ip_to_str(uint16_t ipversion, struct in6_addr *addr, char *valstr)
{
size_t vallen;
ilb_ip_addr_t ipaddr;
void *addrptr;
vallen = (ipversion == AF_INET) ? INET_ADDRSTRLEN : INET6_ADDRSTRLEN;
IP_COPY_IMPL_2_CLI(addr, &ipaddr);
addrptr = (ipversion == AF_INET) ?
(void *)&ipaddr.ia_v4 : (void *)&ipaddr.ia_v6;
if (inet_ntop(ipversion, (void *)addrptr, valstr, vallen) == NULL)
logerr("ilbd_ip_to_str: inet_ntop failed");
return;
}
ilb_status_t
ilbd_create_rule(ilb_rule_info_t *rl, int ev_port,
const struct passwd *ps, ucred_t *ucredp)
{
ilb_status_t rc;
ilbd_rule_t *irl = NULL;
ilbd_sg_t *sg;
ilb_rule_cmd_t *kcmd = NULL;
if (ps != NULL) {
if ((rc = ilbd_check_client_config_auth(ps)) != ILB_STATUS_OK)
goto out;
}
if (i_find_rule_byname(rl->rl_name) != NULL) {
logdebug("ilbd_create_rule: rule %s"
" already exists", rl->rl_name);
ilbd_audit_rule_event(NULL, rl, ILBD_CREATE_RULE,
ILB_STATUS_DUP_RULE, ucredp);
return (ILB_STATUS_DUP_RULE);
}
sg = i_find_sg_byname(rl->rl_sgname);
if (sg == NULL) {
logdebug("ilbd_create_rule: rule %s uses non-existent"
" servergroup name %s", rl->rl_name, rl->rl_sgname);
ilbd_audit_rule_event(NULL, rl, ILBD_CREATE_RULE,
ILB_STATUS_SGUNAVAIL, ucredp);
return (ILB_STATUS_SGUNAVAIL);
}
if ((rc = ilbd_sg_check_rule_port(sg, rl)) != ILB_STATUS_OK) {
ilbd_audit_rule_event(NULL, rl, ILBD_CREATE_RULE, rc, ucredp);
return (rc);
}
irl = i_alloc_ilbd_rule(rl);
if (irl == NULL) {
ilbd_audit_rule_event(NULL, rl, ILBD_CREATE_RULE,
ILB_STATUS_ENOMEM, ucredp);
return (ILB_STATUS_ENOMEM);
}
if (sg->isg_srvcount > 0) {
ilbd_srv_t *srv = list_head(&sg->isg_srvlist);
int32_t r_af = rl->rl_ipversion;
int32_t s_af = GET_AF(&srv->isv_addr);
if (r_af != s_af) {
logdebug("address family mismatch with servergroup");
rc = ILB_STATUS_MISMATCHSG;
goto out;
}
}
irl->irl_sg = sg;
if (RULE_HAS_HC(irl)) {
if ((rc = ilbd_hc_associate_rule(irl, ev_port)) !=
ILB_STATUS_OK)
goto out;
}
kcmd = i_alloc_kernel_rule_cmd(irl);
if (kcmd == NULL) {
rc = ILB_STATUS_ENOMEM;
goto rollback_hc;
}
kcmd->cmd = ILB_CREATE_RULE;
rc = do_ioctl(kcmd, 0);
if (rc != ILB_STATUS_OK)
goto rollback_kcmd;
rc = i_update_ksrv_rules(kcmd->name, sg, irl);
if (rc != ILB_STATUS_OK)
goto rollback_kcmd;
(void) i_attach_rule2sg(sg, irl);
list_insert_tail(&ilbd_rule_hlist, irl);
if (ps != NULL) {
rc = i_ilbd_save_rule(irl, ILBD_SCF_CREATE);
if (rc != ILB_STATUS_OK)
goto rollback_rule;
}
free(kcmd);
ilbd_audit_rule_event(NULL, rl, ILBD_CREATE_RULE,
ILB_STATUS_OK, ucredp);
return (ILB_STATUS_OK);
rollback_rule:
(void) ilbd_destroy_one_rule(irl);
ilbd_audit_rule_event(NULL, rl, ILBD_CREATE_RULE, rc, ucredp);
free(kcmd);
return (rc);
rollback_kcmd:
free(kcmd);
rollback_hc:
if (RULE_HAS_HC(irl))
(void) ilbd_hc_dissociate_rule(irl);
out:
ilbd_audit_rule_event(NULL, rl, ILBD_CREATE_RULE, rc, ucredp);
free(irl);
return (rc);
}
static uint32_t
i_flags_d2k(int f)
{
uint32_t r = 0;
if (ILB_IS_SRV_ENABLED(f))
r |= ILB_SERVER_ENABLED;
return (r);
}
ilb_status_t
i_add_srv2krules(list_t *rlist, ilb_sg_srv_t *srv, int ev_port)
{
ilb_status_t rc = ILB_STATUS_OK;
ilbd_rule_t *rl, *del_rl;
ilb_servers_info_cmd_t kcmd;
ilb_servers_cmd_t del_kcmd;
kcmd.cmd = ILB_ADD_SERVERS;
kcmd.num_servers = 1;
kcmd.servers[0].err = 0;
kcmd.servers[0].addr = srv->sgs_addr;
kcmd.servers[0].flags = i_flags_d2k(srv->sgs_flags);
(void) strlcpy(kcmd.servers[0].name, srv->sgs_srvID,
sizeof (kcmd.servers[0].name));
for (rl = list_head(rlist); rl != NULL; rl = list_next(rlist, rl)) {
(void) strlcpy(kcmd.name, rl->irl_name, sizeof (kcmd.name));
if (srv->sgs_minport == 0) {
kcmd.servers[0].min_port = rl->irl_minport;
kcmd.servers[0].max_port = rl->irl_maxport;
} else {
kcmd.servers[0].min_port = srv->sgs_minport;
kcmd.servers[0].max_port = srv->sgs_maxport;
}
rc = do_ioctl((void *)&kcmd, 0);
if (rc != ILB_STATUS_OK) {
logdebug("i_add_srv2krules: do_ioctl call failed");
del_rl = list_prev(rlist, rl);
goto rollback;
}
if (kcmd.servers[0].err != 0) {
logerr("i_add_srv2krules: SIOCILB ioctl returned"
" error %d", kcmd.servers[0].err);
rc = ilb_map_errno2ilbstat(kcmd.servers[0].err);
del_rl = list_prev(rlist, rl);
goto rollback;
}
if (RULE_HAS_HC(rl)) {
if ((rc = ilbd_hc_add_server(rl, srv, ev_port)) !=
ILB_STATUS_OK) {
logerr("i_add_srv2krules: cannot start timer "
" for rules %s server %s", rl->irl_name,
srv->sgs_srvID);
del_rl = rl;
goto rollback;
}
}
}
return (rc);
rollback:
del_kcmd.cmd = ILB_DEL_SERVERS;
del_kcmd.num_servers = 1;
del_kcmd.servers[0].addr = srv->sgs_addr;
while (del_rl != NULL) {
if (RULE_HAS_HC(del_rl))
(void) ilbd_hc_del_server(del_rl, srv);
(void) strlcpy(del_kcmd.name, del_rl->irl_name,
sizeof (del_kcmd.name));
(void) do_ioctl((void *)&del_kcmd, 0);
del_rl = list_prev(rlist, del_rl);
}
return (rc);
}
ilb_status_t
i_rem_srv_frm_krules(list_t *rlist, ilb_sg_srv_t *srv, int ev_port)
{
ilb_status_t rc = ILB_STATUS_OK;
ilbd_rule_t *rl, *add_rl;
ilb_servers_cmd_t kcmd;
ilb_servers_info_cmd_t add_kcmd;
kcmd.cmd = ILB_DEL_SERVERS;
kcmd.num_servers = 1;
kcmd.servers[0].err = 0;
kcmd.servers[0].addr = srv->sgs_addr;
for (rl = list_head(rlist); rl != NULL; rl = list_next(rlist, rl)) {
(void) strlcpy(kcmd.name, rl->irl_name, sizeof (kcmd.name));
rc = do_ioctl((void *)&kcmd, 0);
if (rc != ILB_STATUS_OK) {
logdebug("i_rem_srv_frm_krules: do_ioctl"
"call failed");
add_rl = list_prev(rlist, rl);
goto rollback;
}
if (kcmd.servers[0].err != 0) {
logerr("i_rem_srv_frm_krules: SIOCILB ioctl"
" returned error %s",
strerror(kcmd.servers[0].err));
rc = ilb_map_errno2ilbstat(kcmd.servers[0].err);
add_rl = list_prev(rlist, rl);
goto rollback;
}
if (RULE_HAS_HC(rl) &&
(rc = ilbd_hc_del_server(rl, srv)) != ILB_STATUS_OK) {
logerr("i_rem_srv_frm_krules: cannot delete "
"timer for rules %s server %s", rl->irl_name,
srv->sgs_srvID);
add_rl = rl;
goto rollback;
}
}
return (rc);
rollback:
if (ev_port == -1)
return (rc);
add_kcmd.cmd = ILB_ADD_SERVERS;
add_kcmd.num_servers = 1;
add_kcmd.servers[0].err = 0;
add_kcmd.servers[0].addr = srv->sgs_addr;
add_kcmd.servers[0].flags = i_flags_d2k(srv->sgs_flags);
(void) strlcpy(add_kcmd.servers[0].name, srv->sgs_srvID,
sizeof (add_kcmd.servers[0].name));
while (add_rl != NULL) {
if (srv->sgs_minport == 0) {
add_kcmd.servers[0].min_port = add_rl->irl_minport;
add_kcmd.servers[0].max_port = add_rl->irl_maxport;
} else {
add_kcmd.servers[0].min_port = srv->sgs_minport;
add_kcmd.servers[0].max_port = srv->sgs_maxport;
}
if (RULE_HAS_HC(add_rl))
(void) ilbd_hc_add_server(add_rl, srv, ev_port);
(void) strlcpy(add_kcmd.name, add_rl->irl_name,
sizeof (add_kcmd.name));
(void) do_ioctl((void *)&add_kcmd, 0);
add_rl = list_prev(rlist, add_rl);
}
return (rc);
}